Index: server/pifr_server.test
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/project_issue_file_review/server/pifr_server.test,v
retrieving revision 1.20
diff -u -r1.20 pifr_server.test
--- server/pifr_server.test	8 Feb 2010 21:17:01 -0000	1.20
+++ server/pifr_server.test	23 Feb 2010 01:21:43 -0000
@@ -218,6 +218,9 @@
   }
 }
 
+/**
+ * Run through the various XML-RPC calls and ensure that they function properly.
+ */
 class PIFRServerXMLRPCTestCase extends PIFRServerTestCase {
 
   /**
@@ -432,4 +435,155 @@
     $this->assertFalse($response['next'], 'No more results');
     $this->assertEqual($response['last'], $last, 'Last timestamp is valid');
   }
+
+  /**
+   * Ensure that the pifr.queue() validation routines work properly.
+   */
+  protected function testValidation() {
+    $url = url('', array('absolute' => TRUE)) . 'xmlrpc.php';
+    $batch = array();
+
+    // Ensure that 'bogus' client key is rejected.
+    $response = xmlrpc($url, 'pifr.queue', 'bogus', $batch);
+    $this->assertError($response, PIFR_RESPONSE_INVALID_SERVER, 'Invalid project client, check key and ensure that client is enabled.');
+
+    // Ensure that disabled project client is rejected.
+    $response = xmlrpc($url, 'pifr.queue', $this->clients['project']['client_key'], $batch);
+    $this->assertError($response, PIFR_RESPONSE_INVALID_SERVER, 'Invalid project client, check key and ensure that client is enabled.');
+
+    // Ensure that valid project client is deneied when server is not active.
+    $this->clients['project']['status'] = PIFR_SERVER_CLIENT_STATUS_ENABLED;
+    pifr_server_client_save($this->clients['project']);
+    $response = xmlrpc($url, 'pifr.queue', $this->clients['project']['client_key'], $batch);
+    $this->assertError($response, PIFR_RESPONSE_DENIED, 'Server is not active.');
+
+    // Enable server and ensure that request is not denied.
+    variable_set('pifr_active', TRUE);
+    $response = xmlrpc($url, 'pifr.queue', $this->clients['project']['client_key'], $batch);
+    $this->assertEqual($response['branches'], array(), 'No branches queued');
+    $this->assertEqual($response['files'], array(), 'No branches queued');
+
+    // Begin validation of various parts of the queue batch.
+    $batch = array(
+      'branches' => array(),
+      'files' => array(),
+      'projects' => array(),
+      'rawr' => array(),
+    );
+    $response = xmlrpc($url, 'pifr.queue', $this->clients['project']['client_key'], $batch);
+    $this->assertError($response, PIFR_RESPONSE_DENIED, 'Only keys [branches, files, projects] are allowed.');
+
+    // Check the various project validation.
+    unset($batch['rawr']);
+    $batch['projects'][] = array(
+      'name' => 'Example',
+      'repository_type' => 'rawr',
+      'repository_url' => 'http://example.com/',
+    );
+    $batch['projects'][] = array(
+      'client_identifier' => '17a',
+      'name' => 'Example 2',
+      'repository_type' => 'cvs',
+      'repository_url' => 'http://example.com/2',
+      'rawr' => 'hi',
+    );
+    $response = xmlrpc($url, 'pifr.queue', $this->clients['project']['client_key'], $batch);
+    $this->assertError($response, PIFR_RESPONSE_DENIED, array(
+      'Error(s) found in project #1: required key [client_identifier] not found.',
+      'Error(s) found in project #2: Only keys [client_identifier, name, repository_type, repository_url, link] are allowed.',
+    ));
+
+    unset($batch['projects'][1]['rawr']);
+    $response = xmlrpc($url, 'pifr.queue', $this->clients['project']['client_key'], $batch);
+    $this->assertError($response, PIFR_RESPONSE_DENIED, array(
+      'Error(s) found in project #1: required key [client_identifier] not found.',
+      'Error(s) found in project #2: [client_identifier] must be an integer.',
+    ));
+
+    $batch['projects'][0]['client_identifier'] = 17;
+    $batch['projects'][1]['client_identifier'] = 17;
+    $response = xmlrpc($url, 'pifr.queue', $this->clients['project']['client_key'], $batch);
+    $this->assertError($response, PIFR_RESPONSE_DENIED, array(
+      'Error(s) found in project #1: [repository_type] must be one of [cvs, bzr].',
+      'Error(s) found in project #2: [client_identifier] must be unique.',
+    ));
+
+    // Check the various branch validation.
+    $batch['projects'][0]['repository_type'] = 'cvs';
+    $batch['projects'][1]['client_identifier'] = 18;
+    $batch['branches'][] = array(
+      'project_identifier' => '17a',
+      'client_identifier' => '170a',
+      'vcs_identifier' => 'HEAD',
+    );
+    $batch['branches'][] = array(
+      'project_identifier' => 18,
+      'client_identifier' => 180,
+      'vcs_identifier' => 'HEAD',
+      'dependency' => '170',
+      'rawr' => 'hi',
+      'plugin_argument' => 'foo',
+    );
+    $response = xmlrpc($url, 'pifr.queue', $this->clients['project']['client_key'], $batch);
+    $this->assertError($response, PIFR_RESPONSE_DENIED, array(
+      'Error(s) found in branch #1: [project_identifier] must be an integer, [client_identifier] must be an integer.',
+      'Error(s) found in branch #2: Only keys [project_identifier, client_identifier, vcs_identifier, dependency, plugin_argument, test, link] are allowed.',
+    ));
+
+    unset($batch['branches'][1]['rawr']);
+    $response = xmlrpc($url, 'pifr.queue', $this->clients['project']['client_key'], $batch);
+    $this->assertError($response, PIFR_RESPONSE_DENIED, array(
+      'Error(s) found in branch #1: [project_identifier] must be an integer, [client_identifier] must be an integer.',
+      'Error(s) found in branch #2: invalid dependency [170], [plugin_argument] must be an array.',
+    ));
+
+    // Check the various file validation.
+    $batch['branches'][0]['project_identifier'] = 17;
+    $batch['branches'][0]['client_identifier'] = 170;
+    $batch['branches'][1]['plugin_argument'] = array('foo' => 'bar');
+    $batch['files'][] = array(
+      'branch_identifier' => 171,
+      'client_identifier' => '1700a',
+      'file_url' => 'http://example.com/foo.patch',
+      'rawr' => 'hi',
+    );
+    $response = xmlrpc($url, 'pifr.queue', $this->clients['project']['client_key'], $batch);
+    $this->assertError($response, PIFR_RESPONSE_DENIED, array(
+      'Error(s) found in file #1: Only keys [branch_identifier, client_identifier, file_url, link] are allowed.',
+    ));
+
+    unset($batch['files'][0]['rawr']);
+    $response = xmlrpc($url, 'pifr.queue', $this->clients['project']['client_key'], $batch);
+    $this->assertError($response, PIFR_RESPONSE_DENIED, array(
+      'Error(s) found in file #1: [branch_identifier] not found in branch list, [client_identifier] must be an integer.',
+    ));
+
+    $batch['files'][0]['branch_identifier'] = 170;
+    $batch['files'][0]['client_identifier'] = 1700;
+    $response = xmlrpc($url, 'pifr.queue', $this->clients['project']['client_key'], $batch);
+    $this->assertEqual($response['branches'], array(
+      170 => 3,
+      180 => 4,
+    ), 'Branches saved');
+    $this->assertEqual($response['files'], array(
+      1700 => 5,
+    ), 'File queued');
+  }
+
+  /**
+   * Assert that an error was returned in the XML-RPC response.
+   *
+   * @param array $response XML-RPC response.
+   * @param integer $code XML-RPC response code to assert.
+   * @param mixed $error Either an array or errors to expect or a single error.
+   */
+  protected function assertError($response, $code, $error) {
+    $this->assertEqual($code, $response['response'], 'Response code equal to [' . $code . ']');
+    if (is_array($error)) {
+      $this->assertEqual($error, $response['errors'], 'Response errors found');
+    }
+    else {
+      $this->assertEqual($error, $response['errors'][0], 'Response error found [' . $error . ']');
+    }
+  }
 }
Index: server/pifr_server.xmlrpc.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/project_issue_file_review/server/pifr_server.xmlrpc.inc,v
retrieving revision 1.41
diff -u -r1.41 pifr_server.xmlrpc.inc
--- server/pifr_server.xmlrpc.inc	10 Feb 2010 22:14:10 -0000	1.41
+++ server/pifr_server.xmlrpc.inc	23 Feb 2010 01:21:43 -0000
@@ -29,22 +29,25 @@
  *     'project_identifier', // Reference to project by client identifier.
  *     'client_identifier', // Identifier that will be used to reference the branch.
  *     'vcs_identifier', // Version control system identifier.
- *     'dependency', // Comma seperated list of branch identifiers that this branch depends on.
- *     'plugin_argument', // Associative array of arguments keyed by plugin.
- *     'test', // Request branch to be tested.
+ *     'dependency', // (Optional) Comma seperated list of branch identifiers that this branch depends on.
+ *     'plugin_argument', // (Optional) Associative array of arguments keyed by plugin.
+ *     'test', // (Optional) (Default: false) Request branch to be tested.
+ *     'link', // (Optional) Web accessible link representing branch.
  *   );
  *
  *   $file = array(
  *     'branch_identifier', // Reference to branch by client identifier.
  *     'client_identifier', // Identifier that will be used to reference the file.
  *     'file_url', // Absolute URL to file.
+ *     'link', // (Optional) Web accessible link representing file.
  *   );
  *
  *   $project = array(
  *     'client_identifier', // Identifier that will be used to reference the project.
  *     'name', // Name of project.
- *     'repository_type', // Repository type (currently only 'cvs').
+ *     'repository_type', // Repository type.
  *     'repository_url', // Repository URL, pserver:anonymous:anonymous@cvs.drupal.org:/cvs/drupal.
+ *     'link', // (Optional) Web accessible link representing project.
  *   );
  * </code>
  * @return array Assigned test IDs, or array with response code, PIFR_RESPONSE_*.
@@ -59,23 +62,28 @@
  *
  * <code>
  *   $test = array(
- *     'response' // Response code: PIFR_RESPONSE_*.
+ *     'response', // Response code: PIFR_RESPONSE_*.
+ *     'errors', // (Optional) List of errors related to response code.
  *   );
  * </code>
  */
 function pifr_server_xmlrpc_queue($client_key, $batch) {
-  pifr_debug('XML-RPC batch(): received.');
-
   // Check if valid project client.
   $client = pifr_server_client_check_key($client_key, PIFR_SERVER_CLIENT_TYPE_PROJECT);
 
   if ($client) {
-    pifr_debug('XML-RPC batch(): valid project client.');
-
     if (!PIFR_ACTIVE) {
-      pifr_debug('XML-RPC batch(): server is not active, request denied.');
       return array(
         'response' => PIFR_RESPONSE_DENIED,
+        'errors' => array(t('Server is not active.')),
+      );
+    }
+
+    // Validate and clean the incoming batch.
+    if (!($batch = pifr_server_xmlrpc_validate('queue', $batch))) {
+      return array(
+        'response' => PIFR_RESPONSE_DENIED,
+        'errors' => pifr_server_xmlrpc_error(),
       );
     }
 
@@ -101,21 +109,18 @@
       'branches' => array(),
       'files' => array(),
     );
-
-    foreach ($branches as $branch) {
-      $response['branches'][$branch['client_identifier']] = $branch['test_id'];
-    }
-
-    foreach ($files as $file) {
-      $response['files'][$file['client_identifier']] = $file['test_id'];
+    foreach (array_keys($response) as $key) {
+      foreach ($$key as $item) {
+        $response[$key][$item['client_identifier']] = (int) $item['test_id'];
+      }
     }
 
     return $response;
   }
   else {
-    pifr_debug('XML-RPC batch(): invalid project client.');
     return array(
       'response' => PIFR_RESPONSE_INVALID_SERVER,
+      'errors' => array(t('Invalid project client, check key and ensure that client is enabled.')),
     );
   }
 }
@@ -151,8 +156,8 @@
   $saved = array();
   foreach ($branches as $branch) {
     if (!isset($projects[$branch['project_identifier']])) {
-      pifr_debug(t('XML-RPC batch(): missing project data for branch: (b: @identifier; p: @project_identifier).',
-                   array('@identifier' => $branch['client_identifier'], '@project_identifier' => $branch['client_identifier'])));
+      watchdog('pifr_server', 'XML-RPC batch(): missing project data for branch: (b: @identifier; p: @project_identifier).',
+        array('@identifier' => $branch['client_identifier'], '@project_identifier' => $branch['client_identifier']), WATCHDOG_ERROR);
       continue;
     }
 
@@ -201,8 +206,8 @@
   $saved = array();
   foreach ($files as $file) {
     if (!isset($branches[$file['branch_identifier']])) {
-      pifr_debug(t('XML-RPC batch(): missing branch data for file: (u: @file_url; n: @issue_nid).',
-                   array('@file_url' => $file['file_url'], '@issue_nid' => $file['issue_nid'])));
+      watchdog('pifr_server', 'XML-RPC batch(): missing branch data for file: (u: @file_url; n: @issue_nid).',
+        array('@file_url' => $file['file_url'], '@issue_nid' => $file['issue_nid']), WATCHDOG_ERROR);
       continue;
     }
 
@@ -272,32 +277,29 @@
  *
  * <code>
  *   $test = array(
- *     'response' // Response code: PIFR_RESPONSE_*.
+ *     'response', // Response code: PIFR_RESPONSE_*.
+ *     'errors', // (Optional) List of errors related to response code.
  *   );
  * </code>
  */
 function pifr_server_xmlrpc_next($client_key) {
-  pifr_debug('XML-RPC next(): received.');
-
   // Check if valid test client.
   $client = pifr_server_client_check_key($client_key, PIFR_SERVER_CLIENT_TYPE_TEST);
 
   if ($client) {
-    pifr_debug('XML-RPC next(): valid test client.');
-
     if (!PIFR_ACTIVE) {
-      pifr_debug('XML-RPC next(): server is not active, request denied.');
       return array(
         'response' => PIFR_RESPONSE_DENIED,
+        'errors' => array(t('Server is not active.')),
       );
     }
 
     return pifr_server_test_get_next($client);
   }
   else {
-    pifr_debug('XML-RPC next(): invalid test client.');
     return array(
       'response' => PIFR_RESPONSE_INVALID_SERVER,
+      'errors' => array(t('Invalid testing client, check key and ensure that client is enabled.')),
     );
   }
   return array(
@@ -341,14 +343,10 @@
  * @return Response code: PIFR_RESPONSE_*.
  */
 function pifr_server_xmlrpc_result($client_key, array $result) {
-  pifr_debug('XML-RPC result(): received.');
-
   // Check if valid test client.
   $client = pifr_server_client_check_key($client_key, PIFR_SERVER_CLIENT_TYPE_TEST);
 
   if ($client) {
-    pifr_debug('XML-RPC result(): valid test client.');
-
     // Ensure that test was sent to the client returning the result.
     if ($test = pifr_server_test_get($result['test_id'])) {
       if (pifr_server_environment_status_get_client($test, $client)) {
@@ -356,18 +354,17 @@
       }
       else {
         watchdog('pifr_server', 'Test not assigned to client: (t: @test_id, c: @client_id)',
-             array('@test_id' => $test['test_id'], '@client_id' => $client['client_id']), WATCHDOG_ALERT);
+          array('@test_id' => $test['test_id'], '@client_id' => $client['client_id']), WATCHDOG_ALERT);
         return PIFR_RESPONSE_DENIED;
       }
     }
     else {
       watchdog('pifr_server', 'Invalid test ID: (t: @test_id, c: @client_id)',
-             array('@test_id' => $test['test_id'], '@client_id' => $client['client_id']), WATCHDOG_ALERT);
+        array('@test_id' => $test['test_id'], '@client_id' => $client['client_id']), WATCHDOG_ALERT);
       return PIFR_RESPONSE_DENIED;
     }
   }
   else {
-    pifr_debug('XML-RPC result(): invalid test client.');
     return PIFR_RESPONSE_INVALID_SERVER;
   }
   return PIFR_RESPONSE_ACCEPTED;
@@ -398,23 +395,20 @@
  *
  * <code>
  *   $test = array(
- *     'response' // Response code: PIFR_RESPONSE_*.
+ *     'response', // Response code: PIFR_RESPONSE_*.
+ *     'errors', // (Optional) List of errors related to response code.
  *   );
  * </code>
  */
 function pifr_server_xmlrpc_retrieve($client_key, $since) {
-  pifr_debug('XML-RPC retrieve(): received.');
-
   // Check if valid test client.
   $client = pifr_server_client_check_key($client_key, PIFR_SERVER_CLIENT_TYPE_PROJECT);
 
   if ($client) {
-    pifr_debug('XML-RPC retrieve(): valid project client.');
-
     if (!PIFR_SERVER_REPORT) {
-      pifr_debug('XML-RPC retrieve(): reporting is not enabled, request denied.');
       return array(
         'response' => PIFR_RESPONSE_DENIED,
+        'errors' => array(t('Reporting is not enabled.')),
       );
     }
 
@@ -450,9 +444,9 @@
     return $batch;
   }
   else {
-    pifr_debug('XML-RPC retrieve(): invalid test client.');
     return array(
       'response' => PIFR_RESPONSE_INVALID_SERVER,
+      'errors' => array(t('Invalid project client, check key and ensure that client is enabled.')),
     );
   }
   return array(
@@ -483,3 +477,258 @@
   $offset = $now - $timestamp;
   return gmmktime() - $offset;
 }
+
+/**
+ * Validate the arguments for a given XML-RPC method.
+ *
+ * @param string $method XML-RPC method without the 'pifr.' prefix.
+ * @param ... Additional arguments to validate.
+ * @return mixed Validated and cleaned data.
+ */
+function pifr_server_xmlrpc_validate($method) {
+  // Get all the arguments after the first argument ($method).
+  $args = func_get_args();
+  array_shift($args);
+
+  // Call the related validation function and return the cleaned data.
+  $return = call_user_func_array('pifr_server_xmlrpc_validate_' . $method, $args);
+
+  // If an error occured return FALSE, execution should stop and the errors
+  // should be returned, as reported by pifr_server_xmlrpc_error().
+  if (pifr_server_xmlrpc_error()) {
+    return FALSE;
+  }
+  return $return;
+}
+
+/**
+ * Validate pifr.queue() batch.
+ *
+ * @param array $batch Queue batch to validate.
+ * @return array Cleaned data.
+ */
+function pifr_server_xmlrpc_validate_queue(array $batch) {
+  // Ensure every base key is present.
+  $default = array(
+    'branches' => array(),
+    'files' => array(),
+    'projects' => array(),
+  );
+  $batch += $default;
+
+  if (count(array_keys($batch)) > 3) {
+    pifr_server_xmlrpc_error(t('Only keys [branches, files, projects] are allowed.'));
+  }
+
+  // Get a list of supported vcs backends.
+  $includes = file_scan_directory(drupal_get_path('module', 'pifr_client') . '/review/vcs', '.*');
+  $backends = array();
+  foreach ($includes as $include) {
+    $backend = str_replace('.inc', '', $include->basename);
+    if ($backend != 'vcs') {
+      $backends[] = $backend;
+    }
+  }
+
+  // Validate each project.
+  $required_keys = array('client_identifier', 'name', 'repository_type', 'repository_url');
+  $default = array(
+    'link' => '',
+  );
+  $i = 0;
+  $project_identifiers = array();
+  foreach ($batch['projects'] as &$project) {
+    // Provide default values for optional keys.
+    $project += $default;
+    $i++;
+
+    // Ensure that all required keys are found and that all keys are valid.
+    if ($errors = pifr_server_xmlrpc_required_check($project, $required_keys)) {
+      pifr_server_xmlrpc_error_list('project', $i, $errors);
+      continue;
+    }
+    elseif (count(array_keys($project)) > 5) {
+      pifr_server_xmlrpc_error_list('project', $i, t('Only keys [client_identifier, name, ' .
+        'repository_type, repository_url, link] are allowed'));
+      continue;
+    }
+
+    if (!pifr_server_xmlrpc_is_int($project['client_identifier'])) {
+      $errors[] = t('[client_identifier] must be an integer');
+    }
+    elseif (in_array($project['client_identifier'], $project_identifiers)) {
+      $errors[] = t('[client_identifier] must be unique');
+    }
+    if (!in_array($project['repository_type'], $backends)) {
+      $errors[] = t('[repository_type] must be one of [@list]', array('@list' => implode(', ', $backends)));
+    }
+
+    // Keep a list of project identifiers for validation.
+    $project_identifiers[] = $project['client_identifier'];
+
+    // If any errors where found then register an error for the file.
+    pifr_server_xmlrpc_error_list('project', $i, $errors);
+  }
+
+  // Keep a list of branch identifiers for dependency validation.
+  $branch_identifiers = array();
+  foreach ($batch['branches'] as &$branch) {
+    $branch_identifiers[] = $branch['client_identifier'];
+  }
+
+  // Validate each branch.
+  $required_keys = array('project_identifier', 'client_identifier', 'vcs_identifier');
+  $default = array(
+    'dependency' => '',
+    'plugin_argument' => array(),
+    'test' => FALSE,
+    'link' => '',
+  );
+  $unique = array();
+  $i = 0;
+  foreach ($batch['branches'] as &$branch) {
+    // Provide default values for optional keys.
+    $branch += $default;
+    $i++;
+
+    // Ensure that all required keys are found and that all keys are valid.
+    if ($errors = pifr_server_xmlrpc_required_check($branch, $required_keys)) {
+      pifr_server_xmlrpc_error_list('branch', $i, $errors);
+      continue;
+    }
+    elseif (count(array_keys($branch)) > 7) {
+      pifr_server_xmlrpc_error_list('branch', $i, t('Only keys [project_identifier, client_identifier, ' .
+        'vcs_identifier, dependency, plugin_argument, test, link] are allowed'));
+      continue;
+    }
+
+    // Validate fields.
+    if (!pifr_server_xmlrpc_is_int($branch['project_identifier'])) {
+      $errors[] = t('[project_identifier] must be an integer');
+    }
+    if (!pifr_server_xmlrpc_is_int($branch['client_identifier'])) {
+      $errors[] = t('[client_identifier] must be an integer');
+    }
+    elseif (in_array($branch['client_identifier'], $unique)) {
+      $errors[] = t('[client_identifier] must be unique');
+    }
+    if (!empty($branch['dependency'])) {
+      $dependencies = explode(',', $branch['dependency']);
+      foreach ($dependencies as $dependency) {
+        if (!pifr_server_xmlrpc_is_int($dependency)) {
+          $errors[] = t('all dependencies must be integers');
+          break;
+        }
+        elseif (!in_array($dependency, $branch_identifiers)) {
+          $errors[] = t('invalid dependency [' . $dependency . ']');
+          break;
+        }
+      }
+    }
+    if (!is_array($branch['plugin_argument'])) { // TODO Validate key/value pairs.
+      $errors[] = t('[plugin_argument] must be an array');
+    }
+
+    // Add the client identifiers during loop for unique validation.
+    $unique[] = $branch['client_identifier'];
+
+    // If any errors where found then register an error for the branch.
+    pifr_server_xmlrpc_error_list('branch', $i, $errors);
+  }
+
+  // Validate each file.
+  $required_keys = array('branch_identifier', 'client_identifier', 'file_url');
+  $default = array(
+    'link' => '',
+  );
+  $i = 0;
+  foreach ($batch['files'] as &$file) {
+    // Provide default values for optional keys.
+    $file += $default;
+    $i++;
+
+    // Ensure that all required keys are found and that all keys are valid.
+    if ($errors = pifr_server_xmlrpc_required_check($file, $required_keys)) {
+      pifr_server_xmlrpc_error_list('file', $i, $errors);
+      continue;
+    }
+    elseif (count(array_keys($file)) > 4) {
+      pifr_server_xmlrpc_error_list('file', $i, t('Only keys [branch_identifier, client_identifier, file_url, link] are allowed'));
+      continue;
+    }
+
+    if (!pifr_server_xmlrpc_is_int($file['branch_identifier'])) {
+      $errors[] = t('[branch_identifier] must be an integer');
+    }
+    elseif (!in_array($file['branch_identifier'], $branch_identifiers)) {
+      $errors[] = t('[branch_identifier] not found in branch list');
+    }
+    if (!pifr_server_xmlrpc_is_int($file['client_identifier'])) {
+      $errors[] = t('[client_identifier] must be an integer');
+    }
+
+    // If any errors where found then register an error for the file.
+    pifr_server_xmlrpc_error_list('file', $i, $errors);
+  }
+
+  return $batch;
+}
+
+/**
+ * Store and retrieve XML-RPC errors.
+ *
+ * @param string $error (Optional) XML-RPC error, or NULL to simply retrieve.
+ * @return array List of errors.
+ */
+function pifr_server_xmlrpc_error($error = NULL) {
+  static $errors = array();
+
+  if ($error) {
+    $errors[] = $error;
+  }
+  return $errors;
+}
+
+/**
+ * Generate a string from a list of errors for a particular field.
+ *
+ * @param string $key Field key.
+ * @param integer $number Entry number of the field that triggered the errors.
+ * @param mixed $error List of errors, or single error message.
+ */
+function pifr_server_xmlrpc_error_list($key, $number, $error) {
+  if ($error) {
+    pifr_server_xmlrpc_error(t('Error(s) found in @key #@number: !list.', array(
+      '@key' => $key,
+      '@number' => $number,
+      '!list' => implode(', ', is_array($error) ? $error : array($error)),
+    )));
+  }
+}
+
+/**
+ * Generate errors for if required keys are not found.
+ *
+ * @param array Data to check for required keys in.
+ * @param array $required_keys List required keys.
+ * @return array List of errors generated by missing keys.
+ */
+function pifr_server_xmlrpc_required_check(array $data, array $required_keys) {
+  $errors = array();
+  foreach ($required_keys as $required_key) {
+    if (empty($data[$required_key])) {
+      $errors[] = t('required key [@key] not found', array('@key' => $required_key));
+    }
+  }
+  return $errors;
+}
+
+/**
+ * Check if a value is an integer.
+ *
+ * @param mixed $value Value to check.
+ * @return boolean TRUE if value is an integer, otherwise FALSE.
+ */
+function pifr_server_xmlrpc_is_int($value) {
+  return is_numeric($value) && (int) $value == (float) $value;
+}
