Right now the module allow to publish content in the user profile but would be great to allow the module to publish directly in a Facebook page if the user has the correct permissions.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

gnuget created an issue. See original summary.

Anonymous’s picture

I support this. For organizational websites it is crucial to be able to configure posting on a page. Best would be to allow this per content type, as an organisation might have more than one page, and so it would be nice to be flexible here.

Anonymous’s picture

WebWalker3D’s picture

I am also looking for a similar feature, as I want to have content posted to the business page.

The module as it stands now would be great for someone writing a personal blog, but aside from that, I'm not sure the autopost feature is something "individuals" would be inclined to use. Especially having an application post on their behalf, and privacy concerns, etc. BUT for pages, it makes tons of sense.

Can we please look at addressing posting to pages vs personal newsfeeds?

Thank you!

sahaj’s picture

This is really a fail for the Drupal Social Initiative to harmonize Social Networking functionality in Drupal.

Without the ability to post on FB Pages, we have to opt for another module (https://www.drupal.org/project/autopost_social), which itself do not offer connections beside FB and Twitter.

Please let's try to harmonize a bit more ))

gnuget’s picture

Priority: Normal » Critical

Now that facebook deprecated (and removed) the publish_actions permission this is now critical.

Without this the module cannot be used without custom code.

I will pump the priority and hope be able to spend time on it this week.

gnuget’s picture

Category: Feature request » Bug report
joshua.boltz’s picture

Initial patch added that adds support for posting to a Facebook page. Seems to be working pretty well, but does rely on custom code, such as in a hook_entity_insert() or hook_entity_update().

Most notably, it updates the code to provide both the Facebook SDK and the League Oauth library and other re-factoring.
In order to post to a Facebook page, there need to be a few things:
1) A Facebook App (these are the credentials you put into the Social Post Facebook module configuration)
2) A Facebook Page
3) A Facebook user who has the `manage_pages` and `publish_pages` permission, which will typically be the user who created or has permission to manage the Facebook page.
4) Custom code in a custom module, such as in a hook_entity_insert() or hook_entity_update() that is triggered on content changes. See below.

// A function called from a hook_entity_insert() or hook_entity_update() to push content to Facebook.
function MODULE_post_to_facebook(EntityInterface $entity) {

  /* @var \Drupal\social_post_facebook\Plugin\Network\FacebookPostInterface $facebookPost */
  // Gets instance of Social Post Facebook.
  $facebookPost = \Drupal::service('plugin.network.manager')
    ->createInstance('social_post_facebook');

  // Gets the Social Post manager.
  /* @var \Drupal\social_post\SocialPostManager $post_manager */
  $post_manager = \Drupal::service('social_post.post_manager');

  $post_manager->setPluginId('social_post_facebook');

  $current_user = \Drupal::service('current_user');

  $accounts = $post_manager->getAccountsByUserId('social_post_facebook', $current_user->id());

  $message = 'This is my message'; // This can be extracted from a field value too.

  foreach ($accounts as $account) {
    if (!$account) {
      \Drupal::logger('module')
        ->error('No @service credentials found.', ['@service' => $account]);
      return;
    }
    $sdk = $facebookPost->getSdk();
    MODULE_send_post_facebook_page($sdk, $account, $message);
  }
}

// Do the post to the Facebook page after getting the page token.
function MODULE_send_post_facebook_page(Facebook $sdk, SocialPost $facebook_user, $text) {
  $facebook_page = '123456789'; // This can be changed to store to a field value, configuration, or setting.
  if (!$facebook_page) {
    \Drupal::logger('module')->error('The facebook page ID is required');
    return;
  }
  // Facebook page token is valid for 10 minutes, so I wrote this method to regenerated if it
  // was already invalid.
  $facebook_token = MODULE_get_facebook_page_token($sdk, $facebook_user, $facebook_page);
  if (!$facebook_token) {
    return;
  }
  $sdk->post('/' . $facebook_page . '/feed', ['message' => $text], $facebook_token);
}

// Get a page token so the app can post to a Facebook page.
function MODULE_get_facebook_page_token(Facebook $sdk, SocialPost $facebook_user, $facebook_page) {
  $facebook_page_token = \Drupal::state()->get('facebook_page_token');
  $facebook_page_token_time = \Drupal::state()->get('facebook_page_token_datetime');
  $current_time = \Drupal::time()->getCurrentTime();
  if (!is_null($facebook_page_token_time) && !is_null($facebook_page_token)) {
    // Regenerate the token 10 mins before to expire (it is valid for an hour).
    $expire_time = $facebook_page_token_time + (60 * 50);
    if ($current_time < $expire_time) {
      return $facebook_page_token;
    }
  }

  // Generate a new token.
  $post_manager = \Drupal::service('social_post.post_manager');
  $token = $post_manager->getToken($facebook_user->getProviderUserId());
  $response = $sdk->get('/' . $facebook_page . '?fields=access_token', $token);
  if ($response->getHttpStatusCode() != 200) {
    \Drupal::logger('module')->error('There was an error getting the facebook page access_token');
    return FALSE;
  }
  $data = $response->getDecodedBody();
  $token = $data['access_token'];
  \Drupal::state()->set('facebook_page_token', $token);
  \Drupal::state()->set('facebook_page_token_datetime', $current_time);
  return $token;
}
gvso’s picture

@gnuget Are you planning to solve this issue on 1.x?

I guess it's time for us to also provide a 2.x branch that really works. I'll try to work on that in the coming weeks.

This is really a fail for the Drupal Social Initiative to harmonize Social Networking functionality in Drupal.

It's hard to maintain all modules, specially when I do not use them anymore. I have taken Social Auth as a priority for now since you can basically build anything from there. That being said, any contributions are welcome to improve Social Post and push it forward. I agree Social Post Facebook should be a priority, but we need everyone's help for that to happen.

gnuget’s picture

Hi @gvso.

@gnuget Are you planning to solve this issue on 1.x?

In the branch 1.x it is possible to set custom permissions and adding code similar as #8 is possible to publish on pages, so the branch 1.x is more or less at the same point as 2.x. but besides that, no new development has been made on the 1.x (because I thought the branch 2.x was the way to go so I stopped the development of the 1.x branch because I didn't know what were the plans).

Last week I reviewed and merged some patches on the 2.x:

And I was planning this week start working on providing a UI to #8 so everyone can use it without the need to write too much custom code but if you have different plans just let me know (Let me know if I should stop reviewing/working on the branch, in theory, I'm not the maintainer I just wanted to help)

gvso’s picture

Oh wait. I didn't actually notice you were updating 2.x. Thanks for that! Last time I checked, it was very behind what's expected. FYI, I have only added new features to 2.x and restrict myself to only solve bugs in 1.x.

I haven't checked 2.x code, but I already noticed that it has two dependencies:

"league/oauth2-facebook": "^2.0",
"facebook/graph-sdk" : "~5.0"

We should only use one of them. Although I have a preference over PHP The League because most projects are using it, I am fine if you decide to use Facebook's official library.

gnuget’s picture

Hi @gvso.

I've been discussing with Joshua about this module the last few weeks and we think that this module should depends on social_auth_facebook for linking the accounts to facebook and remove the auth code from this module, what do you think? it would be possible without touching too much the social_auth_facebook code?

I'm aware that the auth module is already on Beta so i don't want to alter it too much.

gvso’s picture

TL;TR You can do it, but Social Auth implementers were not developed with this use case in mind.

It is true that is technically possible to depend on Social Auth Facebook for this. However, by design, Social Auth modules already gives you options that you might not need for Social Post (the settings form and menu link in admin page for instance). This might confuse people.

Yes, we might consider this a lack of good design, but Social Post was not initially developed with the idea of having as many implementers as Social Auth. There are only a few providers that you might want to post things to or even allow content posting. That's the reason we didn't initially split the modules into 3: a mere authentication module that can be consumed by Social Auth and Social Post implementers.

joshua.boltz’s picture

Added patch that adds support for posting media along with a status update to a Facebook page. Similar to https://www.drupal.org/project/social_post_facebook/issues/2988691#comme..., but has changed due to changes in this issue for adding support for posting to a Facebook page.

With this patch, the following can be added into a entity life cycle event, such as hook_entity_insert(), such as when a new node is created.

function my_module_entity_insert(EntityInterface $entity) {
  my_module_post_to_facebook($entity) {
}

/**
 * Utility function to post to Facebook API.
 *
 * @param \Drupal\Core\Entity\EntityInterface $entity
 *   The node being created or updated being sent to Facebook.
 */
function my_module_post_to_facebook(EntityInterface $entity) {

  /* @var \Drupal\social_post_facebook\Plugin\Network\FacebookPost $facebookPost */
  // Gets instance of Social Post Facebook.
  $facebookPost = \Drupal::service('plugin.network.manager')
    ->createInstance('social_post_facebook');

  // Gets the Social Post manager.
  /* @var \Drupal\social_post\SocialPostManager $post_manager */
  $post_manager = \Drupal::service('social_post.post_manager');

  $post_manager->setPluginId('social_post_facebook');

  $current_user = \Drupal::service('current_user');

  $accounts = $post_manager->getAccountsByUserId('social_post_facebook', $current_user->id());

  $post = [];

  if ($entity->hasField('field_facebook_post')) {
    try {
      $facebook_post_value = $entity->get('field_facebook_post')->first();
    }
    catch (MissingDataException $e) {
      \Drupal::logger('my_module')->error($e->getMessage());
    }

    if (!empty($facebook_post_value)) {
      $post['message'] = $facebook_post_value->getValue()['value'];
    }
  }

  // Use field_image as source images for media posting to Facebook.
  $images = ($entity->hasField('field_image') && !empty($entity->get('field_image')
      ->getValue())) ? $entity->get('field_image')
    ->referencedEntities() : [];

  $image_paths = [];
  foreach ($images as $image) {
    $image_paths[] = file_create_url($image->getFileUri());
  }

  $post['media_paths'] = $image_paths;

  // Page ID stored in a settings variable in settings.php
  $page = \Drupal::config('fb_page.settings')->get('id');
  if (!$page) {
    \Drupal::logger('module')->error('The facebook page ID is required');
    return;
  }

  if (!empty($post)) {
    foreach ($accounts as $account) {
      if (!$account) {
        \Drupal::logger('module')
          ->error('No @service credentials found.', ['@service' => $account]);
        return;
      }
      $sdk = $facebookPost->getSdk();
      // Get access token to post to a Facebook page.
      $access_token = my_module_get_facebook_page_token($sdk, $account, $page);
      if (!$access_token) {
        return;
      }

      if (!$facebookPost->doPost($access_token, $post, $page)) {
        \Drupal::logger('my_module')
          ->error('There was an error posting to the Facebook page.');
      }
    }
  }
}

/**
 * Check if the facebook page token is valid and return it.
 *
 * If it is not valid generate a new one using the facebook graph api.
 *
 * @param \Facebook\Facebook $sdk
 *   The facebook Object.
 * @param \Drupal\social_post\entity\SocialPost $facebook_user
 *   The facebookUser entity.
 * @param string $facebook_page
 *   The facebook page ID.
 *
 * @return string|bool
 *   A valid token or false if cannot be generated.
 */
function my_module_get_facebook_page_token(Facebook $sdk, SocialPost $facebook_user, $facebook_page) {
  $facebook_page_token = \Drupal::state()->get('facebook_page_token');
  $facebook_page_token_time = \Drupal::state()
    ->get('facebook_page_token_datetime');
  $current_time = \Drupal::time()->getCurrentTime();
  if (!is_null($facebook_page_token_time) && !is_null($facebook_page_token)) {
    // Regenerate the token 10 mins before to expire (it is valid for an hour).
    $expire_time = $facebook_page_token_time + (60 * 50);
    if ($current_time < $expire_time) {
      return $facebook_page_token;
    }
  }

  // Generate a new token.
  $post_manager = \Drupal::service('social_post.post_manager');
  $token = $post_manager->getToken($facebook_user->getProviderUserId());
  $response = $sdk->get('/' . $facebook_page . '?fields=access_token', $token);
  if ($response->getHttpStatusCode() != 200) {
    \Drupal::logger('module')
      ->error('There was an error getting the facebook page access_token');
    return FALSE;
  }
  $data = $response->getDecodedBody();
  $token = $data['access_token'];
  \Drupal::state()->set('facebook_page_token', $token);
  \Drupal::state()->set('facebook_page_token_datetime', $current_time);
  return $token;
}
SocialNicheGuru’s picture

Status: Active » Needs review