cis_connector.module

  1. cis7 sites/all/modules/elmsln_contrib/cis_connector/cis_connector.module
  2. cle7 sites/all/modules/elmsln_contrib/cis_connector/cis_connector.module
  3. ecd7 sites/all/modules/elmsln_contrib/cis_connector/cis_connector.module
  4. elmsmedia7 sites/all/modules/elmsln_contrib/cis_connector/cis_connector.module
  5. harmony7 sites/all/modules/elmsln_contrib/cis_connector/cis_connector.module
  6. icor7 sites/all/modules/elmsln_contrib/cis_connector/cis_connector.module
  7. meedjum_blog7 sites/all/modules/elmsln_contrib/cis_connector/cis_connector.module
  8. mooc7 sites/all/modules/elmsln_contrib/cis_connector/cis_connector.module

CIS Connector, library to build an ELMS learning network.

This is a collection of modules and features that enables distributions to talk to each other via structured webservice calls. Drupal to drupal and Drupal to anything that provides xml / json is supported.

Functions

Namesort descending Description
cis_connector_admin_menu_cache_info Implements hook_admin_menu_cache_info().
cis_connector_entity_presave Implements hook_entity_presave().
cis_connector_field_info_alter Implements hook_field_info_alter().
cis_connector_flush_caches Implements hook_flush_caches().
cis_connector_form_alter Implements hook_form_alter().
cis_connector_form_field_ui_field_edit_form_alter Implements hook_form_FORMID_alter().
cis_connector_restws_meta_controls_alter Implements hook_restws_meta_controls_alter().
cis_connector_role_access Access callback for user roles.
cis_connector_views_api Implements hook_views_api().
_cis_connection_object Structured calls against single objects in CIS.
_cis_connection_prepare_entity Prepare an entity for transmission to a webservice.
_cis_connection_query Wrapper for structured queries against CIS system
_cis_connection_service_instance_query Wrapper for structured queries against a matching service.
_cis_connection_set_data Wrapper for structured updates to data in CIS.
_cis_connector_build_registry Build the registry of educational connectors
_cis_connector_cache_clear Invalidates cached data relating to cis_connector.
_cis_connector_format_address Return a well formed address based on certain values.
_cis_connector_real_address Swap file path from service_address to real file location.
_cis_connector_remote_entities Collect all remote entities for sending off items to other buckets.
_cis_connector_request Wrapper for http requests to enable cached requests.
_cis_connector_section_context Returns the user's section context from their group / global.
_cis_connector_simple_mail Simple mail function to send an email.
_cis_connector_transaction Callback to issue a complex transaction against CIS.

Constants

File

sites/all/modules/elmsln_contrib/cis_connector/cis_connector.module
View source
  1. <?php
  2. /**
  3. * @file
  4. * CIS Connector, library to build an ELMS learning network.
  5. *
  6. * This is a collection of modules and features that enables
  7. * distributions to talk to each other via structured
  8. * webservice calls. Drupal to drupal and Drupal to anything
  9. * that provides xml / json is supported.
  10. */
  11. // this is a kill switch to force all calls to be uncached
  12. // trigger this when testing and debugging calls
  13. define ('CIS_CONNECTOR_DEVELOPMENT', FALSE);
  14. define ('CIS_CONNECTOR_ROOT_USER_DEVELOPMENT', FALSE);
  15. define ('CIS_CONNECTOR_ENTITY_BOTH', 0);
  16. define ('CIS_CONNECTOR_ENTITY_REMOTE_ONLY', 1);
  17. /**
  18. * Implements hook_restws_meta_controls_alter().
  19. */
  20. function cis_connector_restws_meta_controls_alter(&$controls) {
  21. $controls['deep-load-refs'] = 'deep-load-refs';
  22. }
  23. /**
  24. * Build the registry of educational connectors
  25. *
  26. * @param $bucket
  27. * (optional) the tool that you want to build
  28. * connection settings for. If no bucket is passed
  29. * then the return will have all settings for all
  30. * buckets that have been defined.
  31. *
  32. * @return $settings
  33. * an array of connection settings details or FALSE
  34. */
  35. function _cis_connector_build_registry($bucket = NULL) {
  36. // statically cache future calls
  37. $settings = &drupal_static(__FUNCTION__);
  38. if (!isset($settings)) {
  39. // invoke special hook for registry settings
  40. $settings = module_invoke_all('cis_service_registry');
  41. // follows best practice of drupal but only provided for future override potential
  42. drupal_alter('cis_service_registry', $settings);
  43. }
  44. if (!is_null($bucket)) {
  45. // validate that this bucket exists
  46. if (isset($settings[$bucket])) {
  47. return $settings[$bucket];
  48. }
  49. return FALSE;
  50. }
  51. // validate settings were found
  52. if (!empty($settings)) {
  53. return $settings;
  54. }
  55. return FALSE;
  56. }
  57. /**
  58. * Structured calls against single objects in CIS.
  59. *
  60. * @param $id
  61. * (optional) entity id of the item to request.
  62. * @param $entity_type
  63. * (optional) entity type to return, defaults to `node`.
  64. * @param $format
  65. * (optional) format of the data to return, json / xml
  66. * are built in RestWS response formats.
  67. * @param $method
  68. * (optional) method of connection to issue, GET POST or PUT.
  69. * @param $data
  70. * (optional) array to send in the request, only useful
  71. * when the method of connection is POST or PUT. This info
  72. * is passed via json encoding so must be used with json
  73. * format option.
  74. * @param $bucket
  75. * (optional) data bucket to request, default is cis but
  76. * any tool currently in this network is valid.
  77. * @param $instance
  78. * (optional) instanace of a tool / distribution to request.
  79. * For example, this can be used as the base_url() to ask
  80. * a cle tool instance about how to create a link to an assignment
  81. * for display in the mooc distributioncourse living in mooc.
  82. * By not requiring instance to match you could also ask other
  83. * drupal based webservices for information where base_url does
  84. * not match. ELMSLN has no use-case for that yet though.
  85. * @param $cache
  86. * (optional) whether or not to cache the request, only valid
  87. * when requesting data via GET.
  88. * @param $extra
  89. * (optional) this can contain other non-data options that the service
  90. * may use internally. Common example could be passing 'deep-load-refs'
  91. * as the $extra variable so that entity references with in the result
  92. * set are automatically loaded. This is a string so it can contain
  93. * any data that the current API doesn't support.
  94. * @param $call_options
  95. * (optional) options beyond method. used for additional flexibility
  96. * in complex requests that may require header changes for example
  97. * transmitting files or issing non-blocking calls.
  98. * @return $data
  99. * an array of resulting entities, even if updated.
  100. */
  101. function _cis_connection_object($id = NULL, $entity_type = 'node', $format = 'json', $method = 'GET', $data = NULL, $bucket = 'cis', $instance = '', $cache = TRUE, $extra = '', $call_options = NULL) {
  102. // options for method
  103. $options = array('method' => $method);
  104. if (!empty($call_options)) {
  105. $options += $call_options;
  106. }
  107. // allow for PUT requests which require use of the data property
  108. if (!empty($data)) {
  109. $options['data'] = drupal_json_encode($data);
  110. $options['headers'] = array('Content-Type' => 'application/json');
  111. }
  112. // build the call
  113. $call = $instance . $entity_type;
  114. if (!is_null($id)) {
  115. $call .= '/' . $id;
  116. }
  117. if (!empty($format)) {
  118. $call .= '.' . $format;
  119. }
  120. // allow for custom additions of properties like deep-load-refs
  121. if (!empty($extra)) {
  122. $call .= '?' . $extra;
  123. }
  124. // generate the well structured request
  125. if ($method == 'PUT' || $method == 'POST') {
  126. // PUT can not be cached
  127. $response = _cis_connector_request($call, $options, $bucket, FALSE);
  128. }
  129. else {
  130. $response = _cis_connector_request($call, $options, $bucket, $cache);
  131. }
  132. // ensure a response to connect in the first place
  133. $data = FALSE;
  134. if ($response) {
  135. // parse format correctly for return if we can
  136. switch ($format) {
  137. case 'json':
  138. $data = drupal_json_decode($response->data);
  139. break;
  140. case 'xml':
  141. $data = simplexml_load_string($response->data);
  142. break;
  143. default:
  144. $data = $response->data;
  145. break;
  146. }
  147. }
  148. return $data;
  149. }
  150. /**
  151. * Wrapper for structured updates to data in CIS.
  152. *
  153. * This provides a standard way of selecting results
  154. * in the CIS from any tool. It then takes those entities
  155. * and alters the data key pairs associated to them
  156. * An example usage of this would be selecting a
  157. * service-instance in the CIS and updating the cron_key
  158. * in one function instead of multiple transactions.
  159. *
  160. * @param $select
  161. * array of values to select in the CIS data source
  162. * which supports multiple values but commonly is
  163. * for the selection of 1 item
  164. * @param $data
  165. * the data values to modify in the result set of
  166. * items that were found via the select.
  167. */
  168. function _cis_connection_set_data($select, $data) {
  169. // query to get an information set
  170. $resp = _cis_connection_query($select);
  171. // if we have an item, its a valid item
  172. if (!empty($resp['list'])) {
  173. // update all items that were found
  174. foreach ($resp['list'] as $response_item) {
  175. // post back to CIS
  176. _cis_connection_object($response_item['nid'], 'node', NULL, 'PUT', $data);
  177. }
  178. }
  179. }
  180. /**
  181. * Wrapper for structured queries against a matching service.
  182. *
  183. * This is mostly a shortcut for getting the information we'll
  184. * most commonly want from _cis_connection_query().
  185. *
  186. * @param $bucket
  187. * (optional) data bucket to request, default is cis but
  188. * this really needs to be performed against a matching
  189. * service instance in the network like mooc -> cle.
  190. * @param $entity_type
  191. * (optional) entity type to return, defaults to `node`.
  192. * @param $query
  193. * (optional) array of key paired data for selecting
  194. * matching entities.
  195. * @param $cache
  196. * (optional) whether or not to cache the request, only valid
  197. * when requesting data via GET.
  198. * @return $result['list']
  199. * an array of resulting entities or null if none returned.
  200. */
  201. function _cis_connection_service_instance_query($bucket = 'cis', $entity_type = 'node', $query = array(), $cached = TRUE) {
  202. // abstract a related sub-service, this will just remove the / if base-path is root
  203. $path = substr(base_path(), 1);
  204. $result = _cis_connection_query($query, $entity_type, 'json', 'GET', $bucket, $path, $cached);
  205. // return items if any were found
  206. if (isset($result['list'])) {
  207. return $result['list'];
  208. }
  209. return NULL;
  210. }
  211. /**
  212. * Wrapper for structured queries against CIS system
  213. *
  214. * @param $query
  215. * (optional) array of key paired data for selecting
  216. * matching entities.
  217. * @param $entity_type
  218. * (optional) entity type to return, defaults to `node`.
  219. * @param $format
  220. * (optional) format of the data to return, json / xml
  221. * are built in RestWS response formats.
  222. * @param $method
  223. * (optional) method of connection to issue, GET POST or PUT.
  224. * @param $bucket
  225. * (optional) data bucket to request, default is cis but
  226. * any tool currently in this network is valid.
  227. * @param $instance
  228. * (optional) instanace of a tool / distribution to request.
  229. * For example, this can be used as the base_url() to ask
  230. * a cle tool instance about how to create a link to an assignment
  231. * for display in the mooc distributioncourse living in mooc.
  232. * By not requiring instance to match you could also ask other
  233. * drupal based webservices for information where base_url does
  234. * not match. ELMSLN has no use-case for that yet though.
  235. * @param $cache
  236. * (optional) whether or not to cache the request, only valid
  237. * when requesting data via GET.
  238. * @return $data
  239. * an array of resulting entities.
  240. */
  241. function _cis_connection_query($query = array(), $entity_type = 'node', $format = 'json', $method = 'GET', $bucket = 'cis', $instance = '', $cached = TRUE) {
  242. // options for method
  243. $options = array('method' => $method);
  244. // build the call
  245. $call = $instance . $entity_type . '.' . $format . '?' . http_build_query($query);
  246. // generate the well structured request
  247. $response = _cis_connector_request($call, $options, $bucket, $cached);
  248. // ensure a response to connect in the first place
  249. $data = FALSE;
  250. if ($response) {
  251. // parse format correctly for return if we can
  252. switch ($format) {
  253. case 'json':
  254. $data = drupal_json_decode($response->data);
  255. break;
  256. case 'xml':
  257. $data = simplexml_load_string($response->data);
  258. break;
  259. default:
  260. $data = $response->data;
  261. break;
  262. }
  263. }
  264. return $data;
  265. }
  266. /**
  267. * Return a well formed address based on certain values.
  268. *
  269. * @param $settings
  270. * The connection settings for a tool in the network.
  271. * This should be provided by _cis_connector_build_registry().
  272. * @param $instance
  273. * (optional) The instance to issue the request against.
  274. * @param $method
  275. * (optional) service based or front facing address to generate.
  276. * This is whether or not to append web service account credentials
  277. * to the request and also has front-end vs back-end call implications.
  278. * @return $address
  279. * A formatted string that links to the other service and would provide
  280. * a successful connection either as a back-end process or front end
  281. * link for a person to see.
  282. *
  283. * @see _cis_connector_build_registry()
  284. * @see _cis_connector_real_address()
  285. */
  286. function _cis_connector_format_address($settings, $instance = '', $method = 'service') {
  287. // start with protocol
  288. $address = $settings['protocol'] . '://';
  289. // allow for front end link or data connection
  290. if ($method == 'service') {
  291. // optional HTTP authenticated request, required in any *real* environment
  292. if (isset($settings['user'])) {
  293. $address .= $settings['user'] . ':' . $settings['pass'] . '@';
  294. }
  295. // append the connection address
  296. $address .= $settings['service_address'] . $instance;
  297. }
  298. elseif ($method == 'front') {
  299. // append the real address
  300. $address .= $settings['address'] . $instance;
  301. }
  302. return $address;
  303. }
  304. /**
  305. * Swap file path from service_address to real file location.
  306. *
  307. * This is required at times when requesting file path of
  308. * a file in the remote system's file directory. While
  309. * it is not a requirement to have an alternate service
  310. * address from the real one, it is highly recommended
  311. * for increased security. As a result, calls against
  312. * this service address will naturally have the service
  313. * address built into them because of how drupal handles
  314. * public:// and private:// file references.
  315. *
  316. * @param $path
  317. * The path to modify to the real address
  318. * @return string
  319. * The path after being modified to have the services location removed.
  320. */
  321. function _cis_connector_real_address($path) {
  322. // verify settings bucket exist
  323. // @ignore druplart_conditional_assignment
  324. if ($settings = _cis_connector_build_registry('cis')) {
  325. // need to account for services in the services sites bucket
  326. // then need to swap service address for front-end address
  327. return str_replace('/services/', '/', str_replace($settings['service_address'], $settings['address'], $path));
  328. }
  329. }
  330. /**
  331. * Returns the user's section context from their group / global.
  332. *
  333. * This function can be thought of as the elmsln equivalent
  334. * of og's og_context function. It looks at environmental values
  335. * including who you are and where you are currently to attempt
  336. * to determine section context. This is important when making
  337. * determinations about which queries to structure and execute
  338. * against the CIS or other systems in the network.
  339. *
  340. * In the MOOC distribution, this is used in order to determine
  341. * which instructional outline to present a user. Needless to say
  342. * this is an extremely important function that helps unify the
  343. * user experience in all interactions with the site without
  344. * the need for bloated capabilies like purl module.
  345. *
  346. * @param $account
  347. * (optional) user account to calculate context against but
  348. * if none is specified, it assumes currently logged in user.
  349. * @return $section
  350. * The system-wide, unique value based on field_section_id
  351. * associated with an OG node in the system.
  352. *
  353. * @see og_context()
  354. */
  355. function _cis_connector_section_context($account = NULL) {
  356. // statically cache future calls
  357. $section = &drupal_static(__FUNCTION__);
  358. if (!isset($section)) {
  359. // check for global session override
  360. if (isset($_SESSION['cis_section_context'])) {
  361. return $_SESSION['cis_section_context'];
  362. }
  363. // use current user if acount is not set
  364. if (empty($account)) {
  365. $account = $GLOBALS['user'];
  366. }
  367. // attempt to grab og context, rare but possible
  368. $group = og_context();
  369. if (isset($group['gid'])) {
  370. $group = node_load($group['gid']);
  371. }
  372. else {
  373. // load groups they are part of
  374. $groups = og_get_entity_groups('user', $account);
  375. if (!empty($groups['node'])) {
  376. $group = node_load(array_pop($groups['node']));
  377. }
  378. }
  379. // ensure section is set
  380. if (isset($group->field_section_id) && is_array($group->field_section_id[LANGUAGE_NONE])) {
  381. // pull out the section value if available
  382. $section = $group->field_section_id[LANGUAGE_NONE][0]['safe_value'];
  383. }
  384. else {
  385. // see if we are an administrative account that might be missing access
  386. // to a group but yet clearly could be in the master
  387. if ($account->uid == 1 || array_intersect(array('administrator', 'instructor', 'staff'), array_values($account->roles))) {
  388. $query = new EntityFieldQuery();
  389. // pull all nodes
  390. $query->entityCondition('entity_type', 'node')
  391. // that are sections
  392. ->entityCondition('bundle', 'section')
  393. // that are published
  394. ->propertyCondition('status', 1)
  395. // that have a section like master_
  396. ->fieldCondition('field_section_id', 'value', 'master_%', 'like')
  397. ->addMetaData('account', user_load(1));
  398. // store results
  399. $result = $query->execute();
  400. // ensure we have results
  401. if (isset($result['node'])) {
  402. $nids = array_keys($result['node']);
  403. $sections = entity_load('node', $nids);
  404. $group = array_pop($sections);
  405. $section = $group->field_section_id[LANGUAGE_NONE][0]['safe_value'];
  406. // add person to this group automatically
  407. $values = array(
  408. 'entity_type' => 'user',
  409. 'entity' => $account,
  410. );
  411. og_group('node',$group->nid, $values);
  412. }
  413. }
  414. else {
  415. // last ditch effort, check for ability to see section switcher
  416. // this implies a role that should be able to see the way sections
  417. // are setup and so we need to just pick the best available option
  418. if (user_access('switch section context')) {
  419. $query = new EntityFieldQuery();
  420. // pull all nodes
  421. $query->entityCondition('entity_type', 'node')
  422. // that are sections
  423. ->entityCondition('bundle', 'section')
  424. // that are published
  425. ->propertyCondition('status', 1)
  426. // that are active
  427. ->fieldCondition('field_cis_active', 'value', '1', '=')
  428. // ordered by date created
  429. ->propertyOrderBy('created', 'DESC')
  430. // load all possible results in system
  431. ->addMetaData('account', user_load(1))
  432. // only get the top one though
  433. ->range(0, 1);
  434. // store results
  435. $result = $query->execute();
  436. // ensure we have results
  437. if (isset($result['node'])) {
  438. $nids = array_keys($result['node']);
  439. $sections = entity_load('node', $nids);
  440. $group = array_pop($sections);
  441. $section = $group->field_section_id[LANGUAGE_NONE][0]['safe_value'];
  442. // also hijack the global session so that the form is auto populated
  443. // with a reasonable value instead of magically giving a section
  444. // association. This means they won't be able to "unset" their
  445. // made up section affiliation but that's ok.
  446. $_SESSION['cis_section_context'] = $section;
  447. }
  448. }
  449. else {
  450. // @todo this use case has no positive outcome
  451. $section = 'default';
  452. }
  453. }
  454. }
  455. }
  456. return $section;
  457. }
  458. /**
  459. * Callback to issue a complex transaction against CIS.
  460. *
  461. * This is for some commonly requested things that may
  462. * take multiple queries to accomplish. There is no API
  463. * for adding things to this list at this time as these
  464. * can be thought of as internal system short-cut routines.
  465. *
  466. * @param $request
  467. * The transaction to request, `section` being a common one.
  468. * @param $format
  469. * (optional) An optional condition to streamline the method
  470. * of output. This is only used by some transactions.
  471. * @param $query
  472. * (optional) Additional query params that some complex
  473. * transactions need to obtain the right data set.
  474. * @param $section
  475. * (optional) Perform this transaction for a specific section
  476. * but will usually just default to active section context.
  477. * @return $output
  478. * The returned output is specific to each transaction
  479. * but is typically a keyed array or string.
  480. */
  481. function _cis_connector_transaction($request, $format = 'default', $query = array(), $section = NULL) {
  482. // return false if we don't match a request
  483. $output = FALSE;
  484. // grab section context of the user
  485. if (is_null($section)) {
  486. $section = _cis_connector_section_context();
  487. }
  488. // form the commonly used base query
  489. $base_query = array('field_access_string' => $section, 'archived' => 0);
  490. // process request
  491. switch ($request) {
  492. case 'section':
  493. // query access string based on section context
  494. $query = $base_query;
  495. // request the section of the user
  496. $cis_section_item = _cis_connection_query($query, 'field_collection_item');
  497. // return the whole section array
  498. $output = $cis_section_item['list'][0];
  499. break;
  500. case 'contact_info':
  501. // query access string based on section context
  502. $query = $base_query;
  503. // request the section of the user
  504. $cis_section_item = _cis_connection_query($query, 'field_collection_item');
  505. // return an array with input format and textual content of contact info
  506. $output = $cis_section_item['list'][0]['field_contact_info'];
  507. break;
  508. case 'other_services':
  509. // query access string based on section context
  510. $query = $base_query;
  511. // request the section of the user
  512. $cis_section_item = _cis_connection_query($query, 'field_collection_item');
  513. // return the whole section array
  514. $section_object = $cis_section_item['list'][0];
  515. // check for services defined
  516. if (isset($section_object['field_services']) && is_array($section_object['field_services'])) {
  517. // walk each service requesting details
  518. foreach ($section_object['field_services'] as $key => $service_field) {
  519. $query = array(
  520. 'status' => 1,
  521. 'nid' => $service_field['id'],
  522. );
  523. // query the service object
  524. $service_object = _cis_connection_query($query, $service_field['resource']);
  525. // hold onto the objects
  526. $output[$key] = $service_object['list'][0];
  527. }
  528. }
  529. break;
  530. case 'help':
  531. case 'guided_tour':
  532. case 'resources':
  533. // query access string based on section context
  534. $query = $base_query;
  535. // request the section of the user
  536. $cis_section_item = _cis_connection_query($query, 'field_collection_item');
  537. // make sure it got data
  538. if (!empty($cis_section_item)) {
  539. $list = '';
  540. if ($request == 'resources') {
  541. // display related services prior to other resources
  542. /*$services = _cis_connector_transaction('other_services');
  543. // make sure this is actually using other services
  544. if (is_array($services)) {
  545. // render each service return item
  546. foreach ($services as $service) {
  547. $list .= '<h2>' . $service['title'] . '</h2>' . "\n";
  548. $list .= '<div>' . $service['body']['value'] . "\n";
  549. $list .= l(t('Access the @title service', array('@title' => $service['title'])), $service['field_location']['url'] . base_path()) . '</div>' . "\n";
  550. }
  551. }*/
  552. // loop through resources associated to build their info
  553. foreach ($cis_section_item['list'][0]['field_' . $request] as $reid) {
  554. $resource = _cis_connection_object($reid['id'], $reid['resource']);
  555. $list .= '<h2>' . $resource['title'] . '</h2>';
  556. $list .= $resource['body']['value'];
  557. }
  558. }
  559. // request the standard resource language
  560. $query = array('type' => 'resource', 'field_machine_name' => $request . '_page');
  561. $resource_page = _cis_connection_query($query);
  562. // render text applying the input filter requested
  563. $resource_page['list'][0]['body']['value'] .= $list;
  564. // send the text with format for processing
  565. $output = $resource_page['list'][0]['body'];
  566. }
  567. break;
  568. case 'section_dates':
  569. $query = $base_query;
  570. // request the section of the user, always uncached
  571. $cis_section_item = _cis_connection_query($query, 'field_collection_item', 'json', 'GET', 'cis', '', FALSE);
  572. // pull back access duration
  573. $dates = $cis_section_item['list'][0]['field_access_dates'];
  574. // pull back the date class officially begins
  575. $class_begin = $cis_section_item['list'][0]['field_course_start'];
  576. // return access start and end timestamps
  577. $output = array('start' => $dates['value'], 'class_begin' => $class_begin, 'end' => $dates['value2']);
  578. break;
  579. case 'welcome_page':
  580. // query access string based on section context
  581. $query = $base_query;
  582. // request the section of the user
  583. $cis_section_item = _cis_connection_query($query, 'field_collection_item');
  584. $output = '';
  585. if (!empty($cis_section_item)) {
  586. // make sure a file was uploaded for this request
  587. if (isset($cis_section_item['list'][0]['field_welcome_page']['value'])) {
  588. $output = check_markup($cis_section_item['list'][0]['field_welcome_page']['value'], $cis_section_item['list'][0]['field_welcome_page']['format']);
  589. }
  590. else {
  591. // request the standard language if we don't have custom
  592. $query = array('type' => 'resource', 'field_machine_name' => 'field_welcome_letter');
  593. $resource = _cis_connection_query($query);
  594. // render text applying the input filter requested
  595. $output = check_markup($resource['list'][0]['body']['value'], $resource['list'][0]['body']['format']);
  596. }
  597. // see if there's a welcome letter to append
  598. if (isset($cis_section_item['list'][0]['field_welcome_letter'])) {
  599. $file = _cis_connection_object($cis_section_item['list'][0]['field_welcome_letter']['file']['id'], 'file');
  600. // append a download link
  601. $output .= l(t('Download @request (PDF)', array('@request' => drupal_ucfirst('welcome_letter'))), $file['url']);
  602. }
  603. }
  604. break;
  605. case 'welcome_letter':
  606. case 'syllabus':
  607. // query access string based on section context
  608. $query = $base_query;
  609. // request the section of the user
  610. $cis_section_item = _cis_connection_query($query, 'field_collection_item');
  611. // make sure it got data
  612. if (!empty($cis_section_item)) {
  613. // make sure a file was uploaded for this request
  614. if (isset($cis_section_item['list'][0]['field_' . $request])) {
  615. $file = _cis_connection_object($cis_section_item['list'][0]['field_' . $request]['file']['id'], 'file');
  616. }
  617. // if a url is set we have a file
  618. if (isset($file['url'])) {
  619. // return the file as a download
  620. if ($format == 'download') {
  621. // return the file via direct http call
  622. $options = array();
  623. $file_contents = _cis_connector_request(_cis_connector_real_address($file['url']), $options, 'none', FALSE);
  624. // set the content type to the file returned info
  625. drupal_add_http_header('Content-type', $file['mime']);
  626. // set the filename to the file returned info
  627. drupal_add_http_header('Content-Disposition', 'attachment; filename="' . $file['name'] . '"');
  628. // prompt headers for file download
  629. drupal_send_headers();
  630. // write the data of the request to the screen
  631. print $file_contents->data;
  632. exit;
  633. }
  634. else {
  635. // request the standard language
  636. $query = array('type' => 'resource', 'field_machine_name' => $request);
  637. $resource = _cis_connection_query($query);
  638. // render text applying the input filter requested
  639. $output = check_markup($resource['list'][0]['body']['value'], $resource['list'][0]['body']['format']);
  640. // append a download link
  641. $output .= l(t('Download @request (PDF)', array('@request' => drupal_ucfirst($request))), _cis_connector_real_address($file['url']));
  642. }
  643. }
  644. else {
  645. // file wasn't found
  646. $output = t('Please request a copy of the "@request" from your instructor', array('@request' => str_replace('_', ' ', $request)));
  647. }
  648. }
  649. else {
  650. $output = t('You must be enrolled in this course in order to view the @request', array('@request' => $request));
  651. }
  652. break;
  653. case 'activation_code':
  654. // request the code input by the user
  655. $codes = _cis_connection_query($query, 'activation_code');
  656. // ensure we found 1 code
  657. if (count($codes['list']) == 1) {
  658. $code = $codes['list'][0];
  659. // verify this was never used before
  660. if ($code['used'] == 0 && $code['name'] == '' && $code['granted'] == '') {
  661. $query = array('used' => REQUEST_TIME, 'name' => $GLOBALS['user']->name, 'granted' => str_replace('/', '', base_path()));
  662. // internal code id to query against path for update
  663. _cis_connection_object($code['acid'], 'activation_code', NULL, 'PUT', $query);
  664. // TRUE
  665. $output = ACTIVATION_CODE_ACCESS_GRANT;
  666. }
  667. // edge case where system revoked their access incorrectly
  668. // this helps improve integrity if a roster sync is inaccurate
  669. if ($code['used'] != 0 && $code['name'] == $GLOBALS['user']->name && $code['granted'] == str_replace('/', '', base_path())) {
  670. $output = ACTIVATION_CODE_ACCESS_GRANT;
  671. }
  672. else {
  673. // a different user already used this code
  674. if ($code['name'] != $GLOBALS['user']->name) {
  675. drupal_set_message(t('Another user already used this code! ECODE: REUSE'), 'error');
  676. }
  677. else {
  678. // user has tried to use a valid code again; in a different course
  679. drupal_set_message(t('This code has been used already to unlock access in another course! ECODE: OTHER'), 'error');
  680. }
  681. $output = ACTIVATION_CODE_ACCESS_DENY;
  682. }
  683. }
  684. else {
  685. $output = ACTIVATION_CODE_ACCESS_DENY;
  686. }
  687. break;
  688. }
  689. return $output;
  690. }
  691. /**
  692. * Wrapper for http requests to enable cached requests.
  693. *
  694. * @param $url
  695. * address to issue the request against.
  696. * @param $options
  697. * (optional) A series of httprl based query options
  698. * The most common one being blocking => FALSE
  699. * @param $bucket
  700. * (optional) The webservice machine name to issue the
  701. * command against. Defaults to cis for hub connection
  702. * but most of the time this should be defined as you
  703. * will want to access data from other tools in the network.
  704. * @param $cached
  705. * (optional) Whether or not to use a cached version of
  706. * the request. This defaults to TRUE though developers
  707. * will want to potentially issue requests that are never
  708. * cached. User 1 always recieves uncached requests to
  709. * avoid potential confusion while debugging.
  710. */
  711. function _cis_connector_request($url, $options = array(), $bucket = 'cis', $cached = TRUE) {
  712. $data = FALSE;
  713. // developers: allow for debug of all calls with fresh values
  714. // user 1 is always uncached
  715. if (CIS_CONNECTOR_DEVELOPMENT || ($GLOBALS['user']->uid == 1 && CIS_CONNECTOR_ROOT_USER_DEVELOPMENT)) {
  716. $cached = FALSE;
  717. }
  718. // trick to mash request into a single item
  719. $args = func_get_args();
  720. // options can be an array so need to implode on its own
  721. if (is_array($args[1])) {
  722. // headers can be a nested array
  723. if (isset($args[1]['headers']) && is_array($args[1]['headers'])) {
  724. $args[1]['headers'] = implode('_', $args[1]['headers']);
  725. }
  726. $args[1] = implode('_', $args[1]);
  727. }
  728. // append bucket type in case default is utilized
  729. if (!isset($args[2])) {
  730. $args[2] = $bucket;
  731. }
  732. // generate a unique call signature
  733. $call = __FUNCTION__ . implode('_', $args);
  734. // statically cache future calls
  735. $data = &drupal_static($call);
  736. if (!isset($data)) {
  737. // convert to something db friendly
  738. $salt = drupal_get_hash_salt();
  739. $cid = hash('sha512', $salt . $call);
  740. // @ignore druplart_conditional_assignment
  741. if ($cached && ($cache = cache_get($cid, 'cache_cis_connector'))) {
  742. $data = $cache;
  743. }
  744. else {
  745. // allow for direct http requests
  746. if ($bucket == 'none') {
  747. // queue request
  748. httprl_request($url, $options);
  749. // send the request off
  750. $tmp = httprl_send_request();
  751. $data = array_pop($tmp);
  752. }
  753. // look for settings for this bucket
  754. // @ignore druplart_conditional_assignment
  755. elseif ($settings = _cis_connector_build_registry($bucket)) {
  756. $address = _cis_connector_format_address($settings);
  757. // queue the request
  758. httprl_request($address . '/' . $url, $options);
  759. // send the request off
  760. $tmp = httprl_send_request();
  761. $data = array_pop($tmp);
  762. }
  763. else {
  764. drupal_set_message(t("Educational service registry missing or connection unavailable. This is not a requirement but would allow talking to the other ELMS distributions including CIS. See <a href='http://drupalcode.org/project/cis_connector.git/blob/refs/heads/7.x-1.x:/README.txt'>README.txt</a> for details on how to set this up, otherwise you can ignore this message and disable access to the pages that display it."), 'error', FALSE);
  765. return FALSE;
  766. }
  767. cache_set($cid, $data->data, 'cache_cis_connector');
  768. }
  769. }
  770. /*
  771. // debug all calls
  772. // @ignore production_code
  773. if (module_exists('devel') && $GLOBALS['user']->uid == 1) {
  774. // @ignore production_code
  775. dpm($data);
  776. // @ignore production_code
  777. }
  778. */
  779. return $data;
  780. }
  781. /**
  782. * Invalidates cached data relating to cis_connector.
  783. *
  784. * @param $cid
  785. * (optional) Cache ID of the record to clear from the private update module
  786. * cache. If empty, all records will be cleared from the table except fetch
  787. * tasks. Defaults to NULL.
  788. * @param $wildcard
  789. * (optional) If TRUE, cache IDs starting with $cid are deleted in addition to
  790. * the exact cache ID specified by $cid. Defaults to FALSE.
  791. */
  792. function _cis_connector_cache_clear($cid = NULL, $wildcard = FALSE) {
  793. if (empty($cid)) {
  794. cache_clear_all('*', 'cache_cis_connector', TRUE);
  795. }
  796. else {
  797. cache_clear_all($cid, 'cache_cis_connector', $wildcard);
  798. }
  799. }
  800. /**
  801. * Implements hook_flush_caches().
  802. */
  803. function cis_connector_flush_caches() {
  804. _cis_connector_cache_clear();
  805. return array();
  806. }
  807. /**
  808. * Implements hook_admin_menu_cache_info().
  809. */
  810. function cis_connector_admin_menu_cache_info() {
  811. $caches['cis_connector'] = array(
  812. 'title' => t('CIS data'),
  813. 'callback' => '_cis_connector_cache_clear',
  814. );
  815. return $caches;
  816. }
  817. /**
  818. * Implements hook_views_api().
  819. */
  820. function cis_connector_views_api() {
  821. return array(
  822. 'api' => 3,
  823. 'path' => drupal_get_path('module', 'cis_connector'),
  824. );
  825. }
  826. /**
  827. * Access callback for user roles.
  828. *
  829. * @param $roles
  830. * a list of roles to compare the current user against.
  831. * @return bolean
  832. * Whether or not the current user has this role.
  833. */
  834. function cis_connector_role_access($roles) {
  835. foreach ($roles as $role) {
  836. if (in_array($role, $GLOBALS['user']->roles)) {
  837. return TRUE;
  838. }
  839. }
  840. return FALSE;
  841. }
  842. /**
  843. * Prepare an entity for transmission to a webservice.
  844. *
  845. * This primary converts an entity object to an array.
  846. * @param $entity
  847. * any entity you wish to convert from object to array
  848. * with the intention of submitting to a remote site
  849. * for saving. This wipes values that are typical with
  850. * nodes but should work with any entity type.
  851. * @return $ary
  852. * The entity cleaned up and smashed into an array.
  853. */
  854. function _cis_connection_prepare_entity($entity) {
  855. $ary = (array) $entity;
  856. foreach ($ary as $key => $val) {
  857. // make sure it's not a key value
  858. if (!in_array($key, array('promote', 'revision', 'status', 'sticky', 'body', 'title', 'type', 'language'))) {
  859. if (strpos($key, 'field_') !== 0) {
  860. unset($ary[$key]);
  861. }
  862. elseif (is_array($val)) {
  863. // @todo support multiple field values
  864. if (isset($val['und'][0]['value'])) {
  865. $ary[$key] = $val['und'][0]['value'];
  866. }
  867. elseif (isset($val['und'][0]['url'])) {
  868. $ary[$key] = $val['und'][0];
  869. }
  870. else {
  871. // null case, remove it
  872. unset($ary[$key]);
  873. }
  874. }
  875. }
  876. elseif ($key == 'body') {
  877. $ary['body'] = $val['und'][0];
  878. }
  879. }
  880. return $ary;
  881. }
  882. /**
  883. * Collect all remote entities for sending off items to other buckets.
  884. *
  885. * @param $type
  886. * (optional) entity type to filter the list to.
  887. * @param $bundle
  888. * (optional) bundle within an entity type to filter to.
  889. * @return $remote
  890. * An array of all entity types and bundles that are
  891. * saved and created in this site but should also
  892. * be shipped off to another site via a web service
  893. * call.
  894. * @todo review if this is something we want to continue supporting.
  895. */
  896. function _cis_connector_remote_entities($type = NULL, $bundle = NULL) {
  897. $items = module_invoke_all('cis_connected_entity');
  898. drupal_alter('cis_connected_entity', $items);
  899. $remote = array();
  900. // allow for filtering just to a certain type
  901. if (!is_null($type)) {
  902. foreach ($items as $key => $item) {
  903. if ($item['type'] == $type) {
  904. // see if we should filter to bundles in this type
  905. if (!is_null($bundle)) {
  906. if ($item['bundle'] == $bundle) {
  907. $remote[$key] = $item;
  908. }
  909. }
  910. else {
  911. $remote[$key] = $item;
  912. }
  913. }
  914. }
  915. }
  916. else {
  917. $remote = $items;
  918. }
  919. return $remote;
  920. }
  921. /**
  922. * Implements hook_field_info_alter().
  923. */
  924. function cis_connector_field_info_alter(&$info) {
  925. // Add a setting to all field types
  926. foreach ($info as $field_type => $field_type_info) {
  927. $info[$field_type]['settings'] += array(
  928. 'cis_connector_access' => FALSE,
  929. 'cis_connector_disable' => FALSE,
  930. );
  931. }
  932. }
  933. /**
  934. * Implements hook_form_FORMID_alter().
  935. *
  936. * Adds settings for controlling how fields are
  937. * handled for remote entities.
  938. */
  939. function cis_connector_form_field_ui_field_edit_form_alter(&$form, &$form_state) {
  940. // check if this entity is allowed to utilize this method
  941. $rem = _cis_connector_remote_entities($form['instance']['entity_type']['#value'], $form['instance']['bundle']['#value']);
  942. // if we have at least 1 match, add the settings
  943. if (count($rem) != 0) {
  944. // allow for blocking the field display based on being remote
  945. $form['field']['settings']['cis_connector_access'] = array(
  946. '#type' => 'checkbox',
  947. '#title' => t('Hide field on remote instance'),
  948. '#default_value' => !empty($form['#field']['settings']['cis_connector_access']),
  949. '#description' => t('If checked, this field will be removed from the form when displayed on a remote site'),
  950. );
  951. // show field but disable it based on being remote
  952. $form['field']['settings']['cis_connector_disable'] = array(
  953. '#type' => 'checkbox',
  954. '#title' => t('Disable field on remote instance'),
  955. '#default_value' => !empty($form['#field']['settings']['cis_connector_disable']),
  956. '#description' => t('If checked, this field will be disabled but still visible on the form when displayed on a remote site'),
  957. );
  958. }
  959. }
  960. /**
  961. * Implements hook_entity_presave().
  962. *
  963. * @todo review this concept as it may be making
  964. * too many assumptions about how entity save works
  965. * when distributed across systems.
  966. */
  967. function cis_connector_entity_presave($entity, $type) {
  968. // only run this when it's our form deployed remotely
  969. $info = entity_get_info($type);
  970. // ensure we have bundle keys at least after loading info
  971. if (isset($info['bundle keys']['bundle']) && isset($entity->{$info['bundle keys']['bundle']})) {
  972. $rem = _cis_connector_remote_entities($type, $entity->{$info['bundle keys']['bundle']});
  973. // @todo this appears to be a glitch in RestWS
  974. // How could an item be submitted as annonymous if the user isn't?
  975. // don't allow annonymous submissions from outside system
  976. // this applies at the moment for service account based items
  977. // this is the only role able to post in this manner
  978. // but we verify this anyway based on role
  979. if (count($rem) > 0 && $entity->uid == 0 && in_array('SERVICE ACCOUNT', $GLOBALS['user']->roles)) {
  980. $entity->uid = $GLOBALS['user']->uid;
  981. }
  982. // if we have at least 1 match, add the settings, otherwise never runs
  983. foreach ($rem as $key => $item) {
  984. // make sure this isn't the current bucket we are working in
  985. if (!in_array(variable_get('install_profile', ''), $item['buckets'])) {
  986. // prepare the item for shipment
  987. $data = _cis_connection_prepare_entity($entity);
  988. // execute call
  989. foreach ($item['buckets'] as $bucket) {
  990. // post the formatted object to the other address
  991. $instance = '';
  992. $settings = _cis_connector_build_registry($bucket);
  993. if ($settings['instance']) {
  994. $instance = str_replace('/', '', base_path()) . '/';
  995. }
  996. $return = _cis_connection_object(NULL, $type, NULL, 'POST', $data, $bucket, $instance, FALSE, '', $item['options']);
  997. // if non-blocking this won't have anything it can do but still..
  998. drupal_alter('cis_remote_entities_insert', $return, $bucket);
  999. }
  1000. // our save mode told us to remove the current one
  1001. if ($item['save'] == CIS_CONNECTOR_ENTITY_REMOTE_ONLY) {
  1002. // try to wipe the entity and hope it doesn't cause an issue
  1003. $entity = NULL;
  1004. // @todo May need to issue drupal_goto to truly
  1005. // hijack the operation. look into running this hook
  1006. // last to avoid possible issues with other projects
  1007. }
  1008. }
  1009. }
  1010. }
  1011. }
  1012. /**
  1013. * Implements hook_form_alter().
  1014. */
  1015. function cis_connector_form_alter(&$form, &$form_state, $form_id) {
  1016. // only run this on entity forms
  1017. if (isset($form['#entity_type']) && isset($form['#bundle'])) {
  1018. // look for remote entities structured for this entity / bundle pair
  1019. $rem = _cis_connector_remote_entities($form['#entity_type'], $form['#bundle']);
  1020. // pop off the 1 we have as this is specific
  1021. $item = array_pop($rem);
  1022. // test for entity form and not in a bucket we write to
  1023. if ($form_id == $item['bundle'] . '_' . $item['type'] . '_' . 'form' && !in_array(variable_get('install_profile', ''), $item['buckets'])) {
  1024. // search fields on external instance and modify settings when needed
  1025. foreach ($form_state['field'] as $field_name => $field) {
  1026. if ($field['und']['field']['settings']['cis_connector_access']) {
  1027. // remove access to this field entirely
  1028. $form[$field_name]['#access'] = FALSE;
  1029. }
  1030. if ($field['und']['field']['settings']['cis_connector_disable']) {
  1031. // remove ability to edit to these field instances
  1032. $form[$field_name]['und']['#disabled'] = TRUE;
  1033. }
  1034. }
  1035. }
  1036. }
  1037. }
  1038. /**
  1039. * Simple mail function to send an email.
  1040. *
  1041. * This is a wrapper above drupal_mail_system() to
  1042. * simplify the process of sending an email as
  1043. * drupal's default mailer call is needlessly complex.
  1044. *
  1045. * @param $to
  1046. * Email address to send to.
  1047. * @param $message_subject
  1048. * Subject of the email.
  1049. * @param $message_body
  1050. * Message to send.
  1051. * @param $from
  1052. * Who to say it is from.
  1053. */
  1054. function _cis_connector_simple_mail($to, $message_subject, $message_body, $from) {
  1055. // These value can remain empty.
  1056. $my_module = 'cis';
  1057. $my_mail_token = 'simplemail';
  1058. $message = array(
  1059. 'id' => $my_module . '_' . $my_mail_token,
  1060. 'to' => $to,
  1061. 'subject' => $message_subject,
  1062. 'body' => array($message_body),
  1063. 'headers' => array(
  1064. 'From' => $from,
  1065. 'Sender' => $from,
  1066. 'Return-Path' => $from,
  1067. ),
  1068. );
  1069. $system = drupal_mail_system($my_module, $my_mail_token);
  1070. // The format function must be called before calling the mail function
  1071. $message = $system->format($message);
  1072. if ($system->mail($message)) {
  1073. // do something when successful
  1074. }
  1075. else {
  1076. // @todo do something if the mail fails
  1077. }
  1078. }
Error | ELMSLN API

Error

×

Error message

  • Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/elmsln_community/api.elmsln.org/includes/common.inc:2791) in drupal_send_headers() (line 1499 of /var/www/html/elmsln_community/api.elmsln.org/includes/bootstrap.inc).
  • Error: Call to undefined function apc_delete() in DrupalAPCCache->clear() (line 289 of /var/www/html/elmsln_community/api.elmsln.org/sites/all/modules/apc/drupal_apc_cache.inc).
The website encountered an unexpected error. Please try again later.