'textfield',
'#title' => t('Default Number of Hits'),
'#default_value' => variable_get('content_share_limit', 10),
'#size' => 5,
'#maxlength' => 5,
'#description' => t("The default maximum number of hits to return per query. Can be overidden in query and in Max number."),
'#required' => TRUE,
);
$form['content_share_cap'] = array(
'#type' => 'textfield',
'#title' => t('Query Cap'),
'#default_value' => variable_get('content_share_cap', 200),
'#size' => 5,
'#maxlength' => 5,
'#description' => t("Max number of hits to return in any single query. Overrides the default and user limits. Can be used to cap the server load."),
'#required' => TRUE,
);
$form['content_share_batch'] = array(
'#type' => 'textfield',
'#title' => t('Batch Size'),
'#default_value' => variable_get('content_share_batch', 200),
'#size' => 5,
'#maxlength' => 5,
'#description' => t("The number of hits to return per batch. To use with the 'batch' query term."),
'#required' => TRUE,
);
$form['content_share_require_authentication'] = array(
'#type' => 'checkbox',
'#title' => t('Block Anonymous Users, Require Authentication'),
'#default_value' => variable_get('content_share_require_authentication', 0),
'#description' => t("Block all anonymous usage and require Session Authentication from every user. Needs Session Authentication in Services Module Enabled"),
'#required' => TRUE,
);
// user roles
$user_roles = user_roles($membersonly = TRUE, $permission = NULL);
$form['content_share_roles'] = array(
'#type' => 'checkboxes',
'#title' => t('Allowed Roles'),
'#options' => $user_roles,
'#default_value' => variable_get('content_share_roles', $user_roles),
'#description' => t("Roles that can use Content Share when authentication is required."),
'#required' => TRUE,
);
// content types
$content_types = node_get_types('names');
// use this array to default to some, not all content types
foreach ($content_types as $key => $value) {
$default_types[$key] = $key;
}
$stored_default_types_auth = variable_get('content_share_content_types_auth', $default_types);
$form['content_share_content_types_auth'] = array(
'#type' => 'checkboxes',
'#title' => t('Content Types Exposed to Logged in User'),
'#options' => $content_types,
'#default_value' => $stored_default_types_auth,
'#description' => t("Select content types available via the Content Share API to a Logged in User'),."),
'#required' => TRUE,
);
$stored_default_types_anon = variable_get('content_share_content_types_anon', $default_types);
$form['content_share_content_types_anon'] = array(
'#type' => 'checkboxes',
'#title' => t('Content Types Exposed to Anonymous User'),
'#options' => $content_types,
'#default_value' => $stored_default_types_anon,
'#description' => t("Select content types available via the Content Share API to an Anonymous User."),
'#required' => TRUE,
);
// content fields
$core_fields = array(
'nid' => 'nid',
'type' => 'type',
'created' => 'created',
'title' => 'title',
'body' => 'body',
'teaser' => 'teaser',
'taxonomy' => 'taxonomy',
'language' => 'language',
'uid' => 'uid',
'url' => 'url',
'uuid' => 'uuid',
'status' => 'status',
'changed' => 'changed',
'comment' => 'comment',
'promote' => 'promote',
'moderate' => 'moderate',
'sticky' => 'sticky',
'tnid' => 'tnid',
'translate' => 'translate',
'vid' => 'vid',
'revision_uid' => 'revision_uid',
'log' => 'log',
'revision_timestamp' => 'revision_timestamp',
'format' => 'format',
'name' => 'name',
'picture' => 'picture',
'data' => 'data',
'last_comment_timestamp' => 'last_comment_timestamp',
'last_comment_name' => 'last_comment_name',
'comment_count' => 'comment_count',
);
$cck_fields = array_keys(content_fields()); //call a Content module function to get a nested array of all CCK fields on site. Get their keys only.
$flat_cck = array_combine($cck_fields, $cck_fields); //combine into associative array with same keys and value
$field_options = array_merge($core_fields, $flat_cck);
$content_share_default_fields = array(
'nid' => 'nid',
'type' => 'type',
'created' => 'created',
'title' => 'title',
'body' => 'body',
'teaser' => 'teaser',
'taxonomy' => 'taxonomy',
);
$stored_default_fields_anon = variable_get('content_share_content_fields_anon', $content_share_default_fields);
$form['content_share_content_fields_anon'] = array(
'#type' => 'checkboxes',
'#title' => t('Expose Fields to Anonymous User'),
'#options' => $field_options,
'#default_value' => $stored_default_fields_anon,
'#description' => t("Select fields available via the Content Share API to an Anonymous User."),
'#required' => TRUE,
);
$stored_default_fields_auth = variable_get('content_share_content_fields_auth', $content_share_default_fields);
$form['content_share_content_fields_auth'] = array(
'#type' => 'checkboxes',
'#title' => t('Expose Fields to Logged in User'),
'#options' => $field_options,
'#default_value' => $stored_default_fields_auth,
'#description' => t("Select fields available via the Content Share API to a Logged in User."),
'#required' => TRUE,
);
$form = system_settings_form($form);
// this calls the theme_content_share_settings function which rewrites most elements of the form
// with custom style and converts multi checkboxes into tables
$form['#theme'] = 'content_share_settings';
return $form;
}
/**
* This function rewrites the form elements with and converts some of them into tables
* with the help of two custom js classes
* note that submit and reset buttons are not rewritten by this function and returned as is
* @param array $form
* The form generated by content_share_settings().
* @return array
* The re-styled form.
*/
function theme_content_share_settings($form) {
drupal_set_html_head('
');
// rewrite all textfield and single checkboxes menu
foreach (element_children($form) as $element) {
if (preg_match('/textfield|checkbox$/', $form[$element]['#type'])) {
$rewritten_textfields = $rewritten_textfields . theme($form[$element]['#type'], $form[$element]);
unset($form[$element]);
}
}
// rewrite roles menu
$role_header = array(
$form['content_share_roles']['#title'],
theme('table_select_header_cell'),
'check/uncheck all',
);
$role_rows[] = array(
array(
'data' => 'ROLE NUMBER',
),
array(
'data' => 'ROLE NAME',
'colspan' => 2,
),
);
foreach (element_children($form['content_share_roles']) as $child) {
$checkboxes_role = drupal_render($form['content_share_roles'][$child]);
// first cell, showing the actual option value
$role_row = array( $child,
// second cell
array('data' => "$checkboxes_role",
'style' => 'text-align: left',
'colspan' => 2,
),
);
$role_rows[] = $role_row;
}
// Now render the table representing content_share_content_types
$rewritten_roles = theme('table', $role_header, $role_rows, array('class' => 'special-single-checkbox'));
unset($form['content_share_roles']); // then remove rewritten elements before rendering
// Rewrite expose content types menu.
$header = array(
'Exposed Types',
theme('table_select_header_cell'),
'check/uncheck all',
);
$rows[] = array(
array(
'data' => 'TYPE',
),
array(
'data' => 'ANONYMOUS',
'colspan' => 2,
),
array(
'data' => 'LOGGED IN',
'colspan' => 2,
),
);
foreach (element_children($form['content_share_content_types_auth']) as $child) {
$checkboxes_anon = drupal_render($form['content_share_content_types_anon'][$child]);
$checkboxes_auth = drupal_render($form['content_share_content_types_auth'][$child]);
$row = array(
// first cell, showing the actual option value
array(
'data' => "$child",
),
// second cell
array(
'data' => "$checkboxes_anon",
'style' => 'text-align: left',
'colspan' => 2,
),
array( // second cell
'data' => "$checkboxes_auth",
'style' => 'text-align: left',
'colspan' => 2,
),
);
$rows[] = $row;
}
// Now render the table representing content_share_content_types
$rewritten_types = theme('table', $header, $rows, array('class' => 'special-checkboxes'));
// $table_attributes = array('width' => '100%', 'border' => 0, 'cellspacing' => 5, 'cellpadding' => 5); //can use this in the array for theme args
unset($form['content_share_content_types_auth']); // then remove rewritten elements before rendering
unset($form['content_share_content_types_anon']);
// Rewrite expose fields menu.
$field_header = array(
'Exposed Fields',
theme('table_select_header_cell'),
'check/uncheck all',
);
$fields_rows[] = array(
array(
'data' => 'FIELD',
),
array(
'data' => 'ANONYMOUS',
'colspan' => 2,
),
array(
'data' => 'LOGGED IN',
'colspan' => 2,
),
);
foreach (element_children($form['content_share_content_fields_anon']) as $child) {
$fields_anon = drupal_render($form['content_share_content_fields_anon'][$child]);
$fields_auth = drupal_render($form['content_share_content_fields_auth'][$child]);
$fields_row = array(
// first cell, showing the actual option value
array(
'data' => "$child",
),
// second cell
array(
'data' => "$fields_anon",
'style' => 'text-align: left',
'colspan' => 2,
),
// second cell
array(
'data' => "$fields_auth",
'style' => 'text-align: left',
'colspan' => 2,
),
);
$fields_rows[] = $fields_row;
}
// Now render the table representing content_share_content_types
$rewritten_fields = theme('table', $field_header, $fields_rows, array('class' => 'special-checkboxes'));
// $table_attributes = array('width' => '100%', 'border' => 0, 'cellspacing' => 5, 'cellpadding' => 5); //can use this in the array for theme args
unset($form['content_share_content_fields_auth']); // then remove rewritten elements before rendering
unset($form['content_share_content_fields_anon']);
// Do a final rendering of the form and return the result.
$rewritten_form = $rewritten_textfields . $rewritten_roles . $rewritten_types . $rewritten_fields . drupal_render($form);
return $rewritten_form;
}
/**
* Implements hook_theme().
*/
function content_share_theme() {
return array(
'content_share_settings' => array(
'arguments' => array('form' => NULL),
),
);
}
/**
* Implements hook_menu().
*/
function content_share_menu() {
$items = array();
$items['content_share/test'] = array(
'title' => 'content_share API testing page.',
'page callback' => '_content_share_test',
'access callback' => TRUE,
'type' => MENU_CALLBACK
);
$items['admin/settings/content_share'] = array(
'title' => t('Content Share module settings'),
'description' => t('Description of the Content Share settings page'),
'page callback' => 'drupal_get_form',
'access callback' => 'user_access',
'page arguments' => array('content_share_settings'),
'access arguments' => array('access administration pages'),
'type' => MENU_NORMAL_ITEM,
);
return $items;
}
/**
* Validate settings input
* @param array, array ref
* The current form and its state
* @return void
*/
function content_share_settings_validate($form, &$form_state) {
$limit = $form_state['values']['content_share_limit'];
$cap = $form_state['values']['content_share_cap'];
if (!is_numeric($limit)) {
form_set_error('content_share_limit', t('You must enter an integer for the Default number of hits.'));
}
elseif ($limit <= 0) {
form_set_error('content_share_limit', t('Default number of hits must be positive.'));
}
elseif ($limit > $cap) {
form_set_error('content_share_limit', t('Default number of hits cannot be larger than Query Cap.'));
}
}
/**
* Implements hook_views_api().
*/
function content_share_views_api() {
return array(
'api' => 2,
'path' => drupal_get_path('module', 'content_share'),
);
}
/**
* Implements hook_services_resources().
*/
function content_share_services_resources() {
return array(
'content' => array(
'retrieve' => array(
'help' => 'Retrieves content',
'file' => array('file' => 'module', 'module' => 'content_share'),
'callback' => '_content_share_retrieve',
'access callback' => '_content_share_access',
'access arguments' => array('view'),
'access arguments append' => TRUE,
'args' => array(
array(
'name' => 'id',
'type' => 'int',
'description' => 'The id of the content to get',
'source' => array('path' => '0'),
'optional' => FALSE,
),
),
),
'index' => array(
'help' => 'Retrieves a listing of contents',
'file' => array('file' => 'module', 'module' => 'content_share'),
'callback' => '_content_share_index',
'access callback' => 'user_access',
'access arguments' => array('access content'),
'access arguments append' => FALSE,
'args' => array(array(
'name' => 'page',
'type' => 'int',
'description' => '',
'source' => array(
'param' => 'page',
),
'optional' => TRUE,
'default value' => 0,
),
array(
'name' => 'parameters',
'type' => 'array',
'description' => '',
'source' => 'param',
'optional' => TRUE,
'default value' => array(),
),
),
),
),
);
}
// *************************** Hooks end ***************************
// **********************************
// **** Tester function(s) begins ***************
/**
* Writes to db when hooks are called from services
* @param string
* @return void
*/
function _content_share_test_write($test_string) {
$string = new stdClass();
date_default_timezone_set('America/New_York');
$string->timestamp = date('c');
$string->query = $_SERVER['QUERY_STRING'];
$string->error = $test_string;
$write_result = drupal_write_record('content_share_stats', $string); //$write_result is FALSE if it fails to write to bd. For debuging only.
}
/**
* function for testing over a URL, called by hook_menu
* @param
* @return string
*/
function _content_share_test() {
_content_share_index();
return 'testing complete';
}
// *************************** Tester function(s) end ***************************
// **********************************
/**
* Function for retrieving nodes. Takes an nid and an error object
* gets field if they are in the query and processes node field according
* to permissions and what is requested in the query
* @param string, object ref
* @return object
*/
function _content_share_retrieve($nid, &$error_object) {
$node = node_load($nid);
// check the fields white list and remove those not in it, provide a default value if variable_get fails
$exposed_fields = _content_share_exposed('fields');
// add field in the URL query to the white list
parse_str($_SERVER['QUERY_STRING'], $fields_request_array); // parse the user URL input
// field to return from URL request input, defaults to settings if no user input
if (isset($fields_request_array['field'])) {
$fieldParse = new ParseField($fields_request_array['field']);
if ($fieldParse->isValid()) {
$exposed_fields = array_merge($exposed_fields, $fieldParse->getField());
}
else{
$error_object->error = 'error';
$error_object->field = $fieldParse->errorMsg;
}
}
// Process CCK field permissions. If the current user has no permissions specified by
// the Content module to view the CCK field, unset the field call a Content module function
// to get a nested array of all CCK fields on site. Get their keys only.
$cck_fields = array_keys(content_fields());
if (module_exists('content_permissions')) {
foreach ($cck_fields as $key => $field) {
if (!content_access('view', $field)) {
unset($exposed_fields[$field]);
}
}
}
// iterate through the node object
// unset if an object property is not in the white field list.
foreach ($node as $property => $value) {
if ($property !== $exposed_fields[$property]) {
unset($node->$property);
}
}
// Create and add custom fields to output
if ($exposed_fields['url']) {
$url_ops = array('absolute' => TRUE);
$node->url = url('node/' . $node->nid, $url_ops);
}
return $node;
}
/**
* Function to get access permissions for nodes
* @param string, array
* @return bool
*/
function _content_share_access($op = 'view', $args = array()) {
$object = (object) $args[0];
$type = $object->type;
return node_access($op, $type);
}
/** Function for cheking authentication and roles
* @params error object by reference
* @return bool
*/
function _content_share_user_validate(&$error_object) {
global $user;
// Check if user is authenticated
if ($user->uid == 0) {
$error_object->error = 'error';
$error_object->auth = 'You need to be log in to access this API service';
return FALSE;
}
// Check if the authenticated user has roles that approve api's use.
$approved_roles = variable_get('content_share_roles', FALSE);
$role_flag = FALSE;
if (is_array($approved_roles) && is_array($user->roles)) {
foreach ($user->roles as $role_id => $role_name) {
if ($approved_roles[$role_id] == $role_id) {
$role_flag = TRUE;
}
}
if ($role_flag == FALSE) {
$error_object->error = 'error';
$error_object->auth = 'Your roles are not approved by the admin for using Content Share API';
return FALSE;
}
}
// If all test are passed return true
return TRUE;
}
/**
* Function for providing an index (list) of nodes to
* convert to output format and return to user
* @param void
* @return array of objects
*/
function _content_share_index() {
//return array with node object as values
$index = array();
//array with parsed user input request
$request_array = array();
// parse the user URL input
parse_str($_SERVER['QUERY_STRING'], $request_array);
// Create an error object to collect all errors.
$error_object = new stdClass();
// A switch to see if authentication is required to use api and a function to validate user
$require_auth = variable_get('content_share_require_authentication', 0);
// Return if the user is not authenticated, write stats.
if ($require_auth == 1 && _content_share_user_validate($error_object) == FALSE) {
$index[] = $error_object;
_content_share_stat_write($error_object);
return $index;
}
// If the checks above are passed and user has validated, continue.
// Check if "help" is in the query and if it is, return only the help response.
if (isset($request_array['help'])) {
$helpParse = new ParseHelp($request_array['help']);
if ($helpParse->isValid()) {
_content_share_stat_write($error_object);
return array($helpParse->getHelp());
}
else{
$error_object->error = 'error';
$error_object->help = $helpParse->errorMsg;
_content_share_stat_write($error_object);
}
}
// Get the nodes from db and track errors.
$nids = _content_share_db_get($error_object);
// Check if count is present in the query request and return just the number of nodes if it is.
if (isset($request_array['size']) && $error_object->error != 'error') {
$sizeParse = new ParseSize($request_array['size']);
if ($sizeParse->isValid()) {
$hit_count = sizeof($nids);
return array('size' => $hit_count);
}
}
if (!empty($nids)) {
// If batch processing is set, limit the number of nodes to retrieve.
if (isset($request_array['batch'])) {
$batchParse = new ParseBatch($request_array['batch']);
if ($batchParse->isValid()) {
$batch_offset = ($batchParse->getBatch() - 1) * variable_get('content_share_batch', 200);
$nids = array_slice($nids, $batch_offset, variable_get('content_share_batch', 200), TRUE); //NB: RESIZING $nids ARRAY TO ONLY THE CURRENT CHUNK
}
else{
$error_object->error = 'error';
$error_object->batch = $batchParse->errorMsg;
$index[] = $error_object;
_content_share_stat_write($error_object);
return $index;}
}
// Nb: here $nids can be either the full list or the current chunk of nids.
foreach ($nids as $key => $nid) {
// This is cpu-intensive only retrieve nodes if there are no errors.
$retrieved_node = _content_share_retrieve($nid, $error_object);
// Index only node objects.
if (is_object($retrieved_node)) {
$index[] = $retrieved_node;
}
}
}
//*** CHECK IF THERE WERE ANY ERRORS DURING NODE RETRIEVAL
if ($error_object->error == 'error') {
$index = array(); //if they were field validation errors, discard all results
$index[] = $error_object; //and output only the error
}
_content_share_stat_write($error_object);
return $index;
}
/**
* Function to construct a db query based on the user input
* and settings, to run the query over the db and return an array of
* matched nodes
* @param $request
* @return return an array of NIDs matching the query
*/
function _content_share_db_get(&$error_object) {
$nids = array(); //return array
$request_array = array(); //contains parsed URL query
$where_clause = array(); //holds the SQL clauses
$select_term_count = 0;
parse_str($_SERVER['QUERY_STRING'], $request_array);
// Parse input and construct sql clauses.
foreach ($request_array as $term => $value) {
switch ($term) {
case "after":
$afterParser = new ParseAfter($value);
if ($afterParser->isValid()) {
$where_clause[] = $afterParser->getSQL();
$select_term_count++;
continue;
}
else{
$error_object->error = 'error';
$error_object->after = $afterParser->errorMsg;
break;
}
case "before":
$beforeParser = new ParseBefore($value);
if ($beforeParser->isValid()) {
$where_clause[] = $beforeParser->getSQL();
$select_term_count++;
continue;
}
else{
$error_object->before = $beforeParser->errorMsg;
break;
}
case "type":
$typeParser = new ParseType($value);
if ($typeParser->isValid()) {
$where_clause[] = $typeParser->getSQL();
$select_term_count++;
continue;
}
else{
$error_object->error = 'error';
$error_object->type = $typeParser->errorMsg;
break;
}
case "limit":
$limitParser = new ParseLimit($value);
if ($limitParser->isValid()) {
$user_limit = $limitParser->getLimit();
}
else{
$error_object->error = 'error';
$error_object->limit = $limitParser->errorMsg;
break;
}
continue;
case "search":
$searchParser = new ParseSearch($value);
if ($searchParser->isValid()) {
$where_clause[] = $searchParser->getSQL();
$select_term_count++;
continue;}
else{
$error_object->error = 'error';
$error_object->search = $searchParser->errorMsg;
break;
}
case "sort":
$sortParser = new ParseSort($value);
if ($sortParser->isValid()) {
$order_clause = $sortParser->getSQL();
}
else{
$error_object->error = 'error';
$error_object->sorting = $sortParser->errorMsg;
break;
}
}
}
// Check if the query is empty or does not have any recognizable terms.
if ($select_term_count == 0) {
$error_object->error = 'error';
$error_object->query = 'Query does not have any known selection terms';
return $nids;
}
// Construct the query from parsed input and sql clauses.
if ($error_object->error != 'error') {
$sql = 'SELECT distinct nid, type FROM {node} WHERE ';
// Construct the limit clause for SQL.
if (is_numeric($user_limit)) {
$limit = min($user_limit, variable_get('content_share_cap', 200));
}
else{
$limit = min(variable_get('content_share_limit', 10), variable_get('content_share_cap', 200));
}
// check if "batch" term is set
parse_str($_SERVER['QUERY_STRING'], $batch_request_array);
if (isset($batch_request_array['batch'])) {
// if batch is set no limit is imposed on the QUERY
$limit_clause = '';
}
else{
$limit_clause = ' LIMIT 0, ' . $limit;
}
// Check if the node is published. If not, do not return it.
$where_clause[] = 'status = 1';
// Construct the "oder by" clause.
$order_clause = isset($order_clause) ? $order_clause : ' ORDER BY created DESC ';
// Get an array with the list of exposed types.
$exposed_types_array = _content_share_exposed('types');
// Check if the indexed node is of the type in exposed types.
if (empty($exposed_types_array)) {
$error_object->error = 'error';
$error_object->types = 'No content types are set to be exposed via Content Share';
return $nids;
}
// Construct the "type in" where clause for types exposed in settings.
else {
// pad content types with " quotes for SQL
foreach ($exposed_types_array as $type_key => $type_value) {
if ($type_key === $type_value) {
$padded_exposed_types_array[] = '"' . $type_key . '"';
}
}
$where_clause[] = "type IN (" . implode( ', ', $padded_exposed_types_array) . ")";
}
// Put the query together and run it.
$whole_query = $sql . implode(' AND ', $where_clause) . $order_clause . $limit_clause;
$whole_query = db_rewrite_sql($whole_query);
$db_result = db_query($whole_query);
while ($node = db_fetch_object($db_result)) {
if (is_object($node)) {
$nids[] = $node->nid;
}
}
}
return $nids;
}
/**
* This function checks if the type of the node is listed as accessible for
* the user (logged or anon)
* @param string
* @return array of strings
*/
function _content_share_exposed($item) {
$require_auth = variable_get('content_share_require_authentication', 0);
$item_array = array();
switch ($require_auth) {
case "1":
$item_array = variable_get('content_share_content_' . $item . '_auth', array());
break;
case "0":
global $user;
if ($user->uid == 0) {
$item_array = variable_get('content_share_content_' . $item . '_anon', array());
}
else{
$item_array = variable_get('content_share_content_' . $item . '_auth', array());
}
break;
default:
$item_array = array();
}
return $item_array;
}
/**function _content_share_stat_write() for writing to stats in DB
* @param object ref - object with error messages from this module and Solr
* @return void
*/
function _content_share_stat_write(&$error_object) {
global $user;
$stats = new stdClass();
$stats->uid = $user->uid;
$stats->error = serialize($error_object);
date_default_timezone_set('America/New_York');
$stats->timestamp = date('c');
// NB test this function on real ANONYMOUS requests to make sure it records IP of the origin,
// not the cashing proxy
$stats->ip = ip_address();
$stats->query = $_SERVER['QUERY_STRING'];
// $write_result is FALSE if it fails to write to bd. For debuging only.
$write_result = drupal_write_record('content_share_stats', $stats);
}