Skip to content
Snippets Groups Projects
Commit 9fb19804 authored by Michael Lee's avatar Michael Lee
Browse files

Custom module D10 readiness

parent 4bdda961
No related branches found
No related tags found
No related merge requests found
Showing
with 330 additions and 330 deletions
......@@ -2,3 +2,5 @@
*.log
*.sql
.DS_Store
.vscode/
\ No newline at end of file
......@@ -14,15 +14,15 @@ The module assumes the presence of a "Course" content type with the appropriate
## Usage
### Site that accesses EIP directly
### "Drupal-free" course data - Fetching courses data without a Drupal site
- Enable the module
- Navigate to **Configuration -> Content Authoring -> ASC Courses Settings -> API Settings**
- Enter EIP consumer key(s) and secret(s) and select an EIP environment
- Run `drush asc_courses:pull-all-dorgs` to fetch raw course data from EIP and store locally
- Run `drush asc_courses:process-api-data` to process the raw course data into the `asc_courses_processed` database table
- Run `drush sql-dump --tables-list=asc_courses_api_data,asc_courses_processed > asc_courses_data.sql` to export the gathered data to a sql file
- Load the `asc_courses_data.sql` file to each site that wants to use the data. I use [this pair of scripts](https://gist.github.com/weaver299/bf8eb877146a5deeab5f41392db65468) to load the data to every Pantheon site using one of my custom upstreams.
Requires terminus installed and authenticated.
API Store (QA): https://api-qa.eip.osu.edu/store/site/pages/applications.jag
API Store (Production): https://api.eip.osu.edu/store/site/pages/applications.jag
- Direct your attention to the 'scripts_drupalfree' sub-folder
- Copy example-config.php to config.php and fill in appropriate configuration values, notably the consumer key and consumer secret from the API store dashboard
- Run `get_courses_drupalfree.sh`
### Sites using the course data
......@@ -32,6 +32,18 @@ The module assumes the presence of a "Course" content type with the appropriate
- Select D-Org numbers (subjects) and/or specific EIP course IDs to import as Drupal nodes
- The "Course Search" tab can be used for looking up EIP course IDs
### Drupal site that accesses EIP directly (lando)
- Enable the module
- Navigate to **Configuration -> Content Authoring -> ASC Courses Settings -> API Settings**
- Enter EIP consumer key(s) and secret(s) and select an EIP environment
- Run `drush asc_courses:pull-all-dorgs` to fetch raw course data from EIP and store locally
- Run `drush asc_courses:process-api-data` to process the raw course data into the `asc_courses_processed` database table
- Run `drush sql-dump --tables-list=asc_courses_processed > asc_courses_data.sql` to export the gathered data to a sql file
- Load the `asc_courses_data.sql` file to each site that wants to use the data. I use [this pair of scripts](https://gist.github.com/weaver299/bf8eb877146a5deeab5f41392db65468) to load the data to every Pantheon site using one of my custom upstreams.
### TODO
- Clean up process-api-data output
......
......@@ -3,7 +3,6 @@ description: Import Course information from EIP
package: Custom
type: module
core_version_requirement: ^8 || ^9
core_version_requirement: ^9 || ^10
php: 7.2
asc_courses.settings:
title: 'ASC Courses Settings'
description: 'Configure settings for the ASC Courses module'
title: "ASC Courses Settings"
description: "Configure settings for the ASC Courses module"
route_name: asc_courses.settings
parent: 'system.admin_config_content'
parent: "system.admin_config_content"
# A nested menu link for the same page as the parent menu link
asc_courses.selections:
title: 'Course Selections'
description: 'Choose courses to import'
title: "Course Selections"
description: "Choose courses to import"
route_name: asc_courses.settings
parent: 'asc_courses.settings'
parent: "asc_courses.settings"
weight: 1
asc_courses.search:
title: 'Course Search'
description: 'Search for courses and identify their IDs'
title: "Course Search"
description: "Search for courses and identify their IDs"
route_name: asc_courses.search
parent: 'asc_courses.settings'
parent: "asc_courses.settings"
weight: 5
asc_courses.api_settings:
title: 'API Settings'
description: 'Administrate API settings'
title: "API Settings"
description: "Administrate API settings"
route_name: asc_courses.api_settings
parent: 'asc_courses.settings'
parent: "asc_courses.settings"
weight: 10
asc_courses.settings:
title: 'Course Selection'
description: 'Select courses by D-org number and/or specific courses by ID'
title: "Course Selection"
title_context: "Select courses by D-org number and/or specific courses by ID"
route_name: asc_courses.settings
base_route: asc_courses.settings
asc_courses.search:
title: 'Course Search'
# I don't know where this is surfaced in the admin UI
description: 'links.task desc - Find courses and identify their IDs'
title: "Course Search"
title_context: "Find courses and identify their IDs"
route_name: asc_courses.search
base_route: asc_courses.settings
asc_courses.api_settings:
title: 'API Settings'
description: 'links.task desc - Configure ASC Courses API backend'
title: "API Settings"
title_context: "Configure ASC Courses API backend"
route_name: asc_courses.api_settings
base_route: asc_courses.settings
asc_courses.settings:
path: '/admin/config/content/asc-courses'
path: "/admin/config/content/asc-courses"
defaults:
_form: 'Drupal\asc_courses\Form\SettingsForm'
_title: 'ASC Courses Settings' # Used in breadcrumbs
link_id: 'asc_courses.settings'
_title: "ASC Courses Settings"
link_id: "asc_courses.settings"
requirements:
_permission: 'asc courses selections'
_permission: "asc courses selections"
asc_courses.search:
path: '/admin/config/content/asc-courses/search'
path: "/admin/config/content/asc-courses/search"
defaults:
_form: 'Drupal\asc_courses\Form\CourseSearchForm'
# _controller: '\Drupal\asc_courses\Controller\DisplayTableController:display'
_title: 'Course Search' # Used in breadcrumbs
link_id: 'asc_courses.search'
_title: "Course Search"
link_id: "asc_courses.search"
requirements:
_permission: 'asc courses selections'
_permission: "asc courses selections"
asc_courses.api_settings:
path: '/admin/config/content/asc-courses/api_settings'
path: "/admin/config/content/asc-courses/api_settings"
defaults:
_form: 'Drupal\asc_courses\Form\ApiSettingsForm'
_title: 'ASC Courses API' # Used in breadcrumbs
link_id: 'asc_courses.api_settings'
_title: "ASC Courses API"
link_id: "asc_courses.api_settings"
requirements:
_permission: 'asc courses admin'
_permission: "asc courses admin"
asc_courses.settings:
type: config_object
label: 'schema label - ASC Courses Settings' # I don't know if these schema labels are surfaced in the admin UI at all
mapping:
asc_courses:
type: mapping
mapping:
dept_org:
type: text
label: 'schema - D-Org number(s) (e.g. D1435)' # I don't know if these schema labels are surfaced in the admin UI at all
individual_courses:
type: text
label: 'schema - Individual course selections' # see comment above
qa_consumer_key:
type: text
label: 'schema - QA Consumer Key' # see comment above
qa_consumer_secret:
type: text
label: 'schema - QA Consumer Secret' # see comment above
prod_consumer_key:
type: text
label: 'schema - Production Consumer Key' # see comment above
prod_consumer_secret:
type: text
label: 'schema - Production Consumer Secret' # see comment above
eip_environment:
type: text
label: 'schema - EIP Environment' # see comment above
asc_courses.settings:
type: config_object
label: 'ASC Courses Settings'
label: "ASC Courses Settings"
mapping:
asc_courses:
type: mapping
mapping:
consumer_key:
dept_org:
type: text
label: 'Consumer Key'
consumer_secret:
label: "D-Org number(s) (e.g. D1435)"
individual_courses:
type: text
label: 'Consumer Secret'
label: "Individual course selections"
qa_consumer_key:
type: text
label: "QA Consumer Key"
qa_consumer_secret:
type: text
label: "QA Consumer Secret"
prod_consumer_key:
type: text
label: "Production Consumer Key"
prod_consumer_secret:
type: text
label: "Production Consumer Secret"
eip_environment:
type: text
label: "EIP Environment"
This diff is collapsed.
<?php
namespace Drupal\asc_courses;
// use Consolidation\OutputFormatters\StructuredData\RowsOfFields;
......@@ -10,7 +11,8 @@
/**
* Generate and update courses Nodes
*/
class AscCoursesImporter {
class AscCoursesImporter
{
public $debug = 0;
public $config;
public $api;
......@@ -22,29 +24,28 @@ class AscCoursesImporter {
/**
* {@inheritdoc}
*/
public function __construct($config = null, $db = null) {
public function __construct($config = null, $db = null)
{
// Load configuration if not passed
if (!empty($config)) {
$this->config = $config;
}
else {
} else {
$this->config = \Drupal::service('config.factory')->get('asc_courses.settings');
}
// Get database connection
if(empty($db)) {
if (empty($db)) {
$this->db = \Drupal::database();
}
else {
} else {
$this->db = $db;
}
}
public function importCourseNodes($courses_data) {
if($this->debug) echo "importCourseNodes()\n";
public function importCourseNodes($courses_data)
{
if ($this->debug) echo "importCourseNodes()\n";
foreach($courses_data as $course_data) {
foreach ($courses_data as $course_data) {
// echo "importCourseNodes(): " . print_r($course_data, true) . "\n";
$node_storage = \Drupal::entityTypeManager()->getStorage('node');
// if($this->debug) echo "$i catalog-nbr: " . $course_data['catalog-nbr'] . "\n";
......@@ -52,13 +53,13 @@ public function importCourseNodes($courses_data) {
// look up existing node
$node_query = \Drupal::entityQuery('node')
->condition('field_eip_id', $course_data['crse_id'], '=');
->condition('field_eip_id', $course_data['crse_id'], '=')
->accessCheck(FALSE);
$existing_node = $node_query->execute();
if(empty($existing_node)) {
if (empty($existing_node)) {
$this->createCourseNode($node_storage, $course_data);
}
else {
} else {
// update existing node
$existing_nid = array_shift($existing_node);
$this->updateCourseNode($node_storage, $existing_nid, $course_data);
......@@ -67,8 +68,9 @@ public function importCourseNodes($courses_data) {
}
}
public function createCourseNode($node_storage, $course_data) {
if($this->debug) echo "createCourseNode() - create new node\n";
public function createCourseNode($node_storage, $course_data)
{
if ($this->debug) echo "createCourseNode() - create new node\n";
// $course_node = Node::create([
$course_node = $node_storage->create(array(
'type' => 'course',
......@@ -84,11 +86,12 @@ public function createCourseNode($node_storage, $course_data) {
$new_course_nid = $course_node->id();
$this->created_nodes[$new_course_nid] = 1; // track this change
if($this->debug) echo "createCourseNode() - New course nid[$new_course_nid] catalog[" . $course_data['catalog-nbr'] . "]\n";
if ($this->debug) echo "createCourseNode() - New course nid[$new_course_nid] catalog[" . $course_data['catalog-nbr'] . "]\n";
}
public function updateCourseNode($node_storage, $nid, $course_data) {
if($this->debug) echo "updateCourseNode() - Existing nid: $nid\n";
public function updateCourseNode($node_storage, $nid, $course_data)
{
if ($this->debug) echo "updateCourseNode() - Existing nid: $nid\n";
$course_node = $node_storage->load($nid);
......@@ -99,7 +102,7 @@ public function updateCourseNode($node_storage, $nid, $course_data) {
// echo "title: " . print_r($title, true);
$title = $course_node->title->value;
if ($title != $course_data['course_title_long']) {
if($this->debug) {
if ($this->debug) {
echo "### Title changed..\n";
echo "Node title: $title\n";
echo "API title: " . $course_data['course_title_long'] . "\n";
......@@ -112,7 +115,7 @@ public function updateCourseNode($node_storage, $nid, $course_data) {
$description = $course_node->field_course_description->value;
// echo "description: $description\n";
if ($description != $course_data['descrlong']) {
if($this->debug) {
if ($this->debug) {
echo "### description changed..\n";
echo "Node description: $description\n";
echo "API description: " . $course_data['descrlong'] . "\n";
......@@ -125,7 +128,7 @@ public function updateCourseNode($node_storage, $nid, $course_data) {
// echo "course_number: " . print_r($course_number, true);
$course_number = $course_node->field_course_number->value;
if ($course_number != $course_data['catalog_nbr']) {
if($this->debug) {
if ($this->debug) {
echo "### course_number changed..\n";
echo "Node course_number: $course_number\n";
echo "API course_number: " . $course_data['catalog_nbr'] . "\n";
......@@ -137,7 +140,7 @@ public function updateCourseNode($node_storage, $nid, $course_data) {
$credit_hours = $course_node->field_credit_hours->value;
if ($credit_hours != $course_data['acad_prog']) {
if($this->debug) {
if ($this->debug) {
echo "### credit_hours changed..\n";
echo "Node credit_hours: $credit_hours\n";
echo "API credit_hours: " . $course_data['acad_prog'] . "\n";
......@@ -148,7 +151,7 @@ public function updateCourseNode($node_storage, $nid, $course_data) {
$subj_abbrev = $course_node->field_subject_abbreviation->value;
if ($subj_abbrev != $course_data['subject']) {
if($this->debug) {
if ($this->debug) {
echo "### subj_abbrev changed..\n";
echo "Node subj_abbrev: $subj_abbrev\n";
echo "API subj_abbrev: " . $course_data['subject'] . "\n";
......@@ -157,25 +160,26 @@ public function updateCourseNode($node_storage, $nid, $course_data) {
$course_node->field_subject_abbreviation->value = $course_data['subject'];
}
if($content_changed) {
if($this->debug) echo "\nContent has changed.. save updated node.\n\n";
if ($content_changed) {
if ($this->debug) echo "\nContent has changed.. save updated node.\n\n";
$course_node->save();
if(!array_key_exists($nid, $this->created_nodes)) {
if (!array_key_exists($nid, $this->created_nodes)) {
$this->updated_nodes[$nid] = 1; // track this change
}
}
}
public function fetchAndImportAll() {
public function fetchAndImportAll()
{
// $this->config = \Drupal::service('config.factory')->get('asc_courses.settings');
if(!isset($this->api)) $this->api = new AscCoursesApi($this->config);
if (!isset($this->api)) $this->api = new AscCoursesApi($this->config);
$config_dorgs = $this->config->get('asc_courses.dept_org');
$dorgs = explode(',', trim($config_dorgs));
foreach($dorgs as $dorg) {
foreach ($dorgs as $dorg) {
$dorg = trim($dorg);
if ($this->debug) echo "D-org: $dorg\n";
......@@ -201,10 +205,10 @@ public function fetchAndImportAll() {
}
$this->importCourseNodes($alacarte_rows);
}
public function loadSubjectDataFromDatabase($dorg) {
public function loadSubjectDataFromDatabase($dorg)
{
if ($this->debug) echo "loadSubjectDataFromDatabase(): dorg[$dorg]\n";
$course_info_query = $this->db->select('asc_courses_processed', 'acp')
......@@ -223,7 +227,8 @@ public function loadSubjectDataFromDatabase($dorg) {
return $rows;
}
public function getDorgToSubject() {
public function getDorgToSubject()
{
$dorg_to_name = [
'D0200' => [
'name' => "Arts Administration",
......@@ -447,7 +452,8 @@ public function getDorgToSubject() {
}
public function getAllCoursesBySubject() {
public function getAllCoursesBySubject()
{
$dorg_to_subject = $this->getDorgToSubject();
// echo "dorg-to-subject: " . print_r($dorg_to_subject) . "\n";
......@@ -455,12 +461,12 @@ public function getAllCoursesBySubject() {
// ->fields('acd', array('id', 'date', 'dept_org'))
->fields('acp')
->orderBy('updated', 'DESC');
// ->range(0, 1);
// ->range(0, 1);
$course_info_result = $course_info_query->execute();
$all_courses_by_subject = [];
while($row = $course_info_result->fetchAssoc()) {
while ($row = $course_info_result->fetchAssoc()) {
// if ($this->debug) echo "==============\n";
// echo "Row: " . print_r($row, true) . "\n";
......@@ -471,11 +477,11 @@ public function getAllCoursesBySubject() {
$course_cat_nbr = $row['catalog_nbr'];
// $course_title = $row['course-title-long'];
if(!array_key_exists($course_subj, $all_courses_by_subject)) {
if (!array_key_exists($course_subj, $all_courses_by_subject)) {
$all_courses_by_subject[$course_subj] = [];
}
if(!array_key_exists($course_cat_nbr, $all_courses_by_subject[$course_subj])) {
if (!array_key_exists($course_cat_nbr, $all_courses_by_subject[$course_subj])) {
$all_courses_by_subject[$course_subj][$course_cat_nbr] = [];
}
......@@ -527,7 +533,8 @@ public function getAllCoursesBySubject() {
return $all_courses_by_subject;
}
public function establishApiReferences() {
public function establishApiReferences()
{
// if(empty($this->config)) {
// $this->config = \Drupal::service('config.factory')->get('asc_courses.settings');
// }
......@@ -541,8 +548,9 @@ public function establishApiReferences() {
$node_storage = \Drupal::entityTypeManager()->getStorage('node');
$node_query = \Drupal::entityQuery('node')
->condition('field_eip_id', NULL, 'IS NULL')
->condition('type', 'course', '=');
->condition('field_eip_id', NULL, 'IS NULL')
->condition('type', 'course', '=')
->accessCheck(FALSE);
$node_query_result = $node_query->execute();
// print_r($orphaned_nodes);
......@@ -561,26 +569,24 @@ public function establishApiReferences() {
$title = $node->title->value;
if ($this->debug) echo "$node_id - $cat_num - $subj_abbrev - $title\n";
if(!array_key_exists($subj_abbrev, $all_courses)) {
if (!array_key_exists($subj_abbrev, $all_courses)) {
if ($this->debug) echo "No courses with subject $subj_abbrev!!!\n";
if(!array_key_exists($subj_abbrev, $lookup_failed)) {
if (!array_key_exists($subj_abbrev, $lookup_failed)) {
$lookup_failed[$subj_abbrev] = "Not in data set at all!";
}
}
else if (!array_key_exists($cat_num, $all_courses[$subj_abbrev])) {
} else if (!array_key_exists($cat_num, $all_courses[$subj_abbrev])) {
if ($this->debug) echo "$subj_abbrev has no courses with catalog number $cat_num!!\n";
if(!array_key_exists($subj_abbrev, $lookup_failed)) {
if (!array_key_exists($subj_abbrev, $lookup_failed)) {
$lookup_failed[$subj_abbrev] = [];
}
$lookup_failed[$subj_abbrev][$node_id] = $cat_num;
}
else {
} else {
$crse_id = false;
foreach($all_courses[$subj_abbrev][$cat_num] as $found_course) {
foreach ($all_courses[$subj_abbrev][$cat_num] as $found_course) {
if ($this->debug) echo "ID: " . $found_course['crse_id'] . "\n";
$crse_id = $found_course['crse_id'];
}
if($crse_id) {
if ($crse_id) {
$node->field_eip_id->value = $crse_id;
$node->save();
}
......@@ -592,5 +598,4 @@ public function establishApiReferences() {
if ($this->debug) echo "===============\n";
if ($this->debug) print_r($lookup_failed);
}
}
name: Book Settings Changes
description: 'Validates node and changes Book outline settings'
description: "Validates node and changes Book outline settings"
type: module
core_version_requirement: ^8 || ^9
core_version_requirement: ^8 || ^9 || ^10
package: ASC Tech
libraries:
- 'book_settings_changes/global-styling'
- "book_settings_changes/global-styling"
name: 'Buckeye Alert'
name: "Buckeye Alert"
type: module
description: 'Drupal 8 Module for displaying Buckeye Alert messages at the top of your webpage.'
core_version_requirement: ^8 || ^9
package: 'Buckeye Alert'
description: "Drupal 8 Module for displaying Buckeye Alert messages at the top of your webpage."
core_version_requirement: ^8 || ^9 || ^10
package: "Buckeye Alert"
dependencies:
- 'drupal:datetime'
- "drupal:datetime"
name: Custom People Descriptions
description: 'Alters descriptions for username and email on People Edit form'
description: "Alters descriptions for username and email on People Edit form"
type: module
core_version_requirement: ^8 || ^9
core_version_requirement: ^8 || ^9 || ^10
package: ASC Tech
dependencies:
- drupal:user
name: Custom Tokens
description: 'Creates custom tokens'
description: "Creates custom tokens"
type: module
core_version_requirement: ^8 || ^9
core_version_requirement: ^8 || ^9 || ^10
package: ASC Tech
<?php
/**
* Implements hook_token_info().
*/
function customtokens_token_info() {
$type = [
'name' => t('Custom Token'),
'description' => t('Tokens for custom things.'),
];
$node['landingct'] = [
'name' => t("Research Landing Page"),
'description' => t('The node\'s Research Landing Page'),
];
$node['landinggallct'] = [
'name' => t("Research Gallery Landing Page"),
'description' => t('The node\'s Research Gallery Landing Page'),
];
return [
'types' => ['customtoken' => $type],
'tokens' => ['customtoken' => $node],
];
* Implements hook_token_info().
*/
function customtokens_token_info()
{
$type = [
'name' => t('Custom Token'),
'description' => t('Tokens for custom things.'),
];
$node['landingct'] = [
'name' => t("Research Landing Page"),
'description' => t('The node\'s Research Landing Page'),
];
$node['landinggallct'] = [
'name' => t("Research Gallery Landing Page"),
'description' => t('The node\'s Research Gallery Landing Page'),
];
return [
'types' => ['customtoken' => $type],
'tokens' => ['customtoken' => $node],
];
}
/**
* Implements hook_tokens().
*/
function customtokens_tokens($type, $tokens, array $data, array $options, \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata) {
$replacements = [];
* Implements hook_tokens().
*/
function customtokens_tokens($type, $tokens, array $data, array $options, \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata)
{
$replacements = [];
//research ct
$nids = \Drupal::entityQuery('node')->condition('type','research_landing_page')->execute();
$lid = array_shift($nids);
$alias = \Drupal::service('path_alias.manager')->getAliasByPath('/node/'.$lid);
//research ct
$nids = \Drupal::entityQuery('node')->condition('type', 'research_landing_page')->accessCheck(FALSE)->execute();
$lid = array_shift($nids);
$alias = \Drupal::service('path_alias.manager')->getAliasByPath('/node/' . $lid);
//research gallery ct
$nids2 = \Drupal::entityQuery('node')->condition('type','research_gallery_landing_page')->execute();
$lid2 = array_shift($nids2);
$alias2 = \Drupal::service('path_alias.manager')->getAliasByPath('/node/'.$lid2);
//research gallery ct
$nids2 = \Drupal::entityQuery('node')->condition('type', 'research_gallery_landing_page')->accessCheck(FALSE)->execute();
$lid2 = array_shift($nids2);
$alias2 = \Drupal::service('path_alias.manager')->getAliasByPath('/node/' . $lid2);
if ($type == 'customtoken' && !empty($data['node'])) {
foreach ($tokens as $name => $original) {
switch ($name) {
case 'landingct':
$replacements[$original] = $alias;
break;
if ($type == 'customtoken' && !empty($data['node'])) {
foreach ($tokens as $name => $original) {
switch ($name) {
case 'landingct':
$replacements[$original] = $alias;
break;
case 'landinggallct':
$replacements[$original] = $alias2;
break;
}
case 'landinggallct':
$replacements[$original] = $alias2;
break;
}
}
return $replacements;
}
return $replacements;
}
name: Google Map Autofill
description: 'Automatically fills in the Google Map field with content from the Address field in User'
description: "Automatically fills in the Google Map field with content from the Address field in User"
type: module
core_version_requirement: ^8 || ^9
core_version_requirement: ^8 || ^9 || ^10
package: ASC Tech
dependencies:
- drupal:user
name: Publish Label Change
description: 'Automatically changes the Published label for a node so that users will not mistake node publish checkbox with paragraph publish checkbox'
description: "Automatically changes the Published label for a node so that users will not mistake node publish checkbox with paragraph publish checkbox"
type: module
core_version_requirement: ^8 || ^9
core_version_requirement: ^8 || ^9 || ^10
package: ASC Tech
dependencies:
- drupal:node
name: Social Media Links Extras
description: 'Creates additional link fields in Social Media Links module'
description: "Creates additional link fields in Social Media Links module"
type: module
core_version_requirement: ^8 || ^9
core_version_requirement: ^8 || ^9 || ^10
package: ASC Tech
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment