cis_helper.module

Workflow and functionality glue based on the structure of the CIS.

Functions

Namesort descending Description
cis_helper_cis_instructional_outlines_alter Implements hook_cis_instructional_outlines_alter().
cis_helper_cron Implements hook_cron().
cis_helper_draw_chart_alter Implements hook_draw_chart_alter().
cis_helper_entity_insert Implements hook_entity_insert().
cis_helper_entity_presave Implements hook_entity_presave().
cis_helper_entity_update Implements hook_entity_update().
cis_helper_form_alter Implements hook_form_alter().
cis_helper_get_data_values Helper to pull list of possible values from custom data tables.
cis_helper_httprl_spider_other_paths_alter Implements hook_httprl_spider_other_paths_alter(&$paths).
cis_helper_menu Implements hook_menu().
cis_helper_page_build Implements hook_page_build().
cis_helper_permission Implements hook_permission().
cis_helper_profiler_builder_drush_modules_list_alter Implements hook_profiler_builder_drush_modules_list_alter().
cis_helper_profiler_builder_ignore_alter Implements hook_profiler_builder_ignore_alter().
cis_helper_restws_response_alter Alter the outgoing response.
cis_helper_theme Implements hook_theme().
theme_cis_helper_checkbox_table Theme callback to render checkboxes as a table.
_cis_helper_add_service_instance Helper function to trigger cron based service creation.
_cis_helper_assemble_entity_list Helper function to generate options list of entities.
_cis_helper_derive_parent Helper function to return a section / offering's associated course
_cis_helper_derive_service_instances Derive service instances from an entity id.
_cis_helper_generate_service_node Callback to generate a service node.
_cis_helper_get_instructional_outlines Returns all instructional outlines that are xml packages
_cis_helper_restws_deep_load Callback to perform deep load on a base entity
_cis_helper_run_operation Callback to run operational logic for a remote call that was requested.

Constants

File

profiles/cis/modules/custom/cis_helper/cis_helper.module
View source
  1. <?php
  2. /**
  3. * @file
  4. * Workflow and functionality glue based on the structure of the CIS.
  5. */
  6. // default service to select for site creation.
  7. define('CIS_HELPER_COURSE_SERVICE', 'courses');
  8. // machine_name to indicate that service is external to the system.
  9. define('CIS_HELPER_EXTERNAL_FLAG', '--external--');
  10. // server location of the jobs path for files to be written to.
  11. define('CIS_HELPER_JOB_PATH', '/var/www/elmsln/config/jobs');
  12. // default authentication method of newly created courses / sites.
  13. define('CIS_HELPER_DEFAULT_AUTHENTICATION', 'open');
  14. // default instructional outline to import into mooc instance.
  15. define('CIS_HELPER_DEFAULT_CONTENT_OUTLINE', 'lesson-based.xml');
  16. /**
  17. * Implements hook_cron().
  18. */
  19. function cis_helper_cron() {
  20. $query = new EntityFieldQuery;
  21. $result = $query
  22. // pull all field collections
  23. ->entityCondition('entity_type', 'field_collection_item')
  24. // that are sections
  25. ->entityCondition('bundle', 'field_sections')
  26. // that aren't archived
  27. ->propertyCondition('archived', 0)
  28. // that have something in the cis_ops field
  29. ->fieldCondition('field_cis_ops', 'value', 'NULL', '!=')
  30. // run as user 1 to avoid issues of ability to load as cron is anon
  31. ->addMetaData('account', user_load(1))
  32. ->execute();
  33. // load the instances
  34. if (isset($result['field_collection_item'])) {
  35. $sections = entity_load('field_collection_item', array_keys($result['field_collection_item']));
  36. foreach ($sections as $section) {
  37. // handle the logic of the operation to perform
  38. _cis_helper_run_operation($section);
  39. }
  40. }
  41. }
  42. /**
  43. * Callback to run operational logic for a remote call that was requested.
  44. *
  45. * This allows for additional global operations to be written back to
  46. * the CIS from other places via the field_cis_ops field. 'set' is the
  47. * current allowed operation which allows other sites in the network
  48. * to define that a remote service should save a file locally and set
  49. * it to the field defined in the call's field.
  50. *
  51. * @param $section
  52. * fully loaded field_collection_item entity that has an operation to run.
  53. */
  54. function _cis_helper_run_operation($section) {
  55. $call = unserialize($section->field_cis_ops[LANGUAGE_NONE][0]['value']);
  56. switch ($call['op']) {
  57. // allow fields to be set and written to data files
  58. case 'set':
  59. // grab the semester, year and course
  60. $semester = $section->HostEntity()->field_semester[LANGUAGE_NONE][0]['value'];
  61. $year = $section->HostEntity()->field_year[LANGUAGE_NONE][0]['value'];
  62. $course_name = $section->HostEntity()->HostEntity()->field_machine_name[LANGUAGE_NONE][0]['value'];
  63. // build registry for this setting
  64. $settings = _cis_connector_build_registry($call['bucket']);
  65. // build the address to do a secure call over webservices
  66. $address = _cis_connector_format_address($settings, '/' . $course_name);
  67. // execute the request
  68. httprl_request($address . '/' . $call['url']);
  69. $request = httprl_send_request();
  70. // pull out the data from this call
  71. $xml = array_pop($request)->data;
  72. // load the structure of the field so we can get the storage location
  73. $field = field_info_field('field_course_archive');
  74. // get the instance settings so we can get the directory
  75. $instance = field_info_instance('field_collection_item', 'field_course_archive', 'field_sections');
  76. // file path
  77. $file_base = $field['settings']['uri_scheme'] . '://';
  78. if (isset($instance['settings']['file_directory']) && !empty($instance['settings']['file_directory'])) {
  79. $file_base .= $instance['settings']['file_directory'] . '/';
  80. }
  81. $file_path = $file_base . $course_name . '_' . $semester . '_' . $year . '.xml';
  82. // test that we can actually save the file
  83. if ($file = file_save_data($xml, $file_path)) {
  84. // Change status to permanent so it isn't killed on cron clean up
  85. $file->status = FILE_STATUS_PERMANENT;
  86. // bulk job so set uid to 1 since it's site ownership and non anon
  87. $file->uid = 1;
  88. $file->filename = str_replace($file_base, '', $file->uri);
  89. $file->display = 1;
  90. $file->description = '';
  91. $file->focus_rect = '';
  92. $file->crop_rect = '';
  93. $file->rdf_mapping = array();
  94. // save file
  95. file_save($file);
  96. // silly typecast of file object to array for field happiness
  97. $file_ary = (array)$file;
  98. // set the section's file field to match file array
  99. $section->{$call['field']}[LANGUAGE_NONE][0] = $file_ary;
  100. // clear the operation field so this doesn't run next time
  101. $section->field_cis_ops[LANGUAGE_NONE][0]['value'] = NULL;
  102. // save the section
  103. entity_save('field_collection_item', $section);
  104. }
  105. break;
  106. }
  107. }
  108. /**
  109. * Implements hook_page_build().
  110. */
  111. function cis_helper_page_build(&$page) {
  112. drupal_add_css(drupal_get_path('module', 'cis_helper') . '/css/cis_helper.css');
  113. $node = menu_get_object();
  114. if (arg(0) == 'cis-add-offering') {
  115. drupal_add_css(drupal_get_path('module', 'cis_helper') . '/css/cis_helper_add_offering_page.css');
  116. }
  117. elseif (arg(0) == 'cis-quick-setup') {
  118. // special js for hiding / displaying groups of form fields
  119. drupal_add_js(drupal_get_path('module', 'cis_helper') . '/js/cis_helper.js');
  120. }
  121. elseif (isset($node->type)) {
  122. // only apply these to course pages
  123. if (in_array($node->type, array('person', 'program', 'news', 'course')) && isset($node->field_academic_home[LANGUAGE_NONE])) {
  124. $unit = node_load($node->field_academic_home[LANGUAGE_NONE][0]['target_id']);
  125. // ensure we have a color for a unit
  126. if (isset($unit->field_color[LANGUAGE_NONE])) {
  127. $color = $unit->field_color[LANGUAGE_NONE][0]['jquery_colorpicker'];
  128. $css = '
  129. .unit-color-border li.active,
  130. .unit-color-border li.active-trail,
  131. .unit-color-border {
  132. border-color: #' . $color . ' !important;
  133. }
  134. .unit-color, .amp, .unit-color a {
  135. color: #' . $color . ' !important;
  136. }
  137. .unit-bg-color {
  138. background-color: #' . $color . ' !important;
  139. }
  140. ::-moz-selection {
  141. background-color: #' . $color . ';
  142. }
  143. ::selection {
  144. background-color: #' . $color . ';
  145. }';
  146. // make sure it's added very late so it takes priority, even with important
  147. $options = array(
  148. 'type' => 'inline',
  149. 'group' => CSS_THEME,
  150. 'weight' => 100,
  151. 'preprocess' => FALSE,
  152. );
  153. drupal_add_css($css, $options);
  154. }
  155. }
  156. }
  157. // build shortcuts to create other services, excluding current ones
  158. if (arg(2) == 'service-instances' && is_numeric(arg(1))) {
  159. $key = array('field_services', 'target_id');
  160. $fields = array('field_course' => array('target_id', arg(1)));
  161. // build a list of current services
  162. $current = _cis_helper_assemble_entity_list('node', 'service_instance', $key, 'title', $fields);
  163. $services = _cis_helper_assemble_entity_list('node', 'service', 'nid', 'title');
  164. foreach ($current as $k => $t) {
  165. unset($services[$k]);
  166. }
  167. $links = '<div class="cis-create-additional-services">' . "\n<ul>\n";
  168. foreach ($services as $snid => $name) {
  169. $options = array(
  170. 'query' => array(
  171. 'field_services' => $snid,
  172. 'field_course' => arg(1),
  173. 'destination' => current_path(),
  174. )
  175. );
  176. $links .= '<li>' . l(t('Add `@service` service', array('@service' => $name)), 'node/add/service-instance', $options) . "</li>\n";
  177. }
  178. $links .= "\n</ul>\n</div>";
  179. $page['content']['system_main']['cis-links'] = array(
  180. '#markup' => $links,
  181. '#weight' => 1,
  182. );
  183. }
  184. }
  185. /**
  186. * Implements hook_theme().
  187. */
  188. function cis_helper_theme($existing, $type, $theme, $path) {
  189. return array(
  190. 'cis_helper_checkbox_table' => array(
  191. 'render element' => 'element',
  192. ),
  193. );
  194. }
  195. /**
  196. * Theme callback to render checkboxes as a table.
  197. *
  198. * @param $variables
  199. * an element with checkboxes which will be rendered as rows.
  200. */
  201. function theme_cis_helper_checkbox_table($variables) {
  202. $element = $variables['element'];
  203. $rows = array();
  204. $row = array();
  205. foreach (element_children($element) as $n => $key) {
  206. $row[] = drupal_render($element[$key]);
  207. // 5 can be replaced by number of checkboxes you want in one row.
  208. if ($n+1 == count($element['#options']) || ($n+1) % 5 == 0) {
  209. $rows[] = $row;
  210. $row = array();
  211. }
  212. }
  213. return theme('table', array('header' => array(), 'rows' => $rows, 'attributes' => array('class' => array('cis_course_table_list'))));
  214. }
  215. /**
  216. * Implements hook_menu().
  217. */
  218. function cis_helper_menu() {
  219. $items = array();
  220. $path = drupal_get_path('module', 'cis_helper');
  221. // admin settings for CIS
  222. $items['admin/config/system/cis'] = array(
  223. 'title' => 'CIS',
  224. 'description' => 'Course information system configuration',
  225. 'page callback' => 'drupal_get_form',
  226. 'page arguments' => array('cis_helper_admin_settings'),
  227. 'access arguments' => array('administer site configuration'),
  228. 'type' => MENU_NORMAL_ITEM,
  229. 'file' => 'cis_helper.admin.inc',
  230. 'file path' => $path,
  231. );
  232. // quick setup form
  233. $items['cis-quick-setup'] = array(
  234. 'title' => 'System setup',
  235. 'description' => 'Setup a new course and plug it into the network of tools',
  236. 'page callback' => 'drupal_get_form',
  237. 'page arguments' => array('cis_helper_quick_setup_page'),
  238. 'access arguments' => array('access cis setup form'),
  239. 'type' => MENU_NORMAL_ITEM,
  240. 'file' => 'cis_helper.pages.inc',
  241. 'file path' => $path,
  242. );
  243. // quick setup form from a string
  244. $items['cis-quick-setup-access-id'] = array(
  245. 'title' => 'Setup course access',
  246. 'description' => 'Setup access to a space based on having the unique string.',
  247. 'page callback' => 'drupal_get_form',
  248. 'page arguments' => array('cis_helper_quick_setup_access_id_page'),
  249. 'access arguments' => array('access cis setup form'),
  250. 'type' => MENU_NORMAL_ITEM,
  251. 'file' => 'cis_helper.pages.inc',
  252. 'file path' => $path,
  253. );
  254. // add offering form
  255. $items['cis-add-offering'] = array(
  256. 'title' => 'Add course offering',
  257. 'description' => 'Setup an offering / semester for an existing course',
  258. 'page callback' => 'drupal_get_form',
  259. 'page arguments' => array('cis_helper_add_offering_page'),
  260. 'access arguments' => array('access cis setup form'),
  261. 'type' => MENU_NORMAL_ITEM,
  262. 'file' => 'cis_helper.pages.inc',
  263. 'file path' => $path,
  264. );
  265. // quick setup form for planning lots of sections at once
  266. $items['cis/offerings/planning'] = array(
  267. 'title' => 'Planning',
  268. 'weight' => 0,
  269. 'description' => 'Plan what courses are going to be offered in the future',
  270. 'page callback' => 'drupal_get_form',
  271. 'page arguments' => array('cis_helper_offering_planning_page'),
  272. 'access arguments' => array('access cis setup form'),
  273. 'type' => MENU_LOCAL_TASK,
  274. 'file' => 'cis_helper.pages.inc',
  275. 'file path' => $path,
  276. );
  277. return $items;
  278. }
  279. /**
  280. * Implements hook_permission().
  281. */
  282. function cis_helper_permission() {
  283. return array(
  284. 'access cis setup form' => array(
  285. 'title' => t('Access cis quick setup form'),
  286. ),
  287. 'cis setup admin options' => array(
  288. 'title' => t('Access administrative options on setup form'),
  289. ),
  290. );
  291. }
  292. /**
  293. * Implements hook_draw_chart_alter().
  294. */
  295. function cis_helper_draw_chart_alter(&$settings) {
  296. $dept_charts = array('historical-college-data-page-4', 'historical-college-data-page-6');
  297. foreach ($dept_charts as $view_name) {
  298. if (isset($settings['chart'][$view_name])) {
  299. foreach ($settings['chart'][$view_name]['header'] as $record) {
  300. $data = db_select('field_data_field_abbreviation', 'fdfa')
  301. ->fields('fdfa', array('entity_id'))
  302. ->condition('field_abbreviation_value', substr($record, 0, strpos($record, ' (')))
  303. ->execute()
  304. ->fetchObject();
  305. if (isset($data->entity_id)) {
  306. $dept = node_load($data->entity_id);
  307. if (isset($dept->field_color[LANGUAGE_NONE][0]['jquery_colorpicker'])) {
  308. $colors[] = $dept->field_color[LANGUAGE_NONE][0]['jquery_colorpicker'];
  309. }
  310. }
  311. }
  312. $settings['chart'][$view_name]['options']['colors'] = $colors;
  313. }
  314. }
  315. }
  316. /**
  317. * Implements hook_form_alter().
  318. */
  319. function cis_helper_form_alter(&$form, $form_state, $form_id) {
  320. // courses overview view / display
  321. if ($form_id == 'views_exposed_form' && arg(0) == 'courses' && $form_state['view']->name == 'courses_overview') {
  322. // add nice html5 placeholders
  323. $form['title']['#placeholder'] = t('Find a course');
  324. $form['title_1']['#placeholder'] = t('Enter a number');
  325. // course_num container
  326. $form['course_num'] = array(
  327. '#type' => 'fieldset',
  328. '#title' => t('Course number'),
  329. '#collapsible' => TRUE,
  330. '#collapsed' => TRUE,
  331. '#weight' => 3,
  332. );
  333. $form['course_num']['title_1'] = $form['title_1'];
  334. unset($form['title_1']);
  335. }
  336. // faculty overview view / display
  337. if ($form_id == 'views_exposed_form' && arg(0) == 'faculty' && $form_state['view']->name == 'cis_faculty') {
  338. // add nice html5 placeholders
  339. $form['field_display_name_value']['#placeholder'] = t('Search for faculty');
  340. }
  341. // add quick link to add offering to the list of offerings
  342. if ($form_id == 'views_exposed_form' && arg(2) == 'offerings') {
  343. $form['cle_add_new']['#markup'] = l(t('Add new offering'), 'field-collection/field-offerings/add/node/' . arg(1), array('query' => array('destination' => 'node/' . arg(1) . '/offerings')));
  344. $form['cle_add_new']['#weight'] = 100;
  345. }
  346. // alterations for the section field collection
  347. if ($form_id == 'field_collection_item_form' && isset($form['#entity']) && $form['#entity']->field_name == 'field_sections') {
  348. // return all service instances this section will have access to
  349. $service_instances = _cis_helper_derive_service_instances($form['#entity']->item_id, $form['#entity']->HostEntity()->HostEntity());
  350. // we need to replace the service options based on those that exist
  351. $service_options = array('_none' => t('- None -'));
  352. // load each service related to the instances found
  353. if (is_array($service_instances)) {
  354. foreach ($service_instances as $instance) {
  355. $service = node_load($instance->field_services[LANGUAGE_NONE][0]['target_id']);
  356. // collect array of known services available to this course via instances
  357. $service_options[$service->nid] = $service->title;
  358. }
  359. }
  360. // set the allowed options to those found above
  361. $form['field_services'][LANGUAGE_NONE]['#options'] = $service_options;
  362. }
  363. // allow for select style output for fields in data table
  364. if ($form_id == 'views_exposed_form' && strpos($form['#id'], 'historical-college-data-')) {
  365. $form['location'] = array(
  366. '#type' => 'select',
  367. '#options' => cis_helper_get_data_values('location'),
  368. );
  369. $form['semester'] = array(
  370. '#type' => 'select',
  371. '#options' => cis_helper_get_data_values('semester'),
  372. );
  373. $form['year'] = array(
  374. '#type' => 'select',
  375. '#options' => cis_helper_get_data_values('year'),
  376. );
  377. $form['course'] = array(
  378. '#type' => 'select',
  379. '#options' => cis_helper_get_data_values('course'),
  380. );
  381. }
  382. }
  383. /**
  384. * Implements hook_entity_presave().
  385. */
  386. function cis_helper_entity_presave($entity, $type) {
  387. // check to see if a service_instance is being saved
  388. if ($type == 'node' && $entity->type == 'service_instance') {
  389. // force title to match pattern, ant may miss this
  390. $serv_node = node_load($entity->field_services[LANGUAGE_NONE][0]['target_id']);
  391. // load the course node
  392. $course_node = node_load($entity->field_course[LANGUAGE_NONE][0]['target_id']);
  393. $entity->title = $course_node->field_machine_name[LANGUAGE_NONE][0]['value'] . ' - ' . $serv_node->field_machine_name[LANGUAGE_NONE][0]['value'];
  394. }
  395. }
  396. /**
  397. * Implements hook_entity_insert().
  398. */
  399. function cis_helper_entity_insert($entity, $type) {
  400. // run the same code as entity_update as that fires for sections
  401. cis_helper_entity_update($entity, $type);
  402. // check to see if a service_instance is being inserted
  403. if ($type == 'node' && $entity->type == 'service_instance') {
  404. // set the entity reference field to course
  405. global $user;
  406. // generally a bad idea to pass around the user global
  407. $account = $user;
  408. // only run if we have services to process
  409. if (!empty($entity->field_services[LANGUAGE_NONE])) {
  410. $serv_node = node_load($entity->field_services[LANGUAGE_NONE][0]['target_id']);
  411. $course_node = node_load($entity->field_course[LANGUAGE_NONE][0]['target_id']);
  412. // get current college machine name
  413. $college = variable_get('cis_college_name', '');
  414. // make sure this isn't marked external
  415. if ($serv_node->field_machine_name[LANGUAGE_NONE][0]['value'] != CIS_HELPER_EXTERNAL_FLAG) {
  416. _cis_helper_add_service_instance($course_node, $serv_node, $entity, $college, $account);
  417. }
  418. }
  419. }
  420. }
  421. /**
  422. * Implements hook_entity_update().
  423. */
  424. function cis_helper_entity_update($entity, $type) {
  425. // spider crons of associated services when section is updated
  426. if ($type == 'field_collection_item' && $entity->field_name == 'field_sections') {
  427. // load possible services
  428. $service_instances = _cis_helper_derive_service_instances($entity->item_id);
  429. // cross-reference with those this section is reporting it implements
  430. // this avoids possible cron runs of services this section isn't using
  431. // overall it's not a big deal but this saves cycles none-the-less
  432. $use_array = array();
  433. if (!empty($entity->field_services)) {
  434. foreach ($entity->field_services[LANGUAGE_NONE] as $in_use) {
  435. $use_array[$in_use['target_id']] = $in_use['target_id'];
  436. }
  437. }
  438. if (is_array($service_instances)) {
  439. foreach ($service_instances as $instance) {
  440. // check that this instance of a service is in use by this section
  441. if (in_array($instance->field_services[LANGUAGE_NONE][0]['target_id'], $use_array)) {
  442. // load the service and course entities
  443. $service = entity_load_single('node', $instance->field_services[LANGUAGE_NONE][0]['target_id']);
  444. $course = entity_load_single('node', $instance->field_course[LANGUAGE_NONE][0]['target_id']);
  445. // create the location of this service instance cron
  446. $location = $service->field_location[LANGUAGE_NONE][0]['url'] . '/' . $course->field_machine_name[LANGUAGE_NONE][0]['safe_value'];
  447. // add on the cron and cron_key values for a secure remote run
  448. $data = array(
  449. 'cron_key' => (isset($instance->field_cron_key[LANGUAGE_NONE]) ? $instance->field_cron_key[LANGUAGE_NONE][0]['value'] : ''),
  450. );
  451. $location .= '/cron.php?' . http_build_query($data);
  452. // call the cron via remote call
  453. $options = array(
  454. 'blocking' => FALSE,
  455. );
  456. // que request
  457. httprl_request($location, $options);
  458. drupal_set_message(t('The remote service `@service` has been syncronized to reflect your changes.', array('@service' => $service->title)), 'status');
  459. }
  460. }
  461. // Execute requests in the queue in a non-blocking manner (really fast)
  462. $request = httprl_send_request();
  463. }
  464. }
  465. }
  466. /**
  467. * Helper to pull list of possible values from custom data tables.
  468. */
  469. function cis_helper_get_data_values($column) {
  470. // pull from historical course data table
  471. $result = db_select('historical_course_data', 'hcd')
  472. ->fields('hcd', array($column))
  473. ->orderBy($column)
  474. ->distinct()
  475. ->execute();
  476. // build default null option for empty text
  477. $options = array('' => '');
  478. // biuld array
  479. foreach ($result as $item) {
  480. $options[$item->{$column}] = $item->{$column};
  481. }
  482. return $options;
  483. }
  484. /**
  485. * Helper function to trigger cron based service creation.
  486. *
  487. * This is the main function that allows the CIS distro to
  488. * spin up new sites of other distribution types. The contains
  489. * all logic and general structure for creating the file pattern
  490. * needed to be picked up by the drush-create-site process running
  491. * on the server.
  492. *
  493. * @param $course
  494. * fully loaded course node.
  495. * @param $service
  496. * fully loaded service node.
  497. * @param $service_instance
  498. * fully loaded service_instance node
  499. * @param $college
  500. * abreviation for a college, pulled from variables table.
  501. * @param $account
  502. * fully loaded user account that requested this be built.
  503. * @return NULL
  504. * This returns nothing but does write a file to server to build a site.
  505. * @see cis_helper_entity_insert()
  506. * @see hook_service_instance_options_alter()
  507. */
  508. function _cis_helper_add_service_instance($course, $service, $service_instance, $college, $account) {
  509. $options = array();
  510. // allow for advanced drush alterations via code
  511. drupal_alter('cis_service_instance_options', $options, $course, $service);
  512. // allow for tool machine name specific alterations
  513. drupal_alter('cis_service_instance_' . $service->field_machine_name[LANGUAGE_NONE][0]['value'] . '_options', $options, $course, $service);
  514. // vset the uuid from the service_instance entity into the remote site
  515. // this effectively binds the remote drupal site to the service_instance
  516. // content type in CIS
  517. $options['vset'][] = 'cis_service_instance_uuid ' . $service_instance->uuid;
  518. // load optional cis_helper defined values
  519. $extra_options = variable_get('cis_build_' . $course->field_method_of_access[LANGUAGE_NONE][0]['value'], '');
  520. if (!empty($extra_options)) {
  521. $extra_options = explode(',', $extra_options);
  522. // only allow for enabling options from system variable
  523. foreach ($extra_options as $eoption) {
  524. $options['en'][] = $eoption;
  525. }
  526. }
  527. // build space name based on the requested title
  528. $space_name = $course->field_machine_name[LANGUAGE_NONE][0]['value'];
  529. // create a file based on service name
  530. $filename = $space_name . '.' . $service->field_machine_name[LANGUAGE_NONE][0]['value'];
  531. // set title to name of course
  532. $site_title = str_replace('&', 'and', $course->title);
  533. // formulate the pattern of the file to what the job wants
  534. // site symlink to build
  535. $content = $space_name . "\n";
  536. // college requesting for dbo / db name
  537. $content .= $college . "\n";
  538. // domain this is written to
  539. $content .= str_replace('http://', '', str_replace('https://', '', $service->field_location[LANGUAGE_NONE][0]['url'])) . "\n";
  540. // domain data can be accessed on
  541. $content .= str_replace('http://', '', str_replace('https://', '', $service->field_data_location[LANGUAGE_NONE][0]['url'])) . "\n";
  542. // title to set for the site name value
  543. $content .= $site_title . "\n";
  544. // slogan value for the course
  545. $content .= str_replace('&', 'and', $course->field_course_title[LANGUAGE_NONE][0]['value']) . "\n";
  546. // email / user requesting account
  547. $content .= $account->mail . "\n";
  548. // add distribution to create
  549. $content .= $service->field_distribution[LANGUAGE_NONE][0]['value'] . "\n";
  550. // process potential drush operation lists
  551. $fr = '';
  552. $un = '';
  553. foreach ($options as $op => $commands) {
  554. // write each command to the file for this operation
  555. $tmp = '';
  556. // account for drush commands that we can smash into 1 line for performance
  557. switch ($op) {
  558. case 'en':
  559. case 'dis':
  560. case 'fr':
  561. case 'pm-uninstall':
  562. $tmp .= 'drush ' . $op . ' ' . implode(' ', $commands) . "\n";
  563. break;
  564. case 'urol':
  565. foreach ($commands as $command_role => $command_users) {
  566. $tmp .= 'drush urol ' . $command_role . ' ' . implode(',', $command_users) . "\n";
  567. }
  568. break;
  569. default:
  570. foreach ($commands as $command) {
  571. $tmp .= 'drush ' . $op . ' ' . $command . "\n";
  572. }
  573. break;
  574. }
  575. // hold off on fr to after all other operations
  576. if ($op == 'fr') {
  577. $fr = $tmp;
  578. }
  579. // same, hold off on uninstall
  580. elseif ($op == 'pm-uninstall') {
  581. $un = $tmp;
  582. }
  583. else {
  584. $content .= $tmp;
  585. }
  586. }
  587. // push feature revert requests off til the end
  588. // this way they actually revert instead of vset / en disrupting them
  589. $content .= $fr . $un;
  590. // only apply default content import for course based service
  591. if ($service->field_machine_name[LANGUAGE_NONE][0]['value'] == CIS_HELPER_COURSE_SERVICE && !empty($service_instance->field_service_data[LANGUAGE_NONE][0]['value'])) {
  592. // allow for on creation population of material
  593. $content .= 'drush feeds-import feeds_node_helper_book_import --file=' . $service_instance->field_service_data[LANGUAGE_NONE][0]['value'] . "\n";
  594. }
  595. // run the cron as a final clean up mechanism
  596. $content .= 'drush cron' . "\n";
  597. // run ecl to help seed caches for all entities
  598. $content .= 'drush ecl' . "\n";
  599. // may seem odd but double running cron can prime syncing
  600. $content .= 'drush cron' . "\n";
  601. // write the structured file to a lower directory
  602. $handle = fopen(variable_get('cis_job_path', CIS_HELPER_JOB_PATH) . '/' . $filename, 'x+');
  603. fwrite($handle, $content);
  604. fclose($handle);
  605. // tell user about the service
  606. drupal_set_message(t('The "@service" service will be asked to create @space momentarily. You will be emailed when the job is complete.', array('@service' => $service->title, '@space' => $space_name)), 'status');
  607. // log service creation
  608. watchdog('cis_service', 'Service ' . $service->title . ' was told to be built for ' . $space_name);
  609. }
  610. /**
  611. * Helper function to return a section / offering's associated course
  612. *
  613. * Wrapper for hunting for hostEntities from an origin entity. This is
  614. * useful because of the structure of the course node being.
  615. * -- course
  616. * ---- offering(s)
  617. * ------ section(s)
  618. * This allows for easy selection of an offering from a section,
  619. * a course from a section or a course from an offering as they
  620. * are a specialized entity reference otherwise due to how field_collection
  621. * structures related data.
  622. *
  623. * @param $item_id
  624. * field_collection_item id to search from.
  625. * @param $field_collection_type
  626. * type of field collection to search from, section or offering possible.
  627. * @param $find
  628. * item to derive from the current item, defaults to course.
  629. */
  630. function _cis_helper_derive_parent($item_id, $field_collection_type = 'section', $find = 'course') {
  631. // additional level of searching for a section that matches
  632. if ($field_collection_type == 'section') {
  633. $section = entity_load_single('field_collection_item', $item_id);
  634. $offering = $section->HostEntity();
  635. }
  636. else {
  637. $offering = entity_load_single('field_collection_item', $item_id);
  638. }
  639. // allow for stopping here if we wanted offering
  640. if ($find == 'offering') {
  641. return $offering;
  642. }
  643. // make sure we have something as an offering, edge case
  644. if (empty($offering)) {
  645. return FALSE;
  646. }
  647. // make sure we have a course nid set
  648. if (isset($offering->HostEntity()->nid)) {
  649. return $offering->HostEntity();
  650. }
  651. return FALSE;
  652. }
  653. /**
  654. * Returns all instructional outlines that are xml packages
  655. *
  656. * @return $options
  657. * array of options provided by external alter hooks being used.
  658. */
  659. function _cis_helper_get_instructional_outlines() {
  660. $options = array();
  661. // allow for the inclusion of other instructional models
  662. drupal_alter('cis_instructional_outlines', $options);
  663. return $options;
  664. }
  665. /**
  666. * Implements hook_cis_instructional_outlines_alter().
  667. *
  668. * This is CIS's default implementation of the outline list so
  669. * that the outlines that come packaged with CIS are made available
  670. * by default. Also makes it easier to add other things in down the
  671. * road by doing it this way from the start.
  672. *
  673. * @param $outlines
  674. * an array of options as outline location => name
  675. */
  676. function cis_helper_cis_instructional_outlines_alter(&$outlines) {
  677. // lazy scan the included instructional models
  678. $directory = opendir(drupal_get_path('module', 'cis_helper') . '/instructional_models');
  679. $full_path = drupal_get_path('module', 'cis_helper') . '/instructional_models/';
  680. // get each item
  681. $files = array();
  682. while ($file_name = readdir($directory)) {
  683. if (strpos($file_name, '.xml')) {
  684. // add the template to the list
  685. $files[$full_path . $file_name] = t('@name Instructional Outline', array('@name' => ucwords(str_replace('-', ' ', str_replace('.xml', '', $file_name)))));
  686. }
  687. }
  688. // close directory
  689. closedir($directory);
  690. // merge the located file outlines in with those present in the site
  691. $outlines = array_merge($outlines, $files);
  692. }
  693. /**
  694. * Derive service instances from an entity id.
  695. *
  696. * Used to figure out what services a node/collection has access to.
  697. *
  698. * @param $item_id
  699. * field_collection_item's item_id or a course nid
  700. * @param $course
  701. * course object to check against, defaults to null to support lookup.
  702. * @param $type
  703. * The type associated with this id since it can be field collection or node.
  704. *
  705. * @return array
  706. * an array fully loaded service_instance nodes, potentially many.
  707. */
  708. function _cis_helper_derive_service_instances($item_id, $course = NULL, $type = 'section') {
  709. // load course correctly
  710. if (is_null($course)) {
  711. switch ($type) {
  712. case 'section':
  713. case 'offering':
  714. // find related course
  715. $course = _cis_helper_derive_parent($item_id, $type);
  716. break;
  717. case 'course':
  718. $course = node_load($item_id);
  719. break;
  720. // unsupported operation
  721. default:
  722. return FALSE;
  723. break;
  724. }
  725. }
  726. if ($course == NULL) {
  727. return FALSE;
  728. }
  729. // query to return all service instances for this course
  730. $query = new EntityFieldQuery;
  731. $result = $query
  732. // pull all nodes
  733. ->entityCondition('entity_type', 'node')
  734. // that are service instances
  735. ->entityCondition('bundle', 'service_instance')
  736. // where the course equals the current one
  737. ->fieldCondition('field_course', 'target_id', $course->nid, '=')
  738. // run the query as user 1
  739. ->addMetaData('account', user_load(1))
  740. ->execute();
  741. // load the instances
  742. if (isset($result['node'])) {
  743. return entity_load('node', array_keys($result['node']));
  744. }
  745. return FALSE;
  746. }
  747. /**
  748. * Implements hook_profiler_builder_ignore_alter().
  749. */
  750. function cis_helper_profiler_builder_ignore_alter(&$ignore) {
  751. // remove cis specific variables from build
  752. $ignore[] = 'cis_build_code';
  753. $ignore[] = 'cis_build_lms';
  754. $ignore[] = 'cis_build_authenticated';
  755. $ignore[] = 'cis_build_open';
  756. $ignore[] = 'cis_job_path';
  757. $ignore[] = 'cis_college_name';
  758. }
  759. /**
  760. * Implements hook_profiler_builder_drush_modules_list_alter().
  761. */
  762. function cis_helper_profiler_builder_drush_modules_list_alter(&$project_data, $machine_name) {
  763. // this module is included with the package as it was a sandbox
  764. unset($project_data['Views']['views_field_calc']);
  765. }
  766. /**
  767. * Alter the outgoing response.
  768. *
  769. * @param mixed $response
  770. * The response data being returned by the REST service (not yet serialized).
  771. * @param string $function
  772. * The function being called on the REST service.
  773. * @param string $format_name
  774. * The name of the format serializing the response.
  775. *
  776. * related issue https://drupal.org/node/2024603
  777. */
  778. function cis_helper_restws_response_alter(&$response, $function, $format_name) {
  779. // specific modifications based common request type
  780. if (($function == 'viewResource' || $function == 'queryResource') && $format_name == 'json' && isset($_GET['deep-load-refs'])) {
  781. // query response is nested, direct access is not
  782. if ($function == 'queryResource') {
  783. foreach ($response['list'] as &$object) {
  784. _cis_helper_restws_deep_load($object);
  785. }
  786. }
  787. else {
  788. _cis_helper_restws_deep_load($response);
  789. }
  790. }
  791. }
  792. /**
  793. * Callback to perform deep load on a base entity
  794. *
  795. * @param $response
  796. * response about to be sent back for the RestWS query
  797. * @return NULL
  798. * this will load additional items into the response, filtering their
  799. * fields based on account access to the deep loaded items.
  800. */
  801. function _cis_helper_restws_deep_load(&$response) {
  802. // build list of allowed entity types to deep pull down
  803. $allowed = explode(',', $_GET['deep-load-refs']);
  804. // allow for deep loading of resources
  805. foreach ($response as $key => &$val) {
  806. // check for a single resource verses many
  807. if (is_array($val) && isset($val[0]['id'])) {
  808. // loop through items loading them in
  809. foreach ($val as &$item) {
  810. if (in_array($item['resource'], $allowed)) {
  811. // load the entity
  812. $entity = entity_load_single($item['resource'], $item['id']);
  813. // ensure they can view this specific item
  814. if (entity_access('view', $item['resource'], $entity)) {
  815. // create a meta wrapper to act on for entity
  816. $wrapper = entity_metadata_wrapper($item['resource'], $entity);
  817. // filter out these values
  818. $wrap = restws_property_access_filter($wrapper);
  819. $eary = (array) $entity;
  820. foreach ($eary as $property => $value) {
  821. // value needs to be removed as it didn't pass wrapper validation
  822. if (!isset($wrap[$property])) {
  823. unset($eary[$property]);
  824. }
  825. }
  826. // CIS SPECIFIC
  827. // edge-case unique to cis with nested field collections
  828. // the rest of this code is generalized to work with anything
  829. if (isset($eary['field_sections'][LANGUAGE_NONE]) && count($eary['field_sections'][LANGUAGE_NONE]) > 0) {
  830. // perform deep load of sections
  831. foreach ($eary['field_sections'][LANGUAGE_NONE] as &$section) {
  832. if ($section['value'] != NULL) {
  833. $section_entity = entity_load_single($item['resource'], $section['value']);
  834. // ensure they can view this specific item
  835. if (entity_access('view', $item['resource'], $section_entity)) {
  836. // create a meta wrapper to act on for entity
  837. $section_wrapper = entity_metadata_wrapper($item['resource'], $section_entity);
  838. // filter out these values
  839. $sec_wrap = restws_property_access_filter($section_wrapper);
  840. $sec_eary = (array) $section_entity;
  841. foreach ($sec_eary as $sec_property => $sec_value) {
  842. // value needs to be removed as it didn't pass wrapper validation
  843. if (!isset($sec_wrap[$sec_property])) {
  844. unset($sec_eary[$sec_property]);
  845. }
  846. }
  847. $section = $sec_eary;
  848. }
  849. }
  850. }
  851. }
  852. // add values based on wrapper passing correctly
  853. $item = $eary;
  854. }
  855. }
  856. }
  857. }
  858. elseif (is_array($val) && isset($val['id'])) {
  859. if (in_array($val['resource'], $allowed)) {
  860. // load the entity
  861. $entity = entity_load_single($val['resource'], $val['id']);
  862. // ensure they can view this specific item
  863. if (entity_access('view', $val['resource'], $entity)) {
  864. // create a meta wrapper to act on for entity
  865. $wrapper = entity_metadata_wrapper($val['resource'], $entity);
  866. // filter out fields
  867. $wrap = restws_property_access_filter($wrapper);
  868. // typecast entity as array for property evaluation
  869. $eary = (array) $entity;
  870. foreach ($eary as $property => $value) {
  871. // value needs to be removed as it didn't pass wrapper validation
  872. if (!isset($wrap[$property])) {
  873. unset($eary[$property]);
  874. }
  875. }
  876. // add values based on wrapper passing correctly
  877. $val = $eary;
  878. }
  879. }
  880. }
  881. }
  882. }
  883. /**
  884. * Helper function to generate options list of entities.
  885. *
  886. * This is a very powerful function that returns a simple array
  887. * based on passing it a few criteria. Think of this as a function
  888. * to abstract and simplify entity field queries.
  889. *
  890. * @param $entity_type
  891. * entity type to filter by
  892. * @param $bundle
  893. * bundle of that type to filter by
  894. * @param $id_field
  895. * field to use for the key in the array
  896. * @param $title_field
  897. * field to use as the value in the array
  898. * @param $field_conditions
  899. * (optional) additional conditions to apply to field filtering
  900. * @param $property_conditions
  901. * (optional) additional conditions to apply for properties
  902. *
  903. * @return $options
  904. * array of options as a clean list for selection of an entity id
  905. */
  906. function _cis_helper_assemble_entity_list($entity_type, $bundle, $id_field, $title_field, $field_conditions = array(), $property_conditions = array()) {
  907. // pull items based on bundle
  908. $options = array();
  909. // assemble all entities requested
  910. $query = new EntityFieldQuery;
  911. $query->entityCondition('entity_type', $entity_type)
  912. ->entityCondition('bundle', $bundle);
  913. // allow for simple property conditions
  914. if (!empty($property_conditions)) {
  915. // only allowed relationship is simple value field name / value pair
  916. foreach ($property_conditions as $field_name => $field_value) {
  917. $query->propertyCondition($field_name, $field_value, '=');
  918. }
  919. }
  920. // allow for simple field conditions
  921. if (!empty($field_conditions)) {
  922. foreach ($field_conditions as $field_name => $field_value) {
  923. // allow for complex relationship if needed
  924. if (is_array($field_value)) {
  925. $query->fieldCondition($field_name, $field_value[0], $field_value[1], '=');
  926. }
  927. else {
  928. // only allowed relationship is simple value field name / value pair
  929. $query->fieldCondition($field_name, 'value', $field_value, '=');
  930. }
  931. }
  932. }
  933. // order by the title property
  934. $result = $query->propertyOrderBy($title_field)
  935. ->execute();
  936. // ensure this produces results to avoid null queries
  937. if (!empty($result)) {
  938. // convert to load only keys of items found
  939. $etids = array_keys($result[$entity_type]);
  940. // load all the entities in the array
  941. $elist = entity_load($entity_type, $etids);
  942. // load entities and build array of id -> title
  943. foreach ($elist as $entity) {
  944. // allow for field value as key
  945. if (is_array($id_field)) {
  946. $key = $entity->{$id_field[0]}[LANGUAGE_NONE][0][$id_field[1]];
  947. }
  948. elseif (strpos($id_field, 'field_') !== FALSE) {
  949. $key = $entity->{$id_field}[LANGUAGE_NONE][0]['value'];
  950. }
  951. else {
  952. $key = $entity->{$id_field};
  953. }
  954. $options[$key] = $entity->{$title_field};
  955. }
  956. }
  957. return $options;
  958. }
  959. /**
  960. * Callback to generate a service node.
  961. *
  962. * Generate a service node in CIS based on the fields passed.
  963. *
  964. * @param $title
  965. * Name of the new service node.
  966. * @param $machine_name
  967. * machine name for this service to make its namespace unique
  968. * @param $distribution
  969. * Drupal distribution associated with this.
  970. * @param $address
  971. * Front facing URL for this system.
  972. * @param $data_address
  973. * Backend URL for webservice calls to run over.
  974. */
  975. function _cis_helper_generate_service_node($title, $machine_name, $distribution, $address, $data_address) {
  976. $node = new stdClass();
  977. $node->type = $type;
  978. node_object_prepare($node);
  979. $node->language = LANGUAGE_NONE;
  980. $node->uid = 1;
  981. $node->status = 1;
  982. $node->promote = 0;
  983. $node->comment = 0;
  984. $node->revision = 1;
  985. $node->sticky = 0;
  986. $node->type = 'service';
  987. // values from arguments passed in
  988. $node->body['und'][0] = array(
  989. 'value' => t("@title is used for..", array('@title' => $title)),
  990. 'summary' => '',
  991. 'format' => 'textbook_editor',
  992. );
  993. $node->title = $title;
  994. $node->field_machine_name['und'][0]['value'] = $machine_name;
  995. $node->field_distribution['und'][0]['value'] = $distribution;
  996. $node->field_location['und'][0] = array(
  997. 'url' => $address,
  998. 'title' => null,
  999. 'attributes' => array(),
  1000. );
  1001. $node->field_data_location['und'][0] = array(
  1002. 'url' => $data_address,
  1003. 'title' => null,
  1004. 'attributes' => array(),
  1005. );
  1006. // build out via submit then save
  1007. if($node = node_submit($node)) {
  1008. node_save($node);
  1009. }
  1010. return $node;
  1011. }
  1012. /**
  1013. * Implements hook_httprl_spider_other_paths_alter(&$paths).
  1014. */
  1015. function cis_helper_httprl_spider_other_paths_alter(&$paths) {
  1016. $paths[] = 'courses';
  1017. $paths[] = 'programs';
  1018. $paths[] = 'faculty';
  1019. }
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.