Using MySite with external applications and data

Events happening in the community are now at Drupal community events on www.drupal.org.
agentrickard's picture

A few technical notes for developers concerning how MySite interacts with non-Drupal data and application sources.

First, a little history. The core goal of the MySite module is to abstract content published to a Drupal site. So the MySie distribution supports content types native to Drupal. The reason for this is scope: I don't have time to solve every problem, so I focus on those.

That said, the internal MySite API is written with extensibility in mind. What this means is that the MySite API hooks that define content are not Drupal-dependent. Only the core implementations are.

The prime example is Google Gadgets. MySite can use Google Gadgets through a mechanism called 'Droplets'. A Droplet is similar to a Drupal Block. It can accept HTML, PHP, or JS code. A site admin can create whatever droplets they wish.

For very simple implementations, you might simply paste the PHP or JS for calling an external application straight into a Droplet. No programming required.

In the case of Google Gadgets -- and WidgetBox -- that's what Droplets do: The admin creates a droplet and pastes in the appropriate JS. We do not automatically fetch these mini-apps, but we could.

We don't auto-fetch the data for two reasons: the Google EULA and potential security issues with using untested 3rd-party JavaScript. (See http://drupal.org/node/127661 for the full history). Because of these concerns, I decided to make admins select which Google Gadgets they wanted to use rather than fetching them programatically.

That said, let's look at two functions that form the core of the MySite content API, and examine how they could be used to make calls external to Drupal.

  1. mysite_type_hook_options()

This function defines the content that is available for a certain type plugin. For the blog plugin, for instance, it pulls back a list of all site users who have posted a node of type 'blog.'

But let's look at the code logic:

<?php
function mysite_type_hook_options() {
 
$options = array();
 
$sql = "SELECT m.id, m.title, g.group FROM {mytable} m INNER JOIN {group} g ON m.id = g.id";
 
$result = db_query($sql);
 
$items = array();
  while (
$item = db_fetch_object($result)) {
   
$items[] =$item;
  }
  foreach (
$items as $key => $value) {
   
$options['group'][] = $value->group;
   
$options['name'][] = mysite_type_hook_title($value->id, $value->title);
   
$options['type_id'][] = $value->id;
   
$options['type'][] = 'hook';
   
$options['icon'][] = mysite_get_icon('hook', $value->id);
  }
  return
$options;
}
?>

Now, suppose we changed the data request from a SQL query to an HTTP request:

<?php
function mysite_type_example_options() {
 
$options = array();
 
// Use an http request to pull back XML
 
$data = drupal_http_request('http://example.com/REST');
 
// Process the XML into a format we can read.
 
$items = mysite_type_hook_custom_function($data);
  foreach (
$items as $key => $value) {
   
$options['group'][] = $value->group;
   
$options['name'][] = mysite_type_hook_title($value->id, $value->title);
   
$options['type_id'][] = $value->id;
   
$options['type'][] = 'hook';
   
$options['icon'][] = mysite_get_icon('hook', $value->id);
  }
  return
$options;
}
?>

Bingo, we now have an externally-defined list of options.

To interface with applications that require external authentication, you would probably add an authentication step here, since the options will not be shown to a user if the $options array returns blank.

<?php
function mysite_type_example_options() {
  global
$user;
 
// Authenticate the user against the external service.  If it fails, stop this routine.
 
$check = mysite_type_example_custom_check($user);
  if (!
$check) {
    return array();
  }
 
$options = array();
 
// Use an http request to pull back XML
 
$data = drupal_http_request('http://example.com/REST');
 
// Process the XML into a format we can read.
 
$items = mysite_type_example_custom_function($data);
  foreach (
$items as $key => $value) {
   
$options['group'][] = $value->group;
   
$options['name'][] = mysite_type_hook_title($value->id, $value->title);
   
$options['type_id'][] = $value->id;
   
$options['type'][] = 'hook';
   
$options['icon'][] = mysite_get_icon('hook', $value->id);
  }
  return
$options;
}
?>

Very likely you'd also need to cache the $options returned to reduce network latency.

So this takes care of the options that users are presented for content that can be added to their personal collection.

  1. mysite_type_hook_data()

This function takes a content element and prepares the data needed to present that element on a user's MySite page.

Again, the core implementations assume Drupal data, but that is not required.

<?php
function mysite_type_hook_data($type_id = NULL, $settings = NULL) {
  if (!empty(
$type_id)) {
   
$sql = db_rewrite_sql("SELECT n.nid, n.changed FROM {node} n INNER JOIN {term_node} t ON n.nid = t.nid WHERE t.nid = %d AND n.status = 1 ORDER BY n.changed DESC");
   
$result = db_query_range($sql, $type_id, 0, variable_get('mysite_elements', 5));
   
$data = array(
     
'base' => '',
     
'xml' => ''
      'image'
=> ''
     
);
   
$items = array();
   
$i = 0;
    while (
$nid = db_fetch_object($result)) {
     
$node = node_load($nid->nid);
     
$items[$i]['type'] = $node->type;
     
$items[$i]['link'] = l($node->title, 'node/'. $nid->nid);
     
$items[$i]['title'] = check_plain($node->title);
     
$items[$i]['subtitle'] = NULL;
     
$items[$i]['date'] = $node->changed;
     
$items[$i]['uid'] = $node->uid;
     
$items[$i]['author'] = check_plain($node->name);
     
$items[$i]['teaser'] = mysite_teaser($node);
     
$items[$i]['content'] = NULL;
     
$items[$i]['nid'] = $node->nid;
     
$i++;
    }
   
$data['items'] = $items;
    return
$data;
  }
 
drupal_set_message(t('Could not find type data'), 'error');
  return;
}
?>

You could implement this function to grab external data. The only trick will be to translate that data into an HTML format that can be printed and rendered by Drupal. The key here is the 'content' element of the $items array.

The MySite rule is this: If 'content' is not empty, render it, ignoring the other elements. This rule is what allows MySite to use Google Gadgets and other HTML droplets. See theme_mysite_hook_item() for the code.

The mysite_type_droplet_data() function should be instructive:

<?php
function mysite_type_droplet_data($type_id = NULL, $settings = NULL) {
  if (!empty(
$type_id)) {
   
// get the stored data for the droplet
   
$droplet = mysite_get_custom('droplet', $type_id);
   
// pass $droplet by reference in order to parse the subtype and key  
   
$content = mysite_type_droplet_get_content($droplet);
   
$data = array(
     
'base' => $droplet->base,
     
'xml' => $droplet->xml
     
);
   
$items = array();
   
$i = 0;
   
$items[$i]['type'] = 'droplet';
   
$items[$i]['link'] = NULL;
   
$items[$i]['title'] = check_plain($droplet->title);
   
$items[$i]['content'] = $content;
    
$data['items'] = $items;
    return
$data;
  }
 
drupal_set_message(t('Could not find droplet data'), 'error');
  return;
}
?>

Not let's do something similar with an external data call:

<?php
function mysite_type_example_data($type_id = NULL, $settings = NULL) {
  if (!empty(
$type_id)) {

   
// Fetch the data from the app server
   
$app = drupal_http_request('http://example.com/REST/appid='. $type_id);

   
// Transform the data into a format we understand
   
$element = mysite_type_example_process($app);

   
$data = array(
     
'base' => $element->base,
     
'xml' => $element->xml
     
);
   
$items = array();
   
$i = 0;
   
$items[$i]['type'] = 'example';
   
$items[$i]['link'] = NULL;
   
$items[$i]['title'] = check_plain($element->title);
   
$items[$i]['content'] = $element->content;
   
$data['items'] = $items;
    return
$data;
  }
 
drupal_set_message(t('Could not find example data'), 'error');
  return;
}
?>

In this case, the data printed to the MySite user page would be piped in from the external source.

Note: You can access this data externally by using mysite_render(); and you can receive HTML-formatted data using mysite_display().

Again, to be clear: This is all supported by the existing MySite API.

Comments

A Droplet is similar to a

Dave Cohen's picture

A Droplet is similar to a Drupal Block.

I'm missing what the difference is between a Droplet and a block. That is, when would you recommend using a block, and when a Droplet? Is there something a Droplet can do that a block can't?

Thanks. -D

Droplet v. Block

agentrickard's picture

Not really. This was a design decision, here's the logic.

  • Not all blocks should be droplets. (That is, not all blocks are fit for public viewing.)
  • Creating a droplet should not require creating a block (a two-step process should not be required).
  • We don't want Droplets (which could be numerous) cluttering up the Blocks table and UI.

So existing Blocks can be transformed into Droplets -- so can existing Views. The act of transforming a Block or View into a Droplet means that it can be used by any registered MySite user. Droplets, like all MySite elements, have no concept of access control. (They do, however, respect node access rules.)

As a result, created Droplets are nearly identical to custom blocks. But since Droplets are meant to be accessible to all users, it made sense to separate them from the Block module.

Does that make sense? There is a fairly long issue thread for MySite that led to this decision.

So the short answer is: Droplets are blocks that are designed to be accessed by all MySite users.

--
http://ken.therickards.com/
http://savannahnow.com/user/2
http://blufftontoday.com/user/3

Embedding MySite in Custom User Profile

Muslim guy's picture

AgentTrickard

Could you help how to embed MySite (at least for user's RSS feeds) in user-profile.tpl.php

We tried module_invoke but the mysite is entirely a page in a page, and even the browser and page title becomes - USername : MySite - Sitename.

Guestbook is embedded nicely, for 5.x

<?php
if (module_exists('guestbook')) {
  if(!
$user->guestbook_status){  // guestbook_status seems to store 0 for enabled and 1 for disabled
    
print module_invoke('guestbook', 'page', $user->uid);
  }
}
?>

replacing guestbook with mysite will result in `page in a page' (all the headers, blocks are repeated).

So a workaround is to disable all blocks for mysite*

Another concern is how to embed MySite if we want to use nodeprofile like Michelle's way instead of Dublin Drupaller's user-profile.tpl.php

Thanks in advance and for this wonderful module

*Will add to the entry at http://drupal.org/node/165114#comment-281986

Internet Web 2.0 for Islaam

mysite_page

agentrickard's picture

This came up a long time ago, so I rewrote the mysite_page() function to handle this sort of request.

http://therickards.com/api/function/mysite_page/MySite

If you pass the function like so on a user page, you can re-theme the data:

<?php
  $uid
= arg(1);
 
$mycontent = mysite_page($uid, $pageview = FALSE);
?>

This will return the MySite page content for the user. Notice that mysite_page calls mysite_load(), which is where the page title is rewritten. But look what happens when you pass $pageview = FALSE: http://therickards.com/api/function/mysite_load/MySite

So this should return exactly the data you want, without the elements you don't want.

However, the content will not be themed. It will simply be in an array, formatted according to mysite_type_hook_data().

I haven't tested it, but something like the following should work for your user-profile.tpl.php file, as the mysite_page() function should load the appropriate includes.

<?php
  $uid
= arg(1); // you can derive this however you like; it is the UID of the profile owner.
 
$mysite = mysite_get($uid); // this will load the user's MySite settings, including the layout they use.
 
$mycontent = mysite_page($uid, $pageview = FALSE); // this gets the data.
 
$output = theme('mysite_'. $mysite->layout .'_layout', $mycontent); // this themes the data.
 
print $output;
?>

If you get a white screen, it is likely that certain include files did not load. In that case, we'd need to add some extra code to ensure that all MySite includes loaded.

Check the API documentation for more details. http://therickards.com/api

--
http://ken.therickards.com/
http://savannahnow.com/user/2
http://blufftontoday.com/user/3

Community

Group organizers

Group categories

Community Group Freetags

Group notifications

This group offers an RSS feed. Or subscribe to these personalized, sitewide feeds: