Benji Fisher
May 13, 2026 - MidCamp
Usability group, Migration subsystem, Security team
Harvard Web Publishing since Dec. 2023
The easiest way to power all of your institution’s research websites.
Reference: https://theopenscholar.com/about-us
Before my time: decide to move off the platform.
Do it all at once:
Harvard Web Publishing (HWP)
FFW/Jakala:
One at a time.
One at a time.
Just kidding!
33 two-week sprints: February, 2024 to June, 2025
1856 total, up to 118 sites per sprint
12,000 sites means 12,000 site owners
Kill, Keep, Combine:
Problem: too many cooks, not enough validation
What is the common format for Google Sheets and Jira?
Google Sheets and Jira are too mutable. CSV files in a Git repository are reliable.
One row per site. Some of the columns:
Every site that is migrated in one sprint gets launched at the same time.
Just kidding!
Acquia provides an API and a CLI tool:
acli acsf:sites:create \
--install_profile=hwp_dstan \
--stack_id=2 \
mynewsite \
206
The group “Demo Sites” has numeric ID 206.
sites.csv:
name, stack, group
mynewsite, 2, 206
anothersite, 3, 576
Run a script:
./scripts/provision-site.php sites.csv
Migrate less than 25 sites in each of the first 10 sprints.
OpenScholar started in 2009, while Drupal 7 was still in development.
It used “widgets”: sort of like content blocks (Drupal 8+) or Bricks (Drupal 7 contrib module, 2015).
MySQL [db]> SELECT s.value
FROM node n
JOIN vsite_layout_block v ON v.context = CONCAT('os_pages-page-', n.nid)
JOIN spaces_overrides s ON s.object_id = v.delta
WHERE n.nid = 13311\G
*************************** 1. row ***************************
value: O:8:"stdClass":7:{
s:8:"embed_as";a:2:{...}
s:7:"changed";N;
s:5:"delta";i:1362519977;
s:5:"title";s:15:"Graduate Office";
s:11:"description";s:27:"Graduate Office Information";
s:7:"options";a:5:{...}
s:10:"plugin_key";s:13:"os_boxes_html";
}
1 row in set (0.093 sec)
In a database (like MySQL):
SUBSTRING_INDEX()JOIN spaces_overrides s ON s.object_id = v.deltaMySQL [db]> SELECT v.sid, ogm.gid
FROM vsite_layout_block v
JOIN og_membership ogm ON CONCAT('os_pages-page-', ogm.etid) = v.context
WHERE v.sid <> ogm.gid
AND ogm.entity_type = 'node'
LIMIT 0,10;
+------+-----+
| sid | gid |
+------+-----+
| 3111 | 6 |
| 3111 | 6 |
+------+-----+
2 rows in set (55 min 40.365 sec)
Add a column to that table:
ALTER TABLE spaces_overrides
ADD COLUMN plugin_key varchar(64);
Now populate that column:
UPDATE spaces_overrides s
SET plugin_key = SUBSTRING_INDEX(SUBSTRING_INDEX(SUBSTRING_INDEX(s.value, 's:10:"plugin_key";', -1), '";', 1), ':"', -1)
WHERE s.object_type = 'boxes'
AND plugin_key IS NULL;
Query OK, 106770 rows affected (1.801 sec)
Rows matched: 106770 Changed: 106770 Warnings: 0
Thursday 10:00am to 4:00pm
This slide deck by
Benji
Fisher is licensed under a
Creative
Commons Attribution-ShareAlike 4.0 International License.
Based on a work at
https://gitlab.com/benjifisher/slide-decks.