I am using the Services module and views.getView method to distribute content from a central Drupal site to various locations. It is a brilliant solution! Hats off to the maintainers of this fantastic project!

However, while I can transfer entire nodes beautifully, files are an issue. Which leads me on to my "wouldn't it be nice if ....?"

Wouldn't it be nice if someone provided another service: file_service.

file_service would use the Drupal File Interface to send files over xml-rpc. The most basic method would receive a local path and return the file if it exists, call it file.getFile. However, it would also include support for sending files attached to a node, say file.getNodeFiles. Making it work with the core Upload module would be an obvious start (pass a node id and get back an array containing the files attached to the corresponding node), but future support could include CCK FileField and CCK ImageField as well as the Image module.

Any takers?? ;-)

Comments

greg.harvey’s picture

Assigned: Unassigned » greg.harvey
Status: Active » Needs review

Actually, this was way easier than I thought. The code below is for a module called 'file_service.module' which exposes a service that, when passed a node id, passes back an array of base64_encode()'d files:

/**
 * Implementation of hook_service().
 */
function file_service_service() {
  return array(
    array(
      '#method'   => 'file.getFiles',
      '#callback' => 'file_service_get_files',
      '#args'     => array(
        array(
          '#name'         => 'nid',
          '#type'         => 'int',
          '#description'  => t('A node id.'),
        ),
      ),
      '#return'   => 'array',
      '#help'     => t('Returns the files attached to a node.')
    ),
  );
}

 

/**
 * returns an array of base64 encoded files attached to a node
 * 
 * @param $nid
 * integer, node id
 * 
 * @return
 * array of files
 */
function file_service_get_files($nid) {
  $node = node_load($nid);
  if (isset($node->files)) {
    $files = array();
    foreach ($node->files as $file) {
      //rebuild the files array so it only contains files we know we're allowed to list
      if ($file->list) {
        $files[] = $file;
      }
    }
    if (count($files) > 0) {
      $send = array();
      foreach ($files as $file) {
        $file = array_shift($files);
        //this needs cleaning up:
        $filepath = 'c:/path/to/drupal/'.$file->filepath;
        $binaryfile = fopen($filepath, 'rb');
        $send[] = base64_encode(fread($binaryfile, filesize($filepath)));
        fclose($binaryfile);
      }
    }
  }
 
  //don't fail silently if there are no files!
  if (!$send) {
    $send = array();
    $send[] = 'NO FILES';
  }
  
  return $send;
}

There are many additions you could make to this module, but this is all I'll probably do for now. Obviously, you need to base64_decode() the items in the array when you are at the client side. My quick test code on the client looked like this:

$request = xmlrpc_encode_request(
  "file.getFiles", array(
    9
  )
);
 
$context = stream_context_create(
  array(
    'http' => array(
      'method' => "POST",
      'header' => "Content-Type: text/xml",
      'content' => $request
    )
  )
);
 
$file = file_get_contents("http://localhost/services/xmlrpc", false, $context);
 
$response = xmlrpc_decode($file);
if (xmlrpc_is_fault($response)) {
    trigger_error("xmlrpc: $response[faultString] ($response[faultCode])");
} else {
    $newfile = fopen('c:\image.jpg', "wb");
    fwrite($newfile, base64_decode($response[0]));
    fclose($newfile);
    print '<h1>Received</h1><pre>'. htmlspecialchars(print_r($response, true)) .'</pre>';
}
brmassa’s picture

Greg,

it has not a access check. Which means that anyone will be able to get those files!

regards,

massa

greg.harvey’s picture

Good point! I'll build one in. =)

EDIT: anyone with an API key - I have API keys on, so I didn't worry about access too much.

greg.harvey’s picture

New version:

/**
 * Implementation of hook_perm().
 */
function file_service_perm() {
  return array('get binary files');
}

/**
 * Implementation of hook_service().
 */
function file_service_service() {
  return array(
    array(
      '#method'   => 'file.getFiles',
      '#callback' => 'file_service_get_files',
      '#access callback' => 'file_service_get_files_access',
      '#args'     => array(
        array(
          '#name'         => 'nid',
          '#type'         => 'int',
          '#description'  => t('A node id.'),
        ),
      ),
      '#return'   => 'array',
      '#help'     => t('Returns the files attached to a node.')
    ),
  );
}

 

/**
 * returns an array of base64 encoded files attached to a node
 * 
 * @param $nid
 * integer, node id
 * 
 * @return
 * array of files
 */
function file_service_get_files($nid) {
  $node = node_load($nid);
  if (isset($node->files)) {
    $files = array();
    foreach ($node->files as $file) {
      //rebuild the files array so it only contains files we know we're allowed to list
      if ($file->list) {
        $files[] = $file;
      }
    }
    if (count($files) > 0) {
      $send = array();
      foreach ($files as $file) {
        $file = array_shift($files);
        $filepath = variable_get('file_service_fullpath', '/').$file->filepath;
        $binaryfile = fopen($filepath, 'rb');
        $send[$file->fid]['file'] = base64_encode(fread($binaryfile, filesize($filepath)));
        $send[$file->fid]['filename'] = $file->filename;
        $send[$file->fid]['uid'] = $file->uid;
        $send[$file->fid]['filemime'] = $file->filemime;
        $send[$file->fid]['filesize'] = $file->filesize;
        $send[$file->fid]['status'] = $file->status;
        $send[$file->fid]['timestamp'] = $file->timestamp;
        fclose($binaryfile);
      }
    }
  }

  //don't fail silently if there are no files!
  if (!$send) {
    $send = array();
    $send[] = 'NO FILES';
  }
  
  return $send;
}

/**
 * access callback function for the files service
 */
function file_service_get_files_access() {
  return user_access('get binary files');
}

/**
 * admin form for file service
 */
function file_service_admin_form() {
  //define our admin form
  $form = array();
  $form['fullpath'] = array(
    '#title' => t('Path to Drupal installation'),
    '#type' => 'textfield',
    '#required' => 'true',
    '#default_value' => variable_get('file_service_fullpath', '/'),
  );
  $form['submit'] = array(
    '#value' => 'Save settings',
    '#type' => 'submit',
  );
  
  return $form;
}

function file_service_admin_form_submit(&$form, &$form_state) {
  variable_set('file_service_fullpath', $form_state['values']['fullpath']);
  drupal_set_message(t('Settings saved.'));
}
brmassa’s picture

Status: Needs review » Fixed

Greg,

commited.

regards,

massa

greg.harvey’s picture

Thanks. Will post my image service too at some point.

Anonymous’s picture

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for two weeks with no activity.

darrenmothersele’s picture

I'd be interested to see how far you got with the ImageField service as I'm in need of something similar and about to start work on implementing something.

To be more specific I have a node import solution that imports from a specific XML format and I am looking at moving this to using the services module (rather than executing within a bootstrapped drupal environment and using drupal_execute). The XML files reference images that are attached to the created nodes using ImageField.

If I am to move this across to using services I somehow need to augment node.save with support for attaching imagefields.

greg.harvey’s picture

I finished it. Just haven't gotten around to posting it and it's at work (and I'm not!) ... I'll try and remember to post it tomorrow. =)

carlosg2’s picture

Thanks for share your Binary files service!

For the ImageField specifically would be posible to reference to a imagecache preset url?

Regards,

Carlos

greg.harvey’s picture

drupalxykon’s picture

any chance of being backported to drupal 5?

greg.harvey’s picture

Version: 6.x-1.x-dev » 5.x-1.x-dev
Assigned: greg.harvey » Unassigned
Status: Closed (fixed) » Patch (to be ported)

Not by me, but it's a very simple module. Assuming the Services API is the same, there would be little difference. But I don't have any Drupal 5 sites to try it on any more, and I won't be creating one especially. Someone who uses Drupal 5 with services will need to pick this up.

Changed the status for you. =)

marcingy’s picture

Status: Patch (to be ported) » Closed (won't fix)

This will not be ported to D5.