cis_section.module

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

Code for the CIS Section feature.

Functions

Namesort descending Description
cis_section_all_sections Return all found sections as these are valid for switching
cis_section_assemble_roster Assemble the roster
cis_section_cis_section_activate Implements hook_cis_section_activate().
cis_section_cis_section_deactivate Implements hook_cis_section_deactivate().
cis_section_cron Implements hook_cron().
cis_section_entity_presave Implements hook_entity_presave().
cis_section_node_insert Implements hook_node_insert().
cis_section_node_update Implements hook_node_update().
cis_section_page_build Implements hook_page_build().
cis_section_state_change Run hooks for state of section.
_cis_section_activate_section Callback for hook_cis_section_activate().
_cis_section_create_accounts Create accounts, groups, and associate to groups.
_cis_section_deactivate_section Implements hook_cis_section_deactivate().
_cis_section_load_section_by_id Load an organic group by unique section ID.
_cis_section_load_users_by_gid Load an organic group by unique section ID.

Constants

File

sites/all/modules/elmsln_contrib/cis_connector/features/cis_section/cis_section.module
View source
  1. <?php
  2. /**
  3. * @file
  4. * Code for the CIS Section feature.
  5. */
  6. define('CIS_SECTION_STUDENT', 'student');
  7. define('CIS_SECTION_PAST_STUDENT', 'past student');
  8. include_once 'cis_section.features.inc';
  9. /**
  10. * Implements hook_page_build().
  11. */
  12. function cis_section_page_build(&$page) {
  13. drupal_add_css(drupal_get_path('module', 'cis_section') . '/cis_section.css');
  14. }
  15. /**
  16. * Implements hook_cron().
  17. */
  18. function cis_section_cron() {
  19. // the magic that keeps sections in sync with the cis
  20. $query = array(
  21. 'type' => 'course',
  22. 'field_machine_name' => str_replace('/', '', base_path()),
  23. 'deep-load-refs' => 'field_collection_item', // special property that tells sections from course
  24. );
  25. // return a course
  26. $return = _cis_connection_query($query, 'node', 'json', 'GET', 'cis', '', FALSE);
  27. // look for 1 record
  28. if (count($return['list']) == 1) {
  29. $section_strings = array();
  30. $course = $return['list'][0];
  31. // loop through offerings
  32. foreach ($course['field_offerings'] as $offering) {
  33. // loop through sections if they exist
  34. if (isset($offering['field_sections']['und']) && count($offering['field_sections']['und']) > 0) {
  35. foreach ($offering['field_sections']['und'] as $section) {
  36. // verify that this has the full object and has dates
  37. if (isset($section['item_id']) && isset($section['field_access_dates']['und'])) {
  38. $start = $section['field_access_dates']['und'][0]['value'];
  39. // loop through access strings
  40. // this is the magic that binds students to sections automatically
  41. // and allows the entire network to work as an OG dark-net :)
  42. if (isset($section['field_access_string']['und']) && count($section['field_access_string']['und']) > 0) {
  43. foreach ($section['field_access_string']['und'] as $string) {
  44. $section_strings[$string['safe_value']] = $string['safe_value'];
  45. }
  46. }
  47. }
  48. }
  49. }
  50. }
  51. // loop through section strings to make sure we have a matching section node
  52. foreach ($section_strings as $section_id) {
  53. // if we don't know about this section, create it
  54. if (!_cis_section_load_section_by_id($section_id)) {
  55. $section = new stdClass();
  56. $section->type = 'section';
  57. node_object_prepare($section);
  58. // currently both items are given the section id
  59. $section->title = $section_id;
  60. $section->field_section_id['und'][0]['value'] = $section_id;
  61. // mark this as active so it can sync with systems
  62. $section->field_cis_active['und'][0]['value'] = 1;
  63. $section->language = LANGUAGE_NONE;
  64. // default these to admin ownership for less chance of seeing it
  65. $section->uid = 1;
  66. // save section, cron job will then be able to check data source for roster
  67. // if that functionality is enabled in local instance
  68. node_save($section);
  69. }
  70. }
  71. }
  72. // test for if we should sync w/ our roster provider
  73. $frequency = variable_get('cis_service_connection_sync_frequency', CIS_SERVICE_CONNECTION_SYNC_FREQUENCY);
  74. $interval = 86400 * $frequency;
  75. // always sync if this call just came from drush
  76. // sync this course with LMS based on section data if time has passed
  77. // sync if there's an argument asking to force sync
  78. if (drupal_is_cli() || isset($_GET['force_roster_sync']) || (REQUEST_TIME - variable_get('cis_service_connection_last_sync', 0)) > $interval) {
  79. // simple section pull based on those included locally
  80. $sections = array_keys(cis_section_all_sections(TRUE));
  81. // pull the roster together
  82. $roster = cis_section_assemble_roster($sections);
  83. // build the user accounts
  84. watchdog('roster', 'Roster service synced with !roster sections', array('!roster' => count($roster)));
  85. _cis_section_create_accounts($roster);
  86. }
  87. }
  88. /**
  89. * Implements hook_entity_presave().
  90. */
  91. function cis_section_entity_presave($entity, $type) {
  92. // our default handler for LTI section saves is anonymous
  93. // this way we don't need to give users permission to build these
  94. if ($type == 'node' && $entity->type == 'section' && $entity->uid == 0) {
  95. // force this to be user 1's node, we don't want annonymous anything
  96. // as this can create confusion with access control modules and ownership
  97. // of content later on in the life cycle
  98. $entity->uid = 1;
  99. }
  100. // optional ability to email someone standard language
  101. // this happens if its a user, and we forced roster sync
  102. // and it has the special cis_contact flag and the email
  103. // in the address matches the email of this user account
  104. // and they were created by the job which triggered this
  105. // being created in the first place. Edge case but very
  106. // useful for alleviating workflow when a student is denied
  107. // access to the course from an LMS vendor / data source
  108. // and we need to alert IDs about it without forcing action.
  109. if ($type == 'user' &&
  110. isset($_GET['force_roster_sync']) &&
  111. isset($_GET['cis_contact']) &&
  112. $_GET['cis_contact'] == $entity->mail
  113. ) {
  114. $subject = t('You now have access to the @course course', array('@course' => variable_get('site_name', '')));
  115. // request the standard language for this edge case
  116. $query = array('type' => 'resource', 'field_machine_name' => 'lms_out_of_sync');
  117. $resource = _cis_connection_query($query);
  118. // render text applying the input filter requested
  119. $message = check_markup($resource['list'][0]['body']['value'], $resource['list'][0]['body']['format']);
  120. // allow for user in field
  121. $message = str_replace('!name', $entity->name, $message);
  122. // send an email about
  123. watchdog('cis email', '@mail contacted about course being synced', array('@mail' => $entity->mail));
  124. _cis_connector_simple_mail($entity->mail, $subject, $message, variable_get('site_mail', $entity->mail));
  125. }
  126. }
  127. /**
  128. * Return all found sections as these are valid for switching
  129. *
  130. * This is essentially a list of the current user's groups
  131. *
  132. * @param $active_only
  133. * (optional) Whether or not to return only the active sections
  134. * this user is a part of.
  135. * @return $sections
  136. * array of section key paired as key => name
  137. */
  138. function cis_section_all_sections($active_only = FALSE) {
  139. $sections = array();
  140. // select field section data
  141. $query = new EntityFieldQuery();
  142. // pull all nodes
  143. $query->entityCondition('entity_type', 'node')
  144. // that are sections
  145. ->entityCondition('bundle', 'section')
  146. // that are published
  147. ->propertyCondition('status', 1);
  148. // allow for filtering by active state
  149. if ($active_only) {
  150. $query->fieldCondition('field_cis_active', 'value', 1, '=');
  151. }
  152. // store results
  153. $result = $query->execute();
  154. // ensure we have results
  155. if (isset($result['node'])) {
  156. $nids = array_keys($result['node']);
  157. $results = entity_load('node', $nids);
  158. // convert to a readable array of options
  159. foreach ($results as $val) {
  160. $section = $val->field_section_id['und'][0]['safe_value'];
  161. $sections[$section] = $val->title;
  162. }
  163. // useful for custom college / university integrations
  164. drupal_alter('cis_section_list', $sections);
  165. }
  166. return $sections;
  167. }
  168. /**
  169. * Assemble the roster
  170. *
  171. * We must have another module implement roster code to use this.
  172. *
  173. * @param $sections
  174. * An array of sections to assemble the roster for.
  175. * @param $activated
  176. * (optional) Special case where a section was manually
  177. * told to be activated via save or the UI. In this case
  178. * we ignore semester boundaries and sync this roster regardless.
  179. * @return $roster
  180. * an array of users keyed by section and their role.
  181. *
  182. * @see _cis_connector_transaction()
  183. */
  184. function cis_section_assemble_roster($sections, $activated = FALSE) {
  185. // build roster based on an array of sections
  186. $roster = array();
  187. foreach ($sections as $section) {
  188. // ensure we only sync things that should be sycned
  189. // this helps elminate purely developmental section spaces like masters
  190. // as well as sections primed for setup but not active
  191. // as the per the semester dates
  192. $dates = _cis_connector_transaction('section_dates', 'default', array(), $section);
  193. // only perform this if we have dates stored
  194. if (isset($dates['end']) && isset($dates['start'])) {
  195. if (($dates['start'] < REQUEST_TIME && $dates['end'] > REQUEST_TIME) || $activated) {
  196. // build the roster for this section
  197. $roster[$section] = module_invoke_all('cis_section_build_roster', $section);
  198. }
  199. // test for it being before or after date that this section should be active
  200. // this helps allow for logistical setup of a section prior to the semester
  201. // start while removing that section's ability to sync its roster.
  202. // this reduces strain on APIs via needless calls
  203. elseif ($dates['start'] > REQUEST_TIME || $dates['end'] < REQUEST_TIME) {
  204. $nid = _cis_section_load_section_by_id($section);
  205. $node = node_load($nid);
  206. // mark this as inactive, this will trigger the cis_section clean up on node_update hook
  207. $node->field_cis_active['und'][0]['value'] = 0;
  208. node_save($node);
  209. }
  210. }
  211. }
  212. return $roster;
  213. }
  214. /**
  215. * Implements hook_node_insert().
  216. */
  217. function cis_section_node_insert($node) {
  218. // account for newly created sections being activated so we can react immediately
  219. if ($node->type == 'section' && $node->field_cis_active['und'][0]['value'] == 1) {
  220. cis_section_state_change($node, 'activate');
  221. }
  222. }
  223. /**
  224. * Implements hook_node_update().
  225. */
  226. function cis_section_node_update($node) {
  227. // test for a section being deactivated
  228. if ($node->type == 'section') {
  229. if ($node->field_cis_active['und'][0]['value'] == 0) {
  230. cis_section_state_change($node, 'deactivate');
  231. }
  232. else {
  233. // allow other projects to perform actions based on this node being activated
  234. cis_section_state_change($node, 'activate');
  235. }
  236. }
  237. }
  238. /**
  239. * Implements hook_cis_section_activate().
  240. */
  241. function cis_section_cis_section_activate() {
  242. return array('_cis_section_activate_section');
  243. }
  244. /**
  245. * Callback for hook_cis_section_activate().
  246. */
  247. function _cis_section_activate_section($node) {
  248. // check for a property telling us to ignore this sync
  249. // this happens when updating the source node w/o
  250. // need to sync such as when the title changes
  251. if (!isset($node->_ignore_sync)) {
  252. // grab section id
  253. $section = array($node->field_section_id['und'][0]['value']);
  254. // pull the roster together for this section
  255. $roster = cis_section_assemble_roster($section, TRUE);
  256. // build the user accounts
  257. watchdog('roster', 'Roster synced for section @section', array('@section' => $node->field_section_id['und'][0]['value']));
  258. _cis_section_create_accounts($roster);
  259. drupal_set_message(t('Roster synced for section @section', array('@section' => $node->field_section_id['und'][0]['value'])));
  260. }
  261. }
  262. /**
  263. * Implements hook_cis_section_deactivate().
  264. */
  265. function cis_section_cis_section_deactivate() {
  266. return array('_cis_section_deactivate_section');
  267. }
  268. /**
  269. * Implements hook_cis_section_deactivate().
  270. */
  271. function _cis_section_deactivate_section($node) {
  272. // standard conversion of student to past student roles
  273. $student = user_role_load_by_name(CIS_SECTION_STUDENT);
  274. $past = user_role_load_by_name(CIS_SECTION_PAST_STUDENT);
  275. $current = _cis_section_load_users_by_gid($node->nid, $student->rid);
  276. // find users that no longer came across
  277. $diff = array_diff($current, array());
  278. foreach ($diff as $uid) {
  279. $account = user_load($uid);
  280. // drop student role
  281. unset($account->roles[$student->rid]);
  282. // gain past student role
  283. $account->roles[$past->rid] = $past->name;
  284. user_save($account);
  285. }
  286. }
  287. /**
  288. * Run hooks for state of section.
  289. */
  290. function cis_section_state_change($node, $state = 'activate') {
  291. // activate / deactivate only two allowed states at this time
  292. if (!in_array($state, array('activate', 'deactivate'))) {
  293. return FALSE;
  294. }
  295. // easier calls for activate / deactivate in bulk
  296. $calls = module_invoke_all('cis_section_' . $state);
  297. // alter the call list
  298. drupal_alter('cis_section_' . $state, $calls);
  299. // run each call
  300. foreach ($calls as $call) {
  301. call_user_func($call, $node);
  302. }
  303. }
  304. /**
  305. * Create accounts, groups, and associate to groups.
  306. */
  307. function _cis_section_create_accounts($roster) {
  308. $actual = array();
  309. // loop through sections in the overall access roster
  310. foreach ($roster as $section => $members) {
  311. // loop through the user / role combination
  312. foreach ($members as $name => $role_name) {
  313. // convert role name to object bc of stupid storage convention for account creation
  314. $role = user_role_load_by_name($role_name);
  315. // try and load the account first
  316. // @ignore druplart_conditional_assignment
  317. if (!$account = user_load_by_name($name)) {
  318. $fields = array(
  319. 'name' => $name,
  320. 'pass' => user_password(20),
  321. 'status' => 1,
  322. 'roles' => array(
  323. DRUPAL_AUTHENTICATED_RID => 'authenticated user',
  324. $role->rid => $role->name,
  325. ),
  326. );
  327. // allow other projects to update part of the user
  328. drupal_alter('cis_section_user_insert', $fields);
  329. // the first parameter is left blank so a new user is created
  330. $account = user_save('', $fields);
  331. }
  332. else {
  333. // only save role if it is new
  334. if (!isset($account->roles[$role->rid])) {
  335. $account->roles[$role->rid] = $role->name;
  336. user_save($account);
  337. }
  338. }
  339. // load group by section
  340. $gid = _cis_section_load_section_by_id($section);
  341. // false returned if this group doesn't exist
  342. if (!$gid) {
  343. // we need to create the group as this is a new one
  344. // this can happen if we get a group not created via LTI
  345. $group = new stdClass();
  346. $group->type = 'section';
  347. $group->status = 1;
  348. $group->uid = 1;
  349. $group->title = $section;
  350. $group->promote = 0;
  351. $group->revision = 1;
  352. $group->field_section_id = array(
  353. 'und' => array(
  354. 0 => array(
  355. 'value' => $section,
  356. ),
  357. ),
  358. );
  359. node_save($group);
  360. $gid = $group->nid;
  361. }
  362. // add group membership
  363. $values = array(
  364. 'entity_type' => 'user',
  365. 'entity' => $account,
  366. );
  367. og_group('node', $gid, $values);
  368. // rip membership names to uid array for comparison
  369. $actual[] = $account->uid;
  370. }
  371. // compare members that just came across to members currently (that are students)
  372. // anyone currently that didn't just come across, gets role dropped
  373. // they gain past student
  374. $student = user_role_load_by_name(CIS_SECTION_STUDENT);
  375. $past = user_role_load_by_name(CIS_SECTION_PAST_STUDENT);
  376. if (isset($gid)) {
  377. $current = _cis_section_load_users_by_gid($gid, $student->rid);
  378. }
  379. else {
  380. $current = array();
  381. }
  382. // find users that no longer came across
  383. $diff = array_diff($current, $actual);
  384. foreach ($diff as $uid) {
  385. $account = user_load($uid);
  386. // drop student role
  387. unset($account->roles[$student->rid]);
  388. // gain past student role
  389. $account->roles[$past->rid] = $past->name;
  390. user_save($account);
  391. }
  392. }
  393. }
  394. /**
  395. * Load an organic group by unique section ID.
  396. */
  397. function _cis_section_load_section_by_id($id) {
  398. // entity field query to load a section by id
  399. $query = new EntityFieldQuery();
  400. $query
  401. // pull group nodes
  402. ->entityCondition('entity_type', 'node')
  403. // of type section
  404. ->entityCondition('bundle', 'section')
  405. // that are published
  406. ->propertyCondition('status', 1)
  407. // only select based on the id we were passed
  408. ->fieldCondition('field_section_id', 'value', $id, '=')
  409. // execute this as user 1 to avoid object conflicts
  410. ->addMetaData('account', user_load(1))
  411. // only return 1 value
  412. ->range(0, 1);
  413. $result = $query->execute();
  414. // flip the results if it found them
  415. if (isset($result['node'])) {
  416. // we know there's only 1 value in this array
  417. return array_pop(array_keys($result['node']));
  418. }
  419. // no matches
  420. return FALSE;
  421. }
  422. /**
  423. * Load an organic group by unique section ID.
  424. */
  425. function _cis_section_load_users_by_gid($gid, $rid = NULL) {
  426. // select from membership
  427. $query = db_select('og_membership', 'ogm');
  428. // only entity id
  429. $query->fields('ogm', array('etid'));
  430. // join user table
  431. $query->innerJoin('users', 'u', 'ogm.etid = u.uid');
  432. // join role table
  433. $query->innerJoin('users_roles', 'ur', 'u.uid = ur.uid');
  434. // where entity type is user
  435. $query->condition('ogm.entity_type', 'user');
  436. // and group is the one requested
  437. $query->condition('ogm.gid', $gid);
  438. // limit to a certain role if set
  439. if (!is_null($rid)) {
  440. $query->condition('ur.rid', $rid);
  441. }
  442. $result = $query->execute();
  443. // weird call but returns an array of the uids selected
  444. return array_keys($result->fetchAllAssoc('etid'));
  445. }
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.