summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAleix Quintana Alsius (kinta)2016-05-30 21:46:14 (GMT)
committerAleix Quintana Alsius (kinta)2016-05-30 21:46:14 (GMT)
commitf930f7c080bf22f3f84b0b06e40473947e9ffd81 (patch)
tree6d1edd983f1d44a9a5b0c15aa3e562a9ba7a71a8
Initial commit.
-rw-r--r--README.txt61
-rw-r--r--composer.json9
-rw-r--r--gnusocial.info.yml8
-rw-r--r--gnusocial.install23
-rw-r--r--gnusocial.links.menu.yml5
-rw-r--r--gnusocial.module73
-rw-r--r--gnusocial.permissions.yml12
-rw-r--r--gnusocial.routing.yml7
-rw-r--r--gnusocial.services.yml7
-rw-r--r--src/Controller/GnusocialController.php50
-rw-r--r--src/Element/GnusocialConversation.php94
-rw-r--r--src/Element/GnusocialJS.php192
-rw-r--r--src/Element/GnusocialStatus.php130
-rw-r--r--src/Form/GnusocialSettingsForm.php229
-rw-r--r--src/GnusocialService.php234
-rw-r--r--src/Plugin/Field/FieldFormatter/GnusocialDefaultFormatter.php168
-rw-r--r--src/Plugin/Field/FieldFormatter/GnusocialJSFormatter.php79
-rw-r--r--src/Plugin/Field/FieldType/GnusocialItem.php70
-rw-r--r--src/Plugin/Field/FieldWidget/GnusocialDefaultWidget.php104
-rw-r--r--templates/gnusocial-conversation.html.twig29
-rw-r--r--templates/gnusocial-noscript.html.twig15
-rw-r--r--templates/gnusocial-status.html.twig55
22 files changed, 1654 insertions, 0 deletions
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..0b96edb
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,61 @@
+Gnusocial drupal module
+-----------------------
+Provides a field to enable gnusocial comments on any content type. It depends
+on composer manager to enable the needed libraries (oauth-subscriber) through
+composer.
+
+You can use this module to post statuses when a new content is published, or
+simply to attach any conversation living in a gnusocial instance that implements
+the statusnet api to retrieve the conversation.
+
+If you want to simply attach the conversation
+---------------------------------------------
+You'll have to add the field to the content you want in field configuration
+check "Gnusocial Comments" and uncheck "Post to GnuSocial". For every post
+you'll have to manually fill the field with the conversation json url. It's your
+job to get it.
+
+To get full functionality (attach comments and post to gnusocial):
+------------------------------------------------------------------
+You'll need to configure both in gnusocial and drupal.
+For Gnusocial part create the user that will act as bot. It will create the
+statuses anouncing your blog entries, allowing to create a conversation over.
+Go to: Settings >connections; There click on register an Oauth client applic-
+ation in right sidebar (https://gnusocialurl/settings/oauthapps).
+
+You'll have to register a new application, so fill the fields:
+- Icon if you want it,
+- Name: a descriptive name of the app, for example drupal_gnusocial.
+- Description: Explain this application, for example: Communicates with a
+ drupal site.
+- Source Url: The drupal url.
+- Organization: Optionally, the name of the organization that owns the drupal
+ with gnusocial integration.
+- Homepage: The homepage of the organization .
+- Callback url: http://yourdrupal/admin/config/services/gnusocial.
+- Type of application: Browser.
+- Read-write
+
+After that a "Consumer key" and "Consumer secret" will be provided. Copy these
+in a text editor. Don't close the gnusocial tab (session) yet.
+
+We'll go to your drupal instance, there configure in: configuration > services >
+gnusocial. It will do a 3-legged authentication against the provided gnusocial
+url, so add the base url of gnusocial ( https://yourgnusocial.url.com ), OAuth
+consumer key, the "Consumer secret" and click save. After saving, click "update
+tokens", it will redirect to gnusocial url to complete the authentication,
+allowing the application to interact with gnusocial, after that it will redirect
+to drupal and the process will be completed.
+
+
+Developer info:
+---------------
+You can use templates to theme the comments:
+- field: You can use the field template as gnusocial comments is a field.
+- gnusocial-conversation.html.twig will be the container of each conversation
+ (between field wrapper and each status). It has the statuses list, the more
+ link, and the conversation link.
+- gnusocial-status.html.twig will be the template used to render each status.
+
+
+It remains as a BIG TODO the plan to allow commenting directly from drupal.
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..ff9c2ce
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,9 @@
+{
+ "name": "drupal/gnusocial",
+ "description": "composer.json for gnusocial module.",
+ "type": "drupal-module",
+ "require": {
+ "guzzlehttp/oauth-subscriber": "0.3.*"
+ }
+
+}
diff --git a/gnusocial.info.yml b/gnusocial.info.yml
new file mode 100644
index 0000000..6873552
--- /dev/null
+++ b/gnusocial.info.yml
@@ -0,0 +1,8 @@
+name: Gnusocial
+description: A module to integrate gnusocial conversations as a content comments.
+package: Gnusocial
+type: module
+version: 1.0
+core: 8.x
+dependencies:
+ - composer_manager
diff --git a/gnusocial.install b/gnusocial.install
new file mode 100644
index 0000000..5b9a3fc
--- /dev/null
+++ b/gnusocial.install
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the gnusocial module.
+ */
+
+/**
+ * Implements hook_requirements().
+ */
+function gnusocial_requirements($phase) {
+ $requirements = [];
+ if ($phase == 'install') {
+ if (!class_exists('GuzzleHttp\Subscriber\Oauth\Oauth1')) {
+ $requirements['guzzle_subscriber_library'] = [
+ 'description' => t('Address requires the guzzleHttp/Subscriber library to make oauth work.'),
+ 'severity' => REQUIREMENT_ERROR,
+ ];
+ }
+ }
+
+ return $requirements;
+}
diff --git a/gnusocial.links.menu.yml b/gnusocial.links.menu.yml
new file mode 100644
index 0000000..8e938b1
--- /dev/null
+++ b/gnusocial.links.menu.yml
@@ -0,0 +1,5 @@
+gnusocial.settings:
+ title: 'GnuSocial'
+ description: 'Settings for gnusocial integration.'
+ parent: system.admin_config_services
+ route_name: gnusocial.settings
diff --git a/gnusocial.module b/gnusocial.module
new file mode 100644
index 0000000..b5a055b
--- /dev/null
+++ b/gnusocial.module
@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * @file
+ * Enables gnusocial conversation field.
+ */
+
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Implements hook_theme().
+ */
+function gnusocial_theme() {
+ $domain = \Drupal::config('gnusocial.settings')->get('gnusocial_url');
+ return [
+ 'gnusocial_noscript' => [
+ 'variables' => [
+ 'message' => t('View the gnusocial conversation.'),
+ 'url' => $domain,
+ ],
+ ],
+ 'gnusocial_status' => [
+ /*'variables' => [
+ 'url' => 'url',
+ ],*/
+ 'render element' => 'element',
+ ],
+ 'gnusocial_conversation' => [
+ 'render element' => 'element',
+ ],
+ ];
+}
+
+/**
+ * Implements hook_entity_update().
+ */
+function gnusocial_entity_presave(EntityInterface $entity) {
+ // Only act on content entities.
+ if (!($entity instanceof ContentEntityInterface)) {
+ return;
+ }
+
+ $field = \Drupal::service('gnusocial.manager')
+ ->getFields($entity->getEntityTypeId());
+ if (!$entity->hasField(key($field))) {
+ return;
+ }
+ $field_name = key($field);
+ $config = \Drupal::config('gnusocial.settings');
+
+ // Post to gnusocial if checked.
+ if ($entity->get($field_name)->post_gnusocial && $entity->get($field_name)->status) {
+ $url = $entity->toUrl()->setAbsolute(TRUE);
+
+ // Get the rendered teaser display to include in status//TODO!!!
+ $view_builder = \Drupal::entityManager()->getViewBuilder('node');
+ $view_output = $view_builder->view($entity, 'teaser');
+ $vb = drupal_render($view_output);
+ $body = $entity->body->view(["settings" => ["format" => "plain_text"]]);
+ $body_out = drupal_render($body);
+
+ // Post to gnusocial.
+ $poster = \Drupal::service('gnusocial.manager')
+ ->postNotice($entity->getTitle() . ": " . $url->toString());
+
+ $view_builder = \Drupal::entityManager()->getViewBuilder('node');
+ // Fill the conversation_id field.
+ $gs_field = $entity->get($field_name);
+ $gs_field[0]->get('conversation_id')->setValue('/api/statusnet/conversation/' . $poster[0]->statusnet_conversation_id . ".json");
+ return;
+ }
+}
diff --git a/gnusocial.permissions.yml b/gnusocial.permissions.yml
new file mode 100644
index 0000000..19c2cc6
--- /dev/null
+++ b/gnusocial.permissions.yml
@@ -0,0 +1,12 @@
+administer gnusocial:
+ title: 'Administer GnuSocial'
+ description: 'Perform administrative tasks with GnuSocial.'
+view gnusocial comments:
+ title: 'View GnuSocial comments'
+ description: 'Allows access to view GnuSocial comments.'
+toggle gnusocial comments:
+ title: 'Toggle GnuSocial comments'
+ description: 'Allows access to toggle gnusocial comments when editing content.'
+post to gnusocial:
+ title: 'Post to GnuSocial'
+ description: 'Allows posting to configured gnusocial.'
diff --git a/gnusocial.routing.yml b/gnusocial.routing.yml
new file mode 100644
index 0000000..d6cc190
--- /dev/null
+++ b/gnusocial.routing.yml
@@ -0,0 +1,7 @@
+gnusocial.settings:
+ path: '/admin/config/services/gnusocial'
+ defaults:
+ _form: '\Drupal\gnusocial\Form\GnusocialSettingsForm'
+ _title: 'GnuSocial'
+ requirements:
+ _permission: 'administer gnusocial'
diff --git a/gnusocial.services.yml b/gnusocial.services.yml
new file mode 100644
index 0000000..ae5c5ca
--- /dev/null
+++ b/gnusocial.services.yml
@@ -0,0 +1,7 @@
+services:
+ gnusocial.manager:
+ class: Drupal\gnusocial\GnusocialService
+ arguments: ['@entity_type.manager', '@entity_field.manager', '@config.factory', '@http_client_factory']
+#, '@current_user',
+# gnusocial.fetch_tokens:
+# class: Drupal\gnusocial\GnusocialService
diff --git a/src/Controller/GnusocialController.php b/src/Controller/GnusocialController.php
new file mode 100644
index 0000000..d3f58b0
--- /dev/null
+++ b/src/Controller/GnusocialController.php
@@ -0,0 +1,50 @@
+<?php
+
+
+namespace Drupal\gnusocial\Controller;
+
+use Drupal\gnusocial\GnusocialService;
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Http\ClientFactory;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Returns responses for gnusocial module routes.
+ */
+class GnusocialController extends ControllerBase {
+
+ /**
+ * The Gnusocial service.
+ *
+ * @var \Drupal\gnusocial\GnusocialService
+ */
+ protected $gnusocialService;
+ /**
+ * Guzzle Http Client Factory.
+ *
+ * @var GuzzleHttp\ClientFactory
+ */
+ protected $httpClientFactory;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct(
+ GnusocialService $gnusocial_service,
+ ClientFactory $http_client_factory
+ ) {
+ $this->gnusocialService = $gnusocial_service;
+ $this->httpClientFactory = $http_client_factory;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('gnusocial.manager'),
+ $container->get('http_client_factory')
+ );
+ }
+
+}
diff --git a/src/Element/GnusocialConversation.php b/src/Element/GnusocialConversation.php
new file mode 100644
index 0000000..e54904a
--- /dev/null
+++ b/src/Element/GnusocialConversation.php
@@ -0,0 +1,94 @@
+<?php
+
+namespace Drupal\gnusocial\Element;
+
+use Drupal\Core\Render\Element\RenderElement;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\gnusocial\GnusocialService;
+
+/**
+ * Provides GnusocialConversation render element.
+ *
+ * @RenderElement("gnusocial_conversation")
+ */
+class GnusocialConversation extends RenderElement implements ContainerFactoryPluginInterface {
+
+ /**
+ * The Gnusocial service.
+ *
+ * @var \Drupal\gnusocial\GnusocialService
+ */
+ protected $gnusocialService;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct(array $configuration, $plugin_id, $plugin_definition, GnusocialService $gnusocial_service) {
+ parent::__construct($configuration, $plugin_id, $plugin_definition);
+
+ $this->gnusocialService = $gnusocial_service;
+ }
+
+ /**
+ * Creates an instance of the plugin.
+ *
+ * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
+ * The container to pull out services used in the plugin.
+ * @param array $configuration
+ * A configuration array containing information about the plugin instance.
+ * @param string $plugin_id
+ * The plugin ID for the plugin instance.
+ * @param mixed $plugin_definition
+ * The plugin implementation definition.
+ *
+ * @return static
+ * Returns an instance of this plugin.
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ /** @var \Drupal\gnusocial\GnusocialService $gnusocial_service */
+ $gnusocial_service = $container->get('gnusocial.manager');
+
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $gnusocial_service
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getInfo() {
+ return array(
+ '#title' => '',
+ '#url' => '',
+ '#identifier' => '',
+ '#callbacks' => '',
+ '#theme' => 'gnusocial_conversation',
+ '#attributes' => ['id' => 'gnusocial_conversation'],
+ '#pre_render' => [
+ [get_class($this), 'preRenderGnusocialConversation'],
+ ],
+ );
+ }
+
+ /**
+ * Prepare the render array for the template.
+ */
+ public static function preRenderGnusocialConversation(array $element) {
+
+ // The statuses list.
+ $element['statuses'] = $element['#statuses'];
+
+ // The more link to json.
+ $element['conversation_json'] = $element['#conversation_json'];
+
+ // Url to first notice in conversation.
+ $element['conversation_url'] = $element['#conversation_url'];
+
+ return $element;
+ }
+
+}
diff --git a/src/Element/GnusocialJS.php b/src/Element/GnusocialJS.php
new file mode 100644
index 0000000..100197d
--- /dev/null
+++ b/src/Element/GnusocialJS.php
@@ -0,0 +1,192 @@
+<?php
+
+namespace Drupal\gnusocial\Element;
+
+use Drupal\Core\Render\Element\RenderElement;
+use Drupal\gnusocial\GnusocialService;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides GnuSocial JS render element.
+ *
+ * @RenderElement("gnusocial_js")
+ */
+class GnusocialJS extends RenderElement implements ContainerFactoryPluginInterface {
+
+ /**
+ * The Gnusocial service.
+ *
+ * @var \Drupal\gnusocial\GnusocialService $gnusocialService
+ */
+ protected $gnusocialService;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct(array $configuration, $plugin_id, $plugin_definition, GnusocialService $gnusocial_service) {
+ parent::__construct($configuration, $plugin_id, $plugin_definition);
+
+ $this->gnusocialService = $gnusocial_service;
+ }
+
+ /**
+ * Creates an instance of the plugin.
+ *
+ * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
+ * The container to pull out services used in the plugin.
+ * @param array $configuration
+ * A configuration array containing information about the plugin instance.
+ * @param string $plugin_id
+ * The plugin ID for the plugin instance.
+ * @param mixed $plugin_definition
+ * The plugin implementation definition.
+ *
+ * @return static
+ * Returns an instance of this plugin.
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ /** @var \Drupal\gnusocial\GnusocialService $gnusocial_service */
+ $gnusocial_service = $container->get('gnusocial.manager');
+
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $gnusocial_service
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getInfo() {
+ return array(
+ '#title' => '',
+ '#url' => '',
+ '#identifier' => '',
+ '#callbacks' => '',
+ '#theme_wrappers' => ['gnusocial_noscript', 'container'],
+ '#attributes' => ['id' => 'gnusocial_conversation'],
+ '#pre_render' => [
+ get_class() . '::generatePlaceholder',
+ ],
+ );
+ }
+
+ /**
+ * The #pre_render callback to generate a placeholder.
+ *
+ * @param array $element
+ * A renderable array.
+ *
+ * @return array
+ * The updated renderable array containing the placeholder.
+ */
+ public static function generatePlaceholder(array $element) {
+ if (\Drupal::currentUser()->hasPermission('view gnusocial comments')) {
+ $element[] = [
+ '#lazy_builder' => [
+ get_class() . '::displayGnusocialComments',
+ [
+ $element['#title'],
+ $element['#url'],
+ $element['#identifier'],
+ ],
+ ],
+ '#create_placeholder' => TRUE,
+ ];
+ }
+
+ return $element;
+ }
+
+ /**
+ * Post render function to inject the GnuSocial JavaScript. TODO.
+ */
+ public static function displayGnusocialComments($title, $url, $identifier) {
+ $gnusocial_settings = \Drupal::config('gnusocial.settings');
+ /** @var \Drupal\Core\Render\RendererInterface $renderer */
+ $renderer = \Drupal::service('renderer');
+ $gs = \Drupal::service('gnusocial.manager');
+ $element = [
+ '#theme_wrappers' => ['gnusocial_noscript', 'container'],
+ '#attributes' => ['id' => 'gnusocial_conversation'],
+ ];
+ $renderer->addCacheableDependency($element, $gnusocial_settings);
+
+ $stream = $gs->getData("/api/statuses/show/11.json");
+ // $avatar_markup = "<a href='".$avatar->statusnet_profile_url."'>
+ // <img src='".$avatar->profile_image_url."'>".$avatar->screen_name."</a>";.
+ $gnusocial = [
+ 'domain' => $gnusocial_settings->get('gnusocial_url'),
+ 'url' => $url,
+ 'title' => $title,
+ 'identifier' => $identifier,
+ 'data' => ['#markup' => "<pre>" . $stream->text . "</pre>"],
+ ];
+
+ // If the user is logged in, we can inject the username and email.
+ /*
+ $account = \Drupal::currentUser();
+ if ($gnusocial_settings->get('behavior.gnusocial_inherit_login') &&
+ !$account->isAnonymous()) {
+ $renderer->addCacheableDependency($element, $account);
+ $gnusocial['name'] = $account->getUsername();
+ $gnusocial['email'] = $account->getEmail();
+ }
+ */
+
+ // Provide alternate language support if desired.
+ /*
+ if ($gnusocial_settings->get('behavior.gnusocial_localization')) {
+ $gnusocial['language'] = \Drupal::languageManager()
+ ->getCurrentLanguage()
+ ->getId();
+ }
+ */
+
+ // Check if we want to track new comments in some stats analytics service.
+ /*
+ if ($gnusocial_settings
+ ->get('behavior.gnusocial_track_newcomment_stats')) {
+ // Add a callback when a new comment is posted.
+ $gnusocial['callbacks']['onNewComment'][] =
+ 'Drupal.disqus.disqusTrackNewComment';
+ // Attach the js with the callback implementation.
+ //TODO: add stats service query conditional
+ if piwik: $gnusocial['#attached']['library'][] = 'gnusocial/piwik_stats';
+ if ga: $gnusocial['#attached']['library'][] = 'gnusocial/ga_stats';
+ }
+ */
+
+ /*
+ * Pass callbacks on if needed. Callbacks array is two dimensional array
+ * with callback type as key on first level and array of JS callbacks on the
+ * second level.
+ *
+ * Example:
+ * @code
+ * $element = [
+ * '#type' => 'gnusocial',
+ * '#callbacks' = [
+ * 'onNewComment' => [
+ * 'myCallbackThatFiresOnCommentPost',
+ * 'Drupal.mymodule.anotherCallbInsideDrupalObj',
+ * ],
+ * ],
+ * ];
+ * @endcode
+ */
+ if (!empty($element['#callbacks'])) {
+ $gnusocial['callbacks'] = $element['#callbacks'];
+ }
+
+ // Add the gnusocial.js and all the settings to process the JavaScript and
+ // load Gnusocial.
+ $element['#attached']['library'][] = 'gnusocial/gnusocial.js';
+ $element['#attached']['drupalSettings']['gnusocial'] = $gnusocial;
+ return $element;
+ }
+
+}
diff --git a/src/Element/GnusocialStatus.php b/src/Element/GnusocialStatus.php
new file mode 100644
index 0000000..ffc683d
--- /dev/null
+++ b/src/Element/GnusocialStatus.php
@@ -0,0 +1,130 @@
+<?php
+
+namespace Drupal\gnusocial\Element;
+
+use Drupal\Core\Render\Element\RenderElement;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Url;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\gnusocial\GnusocialService;
+
+/**
+ * Provides GnusocialStatus render element.
+ *
+ * @RenderElement("gnusocial_status")
+ */
+class GnusocialStatus extends RenderElement implements ContainerFactoryPluginInterface {
+
+ /**
+ * The Gnusocial service.
+ *
+ * @var \Drupal\gnusocial\GnusocialService
+ */
+ protected $gnusocialService;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct(array $configuration, $plugin_id, $plugin_definition, GnusocialService $gnusocial_service) {
+ parent::__construct($configuration, $plugin_id, $plugin_definition);
+
+ $this->gnusocialService = $gnusocial_service;
+ }
+
+ /**
+ * Creates an instance of the plugin.
+ *
+ * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
+ * The container to pull out services used in the plugin.
+ * @param array $configuration
+ * A configuration array containing information about the plugin instance.
+ * @param string $plugin_id
+ * The plugin ID for the plugin instance.
+ * @param mixed $plugin_definition
+ * The plugin implementation definition.
+ *
+ * @return static
+ * Returns an instance of this plugin.
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ /** @var \Drupal\gnusocial\GnusocialService $gnusocial_service */
+ $gnusocial_service = $container->get('gnusocial.manager');
+
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $gnusocial_service
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getInfo() {
+ return array(
+ '#title' => '',
+ '#url' => '',
+ '#identifier' => '',
+ '#callbacks' => '',
+ '#theme' => 'gnusocial_status',
+ '#attributes' => ['id' => 'gnusocial_conversation'],
+ '#pre_render' => [
+ [get_class($this), 'preRenderGnusocialStatus'],
+ ],
+ );
+ }
+
+ /**
+ * Prepare the render array for the template.
+ */
+ public static function preRenderGnusocialStatus(array $element) {
+
+ // Create a statusnet_html render array using #statusnet_html.
+ $element['statusnet_html'] = [
+ '#markup' => $element['#statusnet_html'],
+ ];
+ // Create a date render array using #created_at.
+ $element['created_at'] = [
+ '#markup' => $element['#created_at'],
+ ];
+ // Avatar.
+ $element['user_profile_image_url'] = [
+ '#markup' => $element['#user_profile_image_url'],
+ ];
+ // User url.
+ $element['user_url'] = [
+ '#markup' => $element['#user_url'],
+ ];
+ // User statusnet profile url.
+ $element['user_statusnet_profile_url'] = [
+ '#type' => 'link',
+ '#title' => $element['#user_screen_name'],
+ '#url' => Url::fromUri($element['#user_statusnet_profile_url']),
+ ];
+ // Screen name - wrapped with url.
+ $element['username'] = [
+ '#type' => 'link',
+ '#title' => $element['#user_screen_name'],
+ '#url' => Url::fromUri($element['#user_url']),
+ ];
+
+ $username = drupal_render($element['user_statusnet_profile_url']);
+ $created_at = drupal_render($element['created_at']);
+
+ // Submitted note as drupal does.
+ $element['submitted'] = [
+ '#markup' => t('Submitted by @username on @datetime', array('@username' => $username, '@datetime' => $created_at)),
+ ];
+
+ // Permalink.
+ $element['permalink'] = [
+ '#type' => 'link',
+ '#title' => 'permalink',
+ '#url' => Url::fromRoute('<current>', [], ["fragment" => "gs-comment-" . $element['#id']]),
+ ];
+
+ return $element;
+ }
+
+}
diff --git a/src/Form/GnusocialSettingsForm.php b/src/Form/GnusocialSettingsForm.php
new file mode 100644
index 0000000..631aa74
--- /dev/null
+++ b/src/Form/GnusocialSettingsForm.php
@@ -0,0 +1,229 @@
+<?php
+
+namespace Drupal\gnusocial\Form;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Form\ConfigFormBase;
+use Drupal\gnusocial\GnusocialService;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+
+/**
+ * Configure gnusocial settings for this site.
+ */
+class GnusocialSettingsForm extends ConfigFormBase {
+
+ /**
+ * Guzzle Http Client Factory.
+ *
+ * @var \Drupal\gnusocial\GnusocialService
+ */
+ protected $gnusocialService;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct(
+ GnusocialService $gnusocial_service
+ ) {
+ $this->gnusocialService = $gnusocial_service;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('gnusocial.manager')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFormId() {
+ return 'gnusocial_settings_form';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getEditableConfigNames() {
+ return ['gnusocial.settings'];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildForm(array $form, FormStateInterface $form_state) {
+ $config = $this->config('gnusocial.settings');
+
+ /* third leg of oauth process */
+ $this->fillWithPermanentTokens();
+
+ $form['gnusocial_url'] = array(
+ '#type' => 'url',
+ '#title' => t('GnuSocial url'),
+ '#description' => $this->t('The gnusocial url ( https://example.gnusocial.com ).'),
+ '#size' => 30,
+ '#default_value' => $config->get('gnusocial_url'),
+ );
+
+ $form['settings'] = [
+ '#type' => 'vertical_tabs',
+ '#weight' => 50,
+ ];
+
+ // Behavior settings.
+ $form['authentication'] = [
+ '#type' => 'details',
+ '#title' => $this->t('Authentication'),
+ '#group' => 'settings',
+ ];
+
+ $form['authentication']['gnusocial_consumer_key'] = [
+ '#type' => 'textfield',
+ '#title' => $this->t('OAuth consumer key'),
+ '#default_value' => $config->get('authentication.gnusocial_consumer_key'),
+ ];
+ $form['authentication']['gnusocial_consumer_secret'] = [
+ '#type' => 'textfield',
+ '#title' => $this->t('OAuth consumer secret'),
+ '#default_value' => $config->get('authentication.gnusocial_consumer_secret'),
+ ];
+ $form['authentication']['gnusocial_token_tmp'] = [
+ '#type' => 'hidden',
+ '#title' => $this->t('OAuth temporal token'),
+ '#default_value' => $config->get('authentication.gnusocial_token_tmp'),
+ '#disabled' => TRUE,
+ ];
+ $form['authentication']['gnusocial_token_secret_tmp'] = [
+ '#type' => 'hidden',
+ '#title' => $this->t('Oauth temporal secret token'),
+ '#default_value' => $config->get('authentication.gnusocial_token_secret_tmp'),
+ '#disabled' => TRUE,
+ ];
+ $form['authentication']['gnusocial_token'] = [
+ '#type' => 'textfield',
+ '#title' => $this->t('OAuth token'),
+ '#default_value' => $config->get('authentication.gnusocial_token'),
+ '#disabled' => TRUE,
+ ];
+ $form['authentication']['gnusocial_token_secret'] = [
+ '#type' => 'textfield',
+ '#title' => $this->t('Oauth secret token'),
+ '#default_value' => $config->get('authentication.gnusocial_token_secret'),
+ '#disabled' => TRUE,
+ ];
+ $form['authentication']['avatar'] = [
+ '#type' => 'item',
+ '#markup' => (NULL !== $config->get('authentication.gnusocial_token') ? $this->getAvatar() : $this->t('No Oauth process has been done')) ,
+ '#title' => $this->t("The user that will be used to login to GnuSocial"),
+ ];
+
+ $form['actions']['#type'] = 'actions';
+ $form['actions']['update_tokens'] = [
+ '#type' => 'submit',
+ '#value' => $this->t('Update tokens'),
+ '#submit' => array('::updateTokens'),
+ '#disabled' => $config->get('authentication.gnusocial_consumer_secret') == "" && $config->get('authentication.gnusocial_consumer_key') == "",
+ ];
+ $form['actions']['verify_credentials'] = [
+ '#type' => 'submit',
+ '#value' => $this->t('Verify credentials'),
+ '#submit' => array('::verifyCredentials'),
+ '#disabled' => $config->get('authentication.gnusocial_token') == "" && $config->get('authentication.gnusocial_token_secret') == "",
+ ];
+
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => $this->t('Save configuration'),
+ '#button_type' => 'primary',
+ );
+ return parent::buildForm($form, $form_state);
+ }
+
+ /**
+ * Submit callback of update tokens button.
+ */
+ public function updateTokens(array &$form, FormStateInterface $form_state) {
+ // Handle submitted values in $form_state here.
+ $config = $this->config('gnusocial.settings');
+ $consumer_key = $form_state->getValue('gnusocial_consumer_key');
+ $consumer_secret = $form_state->getValue('gnusocial_consumer_key');
+
+ $tokens = $this->gnusocialService->fetchTokens();
+
+ if (array_key_exists('oauth_token', $tokens) && array_key_exists('oauth_token_secret', $tokens)) {
+ $config
+ ->set('authentication.gnusocial_token_tmp', $tokens['oauth_token'])
+ ->set('authentication.gnusocial_token_secret_tmp', $tokens['oauth_token_secret'])
+ ->save();
+
+ // Oauth 1st leg: Temporal authorization succesful from gnusocial instance
+ // at @url .', array('@url' => $form_state->getValue('gnusocial_url'))));
+ // drupal_set_message($this->t("Oauth 2nd leg: To carry on, go to @url.",
+ // array('@url' =>$form_state->getValue('gnusocial_url').
+ // "/api/oauth/authorize?oauth_token=".$tokens['oauth_token']) ));.
+ $goto_auth = new RedirectResponse($form_state->getValue('gnusocial_url') . "/api/oauth/authorize?oauth_token=" . $tokens['oauth_token']);
+ $goto_auth->send();
+ }
+ }
+
+ /**
+ * Submit callback of update tokens button.
+ */
+ public function verifyCredentials(array &$form, FormStateInterface $form_state) {
+
+ $this->gnusocialService->getData("/api/account/verify_credentials.json");
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ $config = $this->config('gnusocial.settings');
+
+ $config
+ ->set('gnusocial_url', $form_state->getValue('gnusocial_url'))
+ ->set('authentication.gnusocial_consumer_key', $form_state->getValue('gnusocial_consumer_key'))
+ ->set('authentication.gnusocial_consumer_secret', $form_state->getValue('gnusocial_consumer_secret'))
+ ->save();
+ parent::submitForm($form, $form_state);
+ }
+
+ /**
+ * Fills token items with permanent (Executes the 3rd leg of Oauth)
+ */
+ protected function fillWithPermanentTokens() {
+ $config = $this->config('gnusocial.settings');
+ if (isset($_GET['oauth_token']) && isset($_GET['oauth_verifier'])) {
+
+ if ($_GET['oauth_token'] == $config->get('authentication.gnusocial_token_tmp') && $_GET['oauth_verifier']) {
+ drupal_set_message($this->t("3rd leg of OAUTH coming back as callback from gnu social authorization giving us permanent tokens."));
+ $tokens = $this->gnusocialService->fetchTokens($_GET['oauth_verifier']);
+ if (array_key_exists('oauth_token', $tokens) && array_key_exists('oauth_token_secret', $tokens)) {
+ $config
+ ->set('authentication.gnusocial_token', $tokens['oauth_token'])
+ ->set('authentication.gnusocial_token_secret', $tokens['oauth_token_secret'])
+ ->save();
+ drupal_set_message($this->t('Permanent authorization succesful from gnusocial instance at @url .', array('@url' => $config->get('gnusocial_url'))));
+
+ $this->gnusocialService->getData("/api/account/verify_credentials.json");
+
+ }
+
+ }
+ }
+ }
+
+ /**
+ * Gets info about the user that will be used to comment and fetch notices.
+ */
+ protected function getAvatar() {
+ $avatar = $this->gnusocialService->getData("/api/account/verify_credentials.json");
+ $avatar_markup = "<a href='" . $avatar->statusnet_profile_url . "'><img src='" . $avatar->profile_image_url . "'>" . $avatar->screen_name . "</a>";
+ return $avatar_markup;
+ }
+
+}
diff --git a/src/GnusocialService.php b/src/GnusocialService.php
new file mode 100644
index 0000000..cb36735
--- /dev/null
+++ b/src/GnusocialService.php
@@ -0,0 +1,234 @@
+<?php
+namespace Drupal\gnusocial;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Http\ClientFactory;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Entity\EntityFieldManagerInterface;
+use Drupal\Core\Url;
+use GuzzleHttp\HandlerStack;
+use GuzzleHttp\Subscriber\Oauth\Oauth1;
+
+/**
+ * Processes the communications with gnusocial.
+ */
+class GnusocialService {
+ /**
+ * Guzzle Http Client Factory.
+ *
+ * @var GuzzleHttp\ClientFactory
+ */
+ protected $httpClientFactory;
+
+ /**
+ * The gnusocial settings config object.
+ *
+ * @var \Drupal\Core\Config\ConfigFactoryInterface
+ */
+ protected $configFactory;
+
+
+ /**
+ * The entity type manager service.
+ *
+ * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+ */
+ protected $entityTypeManager;
+
+ /**
+ * The entity field manager service.
+ *
+ * @var \Drupal\Core\Entity\EntityFieldManagerInterface
+ */
+ protected $entityFieldManager;
+
+ /**
+ * Constructs the GnusocialService object.
+ *
+ * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+ * The entity type manager service.
+ * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
+ * The entity field manager service.
+ * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+ * The config factory service.
+ * @param \Drupal\Core\Http\ClientFactory $http_client_factory
+ * The Http client factory service.
+ */
+ public function __construct(
+ EntityTypeManagerInterface $entity_type_manager,
+ EntityFieldManagerInterface $entity_field_manager,
+ ConfigFactoryInterface $config_factory,
+ ClientFactory $http_client_factory
+ ) {
+ $this->entityTypeManager = $entity_type_manager;
+ $this->entityFieldManager = $entity_field_manager;
+ $this->httpClientFactory = $http_client_factory;
+ $this->configFactory = $config_factory;
+ }
+
+ /**
+ * Return the tokens from consumer key and secret.
+ *
+ * If $authVerifier is nonexistant means that it is the first leg:
+ * 1st leg: [oauth/request_token] Get temp tokens from oauth/request_token
+ * from consumer keys:
+ * -we can detect this leg because no $authverifier parameter is
+ * provided.
+ * -link to authorize is provided.
+ * (https://url/api/oauth/authorize?oauth_token=temptokenxxxxx)
+ * 2nd leg: [oauth/authorize] Login in gnusocial using previous link and
+ * accepting the authorization request.
+ * -Once accepted we will get back to callback url (Drupal site)
+ * to continue to 3rd leg with authverifier.
+ *
+ * 3rd leg: [oauth/access_token] Getting manually authverifier from previous
+ * url, it gets permanent tokens giving temp token and the authverifier.
+ * -we can detect this leg because authverifier parameter is
+ * provided.
+ *
+ * @param bool|string $authVerifier
+ * The verifier token or false if empty.
+ */
+ public function fetchTokens($authVerifier = FALSE) {
+ $config = $this->configFactory->get('gnusocial.settings');
+
+ $stack = HandlerStack::create();
+ $middleware = new Oauth1([
+ 'consumer_key' => $config->get('authentication.gnusocial_consumer_key'),
+ 'consumer_secret' => $config->get('authentication.gnusocial_consumer_secret'),
+ // FALSE Means that 2-legged OAuth is needed.
+ 'token_secret' => ($authVerifier) ? $config->get('authentication.gnusocial_token_secret_tmp') : FALSE ,
+ 'token' => ($authVerifier) ? $config->get('authentication.gnusocial_token_tmp') : NULL ,
+ 'signature_method' => Oauth1::SIGNATURE_METHOD_HMAC,
+ ]);
+ $stack->push($middleware);
+ /** @var Client $client */
+ $client = $this->httpClientFactory->fromOptions([
+ 'base_uri' => $config->get('gnusocial_url') . '/api/',
+ 'handler' => $stack,
+ 'auth' => 'oauth',
+ // 'debug' => TRUE,.
+ ]);
+ $callback = Url::fromRoute('gnusocial.settings', [], array('absolute' => TRUE));
+ /** @var Response $res */
+ $res = ($authVerifier) ? $client->post('oauth/access_token', ['form_params' => ['oauth_verifier' => $authVerifier]]) : $client->post('oauth/request_token', ['form_params' => ['oauth_callback' => $callback->toString()]]);
+ $params = (string) $res->getBody();
+ $tokens = array();
+ parse_str($params, $tokens);
+ return $tokens;
+ }
+
+ /**
+ * Get Data from GnuSocial api endpoint in json.
+ *
+ * @param string $resource
+ * The resource to get.
+ */
+ public function getData($resource) {
+ $config = $this->configFactory->get('gnusocial.settings');
+
+ $stack = HandlerStack::create();
+ $middleware = new Oauth1([
+ 'consumer_key' => $config->get('authentication.gnusocial_consumer_key'),
+ 'consumer_secret' => $config->get('authentication.gnusocial_consumer_secret'),
+ 'token_secret' => $config->get('authentication.gnusocial_token_secret') ,
+ 'token' => $config->get('authentication.gnusocial_token') ,
+ 'signature_method' => Oauth1::SIGNATURE_METHOD_HMAC,
+ ]);
+ $stack->push($middleware);
+
+ /** @var bool $is_external */
+ /*If url is external and so not configured via settings. */
+ $is_external = (substr($resource, 0, 7) === "http://" or substr($resource, 0, 8) === "https://");
+ /** @var string $base_uri */
+ /*If different uri has been configured use that. */
+ $base_uri = ($is_external) ? explode("/api/", $resource)[0] : $config->get('gnusocial_url');
+ /** @var string $resource */
+ /*If different uri has been configured use that. */
+ $resource = ($is_external) ? "/api/" . explode("/api/", $resource)[1] : $resource;
+ /** @var Client $client */
+ $client = $this->httpClientFactory->fromOptions([
+ 'base_uri' => $base_uri,
+ 'handler' => ($is_external) ? NULL : $stack,
+ 'auth' => ($is_external) ? FALSE : 'oauth',
+ // 'debug' => TRUE,.
+ ]);
+
+ /** @var Response $data */
+ $data = $client->get($resource);
+ return json_decode($data->getBody());
+ }
+
+ /**
+ * Get more link from resource.
+ *
+ * @param string $resource
+ * The resource to get. The same as getData but without auth.
+ */
+ public function getMoreLink($resource) {
+ $config = $this->configFactory->get('gnusocial.settings');
+ $base_uri = (substr($resource, 0, 7) === "http://" or substr($resource, 0, 8) === "https://") ? explode("/api/", $resource)[0] . "/api/" : $config->get('gnusocial_url');
+ return $base_uri . $resource;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFields($entity_type_id) {
+ $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
+ if (!$entity_type->isSubclassOf('\Drupal\Core\Entity\ContentEntityInterface')) {
+ return [];
+ }
+
+ $map = $this->getAllFields();
+ return isset($map[$entity_type_id]) ? $map[$entity_type_id] : [];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getAllFields() {
+ $map = $this->entityFieldManager->getFieldMap();
+ // Build a list of gnusocial comment fields only.
+ $gnusocial_comment_fields = [];
+ foreach ($map as $entity_type => $data) {
+ foreach ($data as $field_name => $field_info) {
+ if ($field_info['type'] == 'gnusocial_comments') {
+ $gnusocial_comment_fields[$entity_type][$field_name] = $field_info;
+ }
+ }
+ }
+ return $gnusocial_comment_fields;
+ }
+
+ /**
+ * Publishes a notice in gnusocial.
+ */
+ public function postNotice($status) {
+ $config = $this->configFactory->get('gnusocial.settings');
+
+ $stack = HandlerStack::create();
+ $middleware = new Oauth1([
+ 'consumer_key' => $config->get('authentication.gnusocial_consumer_key'),
+ 'consumer_secret' => $config->get('authentication.gnusocial_consumer_secret'),
+ 'token_secret' => $config->get('authentication.gnusocial_token_secret') ,
+ 'token' => $config->get('authentication.gnusocial_token') ,
+ 'signature_method' => Oauth1::SIGNATURE_METHOD_HMAC,
+ ]);
+ $stack->push($middleware);
+ /** @var Client $client */
+ $client = $this->httpClientFactory->fromOptions([
+ 'base_uri' => $config->get('gnusocial_url'),
+ 'handler' => $stack,
+ 'auth' => 'oauth',
+ 'debug' => TRUE,
+ ]);
+
+ /** @var Response $res */
+ $res = $client->post('/api/statuses/update.json', ['form_params' => ['status' => $status]]);
+ $params = json_decode((string) $res->getBody());
+ $answer = array($params);
+ return $answer;
+ }
+
+}
diff --git a/src/Plugin/Field/FieldFormatter/GnusocialDefaultFormatter.php b/src/Plugin/Field/FieldFormatter/GnusocialDefaultFormatter.php
new file mode 100644
index 0000000..7e3d852
--- /dev/null
+++ b/src/Plugin/Field/FieldFormatter/GnusocialDefaultFormatter.php
@@ -0,0 +1,168 @@
+<?php
+namespace Drupal\gnusocial\Plugin\Field\FieldFormatter;
+
+use Drupal\Core\Field\FormatterBase;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Url;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\gnusocial\GnusocialService;
+
+/**
+ * Provides a default gnusocial comment formatter.
+ *
+ * @FieldFormatter(
+ * id = "gnusocial_comments",
+ * label = @Translation("GnuSocial comments"),
+ * field_types = {
+ * "gnusocial_comments"
+ * }
+ * )
+ */
+class GnusocialDefaultFormatter extends FormatterBase implements ContainerFactoryPluginInterface {
+
+ /**
+ * The current user.
+ *
+ * @var \Drupal\Core\Session\AccountInterface
+ */
+ protected $currentUser;
+
+ /**
+ * The gnusocial service.
+ *
+ * @var \Drupal\gnusocial\GnusocialService
+ */
+ protected $gnusocialService;
+
+ /**
+ * The gnusocial settings config object.
+ *
+ * @var \Drupal\Core\Config\ConfigFactoryInterface
+ */
+ protected $configFactory;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, AccountInterface $current_user, GnusocialService $gnusocial_service, ConfigFactoryInterface $config_factory) {
+ parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, array());
+ $this->currentUser = $current_user;
+
+ /** @var \Drupal\gnusocial\GnusocialService $gnusocial_service */
+ $this->gnusocialService = $gnusocial_service;
+ /** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */
+ $this->configFactory = $config_factory;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $plugin_id,
+ $plugin_definition,
+ $configuration['field_definition'],
+ $configuration['settings'],
+ $configuration['label'],
+ $configuration['view_mode'],
+ $container->get('current_user'),
+ $container->get('gnusocial.manager'),
+ $container->get('config.factory')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function viewElements(FieldItemListInterface $items, $langcode) {
+ $elements = [];
+
+ // As the Field API only applies the "field default value" to newly created
+ // entities, we'll apply the default value for existing entities.
+ if ($items->count() == 0) {
+ $field_default_value = $items->getFieldDefinition()->getDefaultValue($items->getEntity());
+ $items->status = $field_default_value[0]['status'];
+ }
+ foreach ($items as $delta => $item) {
+ $elements[$delta] = array($this->viewItem($item));
+ }
+ /*
+ // TODO: ADD LOGIN and remote comment form
+ $container = [
+ '#type' => 'container',
+ '#children' => $elements
+ ];
+ */
+
+ return $elements;
+ }
+
+ /**
+ * View single gnusocial conversation .
+ *
+ * @var \Drupal\gnusocial\Plugin\Field\FieldType\GnusocialItem $item
+ */
+ public function viewItem($item) {
+ $element = [];
+ if ($item->status == 1 && $this->currentUser->hasPermission('view gnusocial comments')) {
+ $config = $this->configFactory->get('gnusocial.settings');
+
+ $stream = $this->gnusocialService->getData($item->conversation_id);
+ if (is_null($stream)) {
+ $element = ['#markup' => "No comments yet"];
+ }
+ else {
+ $statuses = [
+ // '#title' => $this->t("GnuSocial comments"),.
+ '#theme' => 'item_list',
+ '#attributes' => ['class' => ['gnusocial-conversation-wrapper']],
+ ];
+ foreach ($stream as $status) {
+ $statuses['#items'][] = [
+ '#type' => 'gnusocial_status',
+ '#url' => $status->external_url,
+ '#statusnet_html' => $status->statusnet_html,
+ '#created_at' => $status->created_at,
+ '#user_profile_image_url' => $status->user->profile_image_url,
+ '#user_statusnet_profile_url' => $status->user->statusnet_profile_url,
+ '#user_url' => (isset($status->user->url)) ? $status->user->url : $status->user->statusnet_profile_url,
+ '#user_screen_name' => $status->user->screen_name,
+ '#id' => $status->id,
+ ];
+ }
+
+ // Reply info.
+ $notice_url = Url::fromUri($stream[0]->external_url);
+
+ $join_link = [
+ '#type' => 'link',
+ '#url' => $notice_url,
+ '#title' => t('Join conversation in gnusocial'),
+ '#attributes' => [
+ 'target' => "_blank",
+ 'class' => "gnusocial-conversation-link",
+ ],
+ ];
+ // More link.
+ $conversation_json = [
+ '#type' => 'more_link',
+ '#title' => t('json conversation'),
+ '#attributes' => ['class' => "gnusocial-conversation-json-link"],
+ '#url' => Url::fromUri($this->gnusocialService->getMoreLink($item->conversation_id)),
+ ];
+ $element['conversation'][] = [
+ '#type' => 'gnusocial_conversation',
+ '#statuses' => $statuses,
+ '#conversation_url' => $join_link,
+ '#conversation_json' => $conversation_json,
+ ];
+ }
+ }
+ return $element;
+ }
+
+}
diff --git a/src/Plugin/Field/FieldFormatter/GnusocialJSFormatter.php b/src/Plugin/Field/FieldFormatter/GnusocialJSFormatter.php
new file mode 100644
index 0000000..3e110bd
--- /dev/null
+++ b/src/Plugin/Field/FieldFormatter/GnusocialJSFormatter.php
@@ -0,0 +1,79 @@
+<?php
+namespace Drupal\gnusocial\Plugin\Field\FieldFormatter;
+
+use Drupal\Core\Field\FormatterBase;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * The default gnusocial_comments formatter obtained by the client via JS.TODO.
+ *
+ * @FieldFormatter(
+ * id = "gnusocial_js_comments",
+ * label = @Translation("[TODO]GnuSocial js comments"),
+ * field_types = {
+ * "gnusocial_comments"
+ * }
+ * )
+ */
+class GnusocialJSFormatter extends FormatterBase implements ContainerFactoryPluginInterface {
+
+ /**
+ * The current user.
+ *
+ * @var \Drupal\Core\Session\AccountInterface
+ */
+ protected $currentUser;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, AccountInterface $current_user) {
+ parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, array());
+ $this->currentUser = $current_user;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $plugin_id,
+ $plugin_definition,
+ $configuration['field_definition'],
+ $configuration['settings'],
+ $configuration['label'],
+ $configuration['view_mode'],
+ $container->get('current_user')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function viewElements(FieldItemListInterface $items, $langcode) {
+ $element = [];
+
+ // As the Field API only applies the "field default value" to newly created
+ // entities, we'll apply the default value for existing entities.
+ if ($items->count() == 0) {
+ $field_default_value = $items->getFieldDefinition()->getDefaultValue($items->getEntity());
+ $items->status = $field_default_value[0]['status'];
+ }
+
+ if ($items->status == 1 && $this->currentUser->hasPermission('view gnusocial comments')) {
+ $element[] = [
+ '#type' => 'gnusocial_js',
+ '#url' => $items->getEntity()->toUrl('canonical', ['absolute' => TRUE])->toString(),
+ '#title' => (string) $items->getEntity()->label(),
+ '#identifier' => $items->identifier ?: "{$items->getEntity()->getEntityTypeId()}/{$items->getEntity()->id()}",
+ ];
+ }
+
+ return $element;
+ }
+
+}
diff --git a/src/Plugin/Field/FieldType/GnusocialItem.php b/src/Plugin/Field/FieldType/GnusocialItem.php
new file mode 100644
index 0000000..8e01f1e
--- /dev/null
+++ b/src/Plugin/Field/FieldType/GnusocialItem.php
@@ -0,0 +1,70 @@
+<?php
+namespace Drupal\gnusocial\Plugin\Field\FieldType;
+
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\Field\FieldItemBase;
+use Drupal\Core\Field\FieldItemInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\Core\TypedData\DataDefinition;
+
+/**
+ * Plugin implementation of the 'gnusocial_comments' field type.
+ *
+ * @FieldType(
+ * id = "gnusocial_comments",
+ * label = @Translation("GnuSocial comments"),
+ * description = @Translation("GnuSocial comments widget"),
+ * default_widget = "gnusocial_comments",
+ * default_formatter = "gnusocial_comments"
+ * )
+ */
+class GnusocialItem extends FieldItemBase implements FieldItemInterface {
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function schema(FieldStorageDefinitionInterface $field_definition) {
+ return [
+ 'columns' => [
+ 'status' => [
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 1,
+ ],
+ 'conversation_id' => [
+ 'type' => 'varchar',
+ 'default' => '',
+ 'length' => 255,
+
+ ],
+ 'post_gnusocial' => [
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 1,
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
+ $properties['status'] = DataDefinition::create('integer')
+ ->setLabel(new TranslatableMarkup('GnuSocial status value'));
+ $properties['conversation_id'] = DataDefinition::create('string')
+ ->setLabel(new TranslatableMarkup('GnuSocial conversation identifier'));
+ $properties['post_gnusocial'] = DataDefinition::create('integer')
+ ->setLabel(new TranslatableMarkup('Post to GnuSocial'));
+
+ return $properties;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isEmpty() {
+ return $this->get('status')->getValue() === '';
+ }
+
+}
diff --git a/src/Plugin/Field/FieldWidget/GnusocialDefaultWidget.php b/src/Plugin/Field/FieldWidget/GnusocialDefaultWidget.php
new file mode 100644
index 0000000..485c1f3
--- /dev/null
+++ b/src/Plugin/Field/FieldWidget/GnusocialDefaultWidget.php
@@ -0,0 +1,104 @@
+<?php
+namespace Drupal\gnusocial\Plugin\Field\FieldWidget;
+
+use Drupal\Core\Field\WidgetBase;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Plugin implementation of the Disqus comments widget.
+ *
+ * @FieldWidget(
+ * id = "gnusocial_comments",
+ * label = @Translation("Default"),
+ * field_types = {
+ * "gnusocial_comments"
+ * }
+ * )
+ */
+class GnusocialDefaultWidget extends WidgetBase implements ContainerFactoryPluginInterface {
+
+ /**
+ * The current user.
+ *
+ * @var \Drupal\Core\Session\AccountInterface
+ */
+ protected $currentUser;
+
+ /**
+ * The gnusocial settings config object.
+ *
+ * @var \Drupal\Core\Config\ConfigFactoryInterface
+ */
+ protected $configFactory;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, AccountInterface $current_user, ConfigFactoryInterface $configFactory) {
+ parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, array());
+ $this->currentUser = $current_user;
+ $this->configFactory = $configFactory;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $plugin_id,
+ $plugin_definition,
+ $configuration['field_definition'],
+ $configuration['settings'],
+ $container->get('current_user'),
+ $container->get('config.factory')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
+ $config = $this->configFactory->get('gnusocial.settings');
+ $element['status'] = [
+ '#type' => 'checkbox',
+ '#title' => t('Gnusocial Comments'),
+ '#description' => t('Users can post comments using <a href=":gnusocial">GnuSocial</a>.', [':gnusocial' => $config->get('gnusocial_url')]),
+ '#default_value' => isset($items->status) ? $items->status : TRUE,
+ '#access' => $this->currentUser->hasPermission('toggle gnusocial comments'),
+ ];
+ $element['post_gnusocial'] = [
+ '#type' => 'checkbox',
+ '#title' => t('Post to GnuSocial'),
+ '#description' => t('Post to configured <a href=":gnusocial">GnuSocial</a> and automatically obtain the conversation identifier.', [':gnusocial' => $config->get('gnusocial_url')]),
+ // DO NOT mark by default when is set (as it means updating)
+ '#default_value' => isset($items->post_gnusocial) ? FALSE : TRUE,
+ '#access' => $this->currentUser->hasPermission('post to gnusocial'),
+ ];
+ $element['conversation_id'] = [
+ '#type' => 'textfield',
+ '#title' => $this->t('GnuSocial conversation identifier'),
+ '#description' => $this->t("Unique identifier of the GnuSocial conversation thread. It's pulled from gnusocial automatically. You can use both local and remote urls. <br/>For gnusocial configured in settings example: <i>/api/statusnet/conversation/<b>first_status_id</b>.json</i>. <br/>Remote url example: <i>https://gnusocial.org/api/statusnet/conversation/<b>first_status_id</b>.json</i>. <br/> Changing this might cause comments to disappear. Use extreme caution!"),
+ '#default_value' => isset($items->conversation_id) ? $items->conversation_id : '',
+ '#access' => $this->currentUser->hasPermission('administer gnusocial'),
+ ];
+
+ // If the advanced settings tabs-set is available (normally rendered in the
+ // second column on wide-resolutions), place the field as a details element
+ // in this tab-set.
+ if (isset($form['advanced'])) {
+ $element += array(
+ '#type' => 'details',
+ '#group' => 'advanced',
+ );
+ }
+
+ return $element;
+ }
+
+}
diff --git a/templates/gnusocial-conversation.html.twig b/templates/gnusocial-conversation.html.twig
new file mode 100644
index 0000000..881a561
--- /dev/null
+++ b/templates/gnusocial-conversation.html.twig
@@ -0,0 +1,29 @@
+{#
+/**
+ * @file
+ * Display GnuSocial status template.
+ *
+ * Available variables:
+ * - element.conversation_url: The url to first notice in conversation.
+ * - element.conversation_json: The more link to json.
+ * - element.statuses: The statuses list
+ * @see template_preprocess_gnusocial_conversation()
+ *
+ * @ingroup themeable
+ *
+ */
+
+#}
+{%
+ set conversation_classes = [
+ 'gnusocial-conversation'
+ ]
+%}
+<div {{ attributes.addClass( conversation_classes ) }}">
+{{ element.statuses }}
+</div>
+<div>
+{{ element.conversation_url }}
+{{ element.conversation_json }}
+</div>
+
diff --git a/templates/gnusocial-noscript.html.twig b/templates/gnusocial-noscript.html.twig
new file mode 100644
index 0000000..3fc34e4
--- /dev/null
+++ b/templates/gnusocial-noscript.html.twig
@@ -0,0 +1,15 @@
+{#
+/**
+* @file
+* Display Suite reset template.
+*
+* Available variables:
+* - disqus_message: The linked message to view the comments in Disqus site
+*/
+#}
+ <a href="{{ url }}">{{ message }}</a>
+<noscript>
+ <p>
+ <a href="{{ url }}">{{ message }}</a>
+ </p>
+</noscript>
diff --git a/templates/gnusocial-status.html.twig b/templates/gnusocial-status.html.twig
new file mode 100644
index 0000000..7847e13
--- /dev/null
+++ b/templates/gnusocial-status.html.twig
@@ -0,0 +1,55 @@
+{#
+/**
+ * @file
+ * Display GnuSocial status template.
+ *
+ * Available variables:
+ * - element.statusnet_html: The markup of status
+ * - element.created_at: The creation date of status
+ * - element.user_profile_image_url: The profile image of user.
+ * - element.pre_render_addition : To add some additional text.
+ * - element.user_url: Raw user url(not profile url).
+ * - element.user_statusnet_profile_url: User screen name pointing to profile url.
+ * - element.username: The screen name of user pointing to user url (not profile url).
+ * - element.submitted: Submitted text as drupal does
+ * - element.permalink: The link to status in drupal.
+ * @see template_preprocess_comment()
+ *
+ * @ingroup themeable
+ *
+ */
+
+#}
+{%
+ set classes = [
+ 'comment',
+ 'js-comment',
+ 'clearfix',
+ ]
+%}
+
+<article role="article" {{ attributes.addClass(classes)|without('role') }}>
+ <footer class="comment__meta">
+ {% spaceless %}
+ <a class="group" target="_blank" rel="group1" href="{{ element['#user_statusnet_profile_url'] }}">
+ <img src="{{ element.user_profile_image_url }}">
+ </a>
+ {% endspaceless %}
+ <p class="comment__author">{{ element.username }}</p>
+ <p class="comment__time">{{ element.created_at }}</p>
+ <p class="comment__permalink">{{ element.permalink }}</p>
+ {#
+ Indicate the semantic relationship between parent and child comments for
+ accessibility. The list is difficult to navigate in a screen reader
+ without this information.
+ {% if parent %}
+ <p class="visually-hidden">{{ parent }}</p>
+ {% endif %}
+ #}
+
+ </footer>
+
+ <div{{ content_attributes.addClass('comment__content') }}>
+ {{ element.statusnet_html }}
+ </div>
+</article>