Expert Omeka S developer assistant for local development with SFTP deployment. Covers theme development (SCSS/CSS, templates), module creation (PHP, events, database), configuration management, and troubleshooting. Use for Omeka S customization from simple CSS tweaks to advanced module development.
Expert guidance for local Omeka S development with SFTP deployment to test servers - from simple CSS tweaks to advanced module development.
Local development with rsync deployment - Edit files locally, sync via rsync:
OMEKA_PATH per project)ssh user@server "cat \$OMEKA_PATH/application/Module.php | grep VERSION"At the start of each project session, establish these variables:
# Set these for your specific project
OMEKA_HOST="[email protected]"
OMEKA_PATH="/var/www/html/omeka-s" # Adjust to your server's Omeka installation path
# Example configurations:
# OMEKA_PATH="/var/www/html/omeka-s"
# OMEKA_PATH="/home/user/public_html"
# OMEKA_PATH="/var/www/sites/myproject"
# Sync files to server (excludes 'files' folder with media)
rsync -avz --exclude 'files' ./local-path/ $OMEKA_HOST:$OMEKA_PATH/path/
# Sync theme to server
rsync -avz --exclude 'files' ./my-theme/ $OMEKA_HOST:$OMEKA_PATH/themes/my-theme/
# Sync from server to local (for initial setup or backup)
rsync -avz --exclude 'files' $OMEKA_HOST:$OMEKA_PATH/path/ ./local-path/
# SSH to run commands on server (limited access)
ssh $OMEKA_HOST "ls -la $OMEKA_PATH/themes/"
# Clear cache on server
ssh $OMEKA_HOST "rm -rf $OMEKA_PATH/application/data/cache/*"
# View logs on server
ssh $OMEKA_HOST "tail -50 $OMEKA_PATH/logs/application.log"
Database credentials are stored in config/database.ini on the server:
# View database credentials
ssh $OMEKA_HOST "cat $OMEKA_PATH/config/database.ini"
# Example database.ini format:
# user = "omeka_user"
# password = "password"
# dbname = "omeka_s"
# host = "localhost"
Run SQL queries via mariadb command over SSH:
# Run a query (replace credentials from database.ini)
ssh $OMEKA_HOST "mariadb -u DB_USER -pDB_PASS DB_NAME -e 'SELECT * FROM resource LIMIT 5;'"
# Interactive session
ssh $OMEKA_HOST "mariadb -u DB_USER -pDB_PASS DB_NAME"
User Request → What type of work?
├─ Theme Customization
│ ├─ Simple CSS change?
│ │ └─ Edit theme/asset/css/style.css locally, sync via rsync
│ │ (see references/theme-development.md → Quick CSS Editing)
│ │
│ ├─ Need SCSS/variables?
│ │ └─ Use SCSS build process locally
│ │ 1. Edit theme/asset/sass/*.scss locally
│ │ 2. Run locally: cd themes/THEME && npm run build
│ │ 3. Sync compiled CSS via rsync
│ │ (see references/theme-development.md → SCSS Build Process)
│ │
│ └─ Template override?
│ └─ Download from application/view/, edit locally, sync to theme/view/
│ (see references/theme-development.md → Template Structure)
│
├─ Module Development
│ ├─ Creating new module?
│ │ └─ Create locally, upload entire module directory
│ │ (see references/module-development.md → Module Structure)
│ │
│ ├─ Adding event listener?
│ │ └─ Edit Module.php locally, upload, clear cache
│ │ (see references/module-development.md → Event System)
│ │
│ └─ Database entities?
│ └─ Create Entity locally, update module.ini, upload, reinstall module
│ (see references/module-development.md → Database Entities)
│
├─ Content Visibility Issues
│ └─ Items not showing?
│ 1. Run queries via mariadb over SSH
│ 2. Verify is_public flags
│ 3. Confirm site assignments
│ (see references/database.md → Content Visibility)
│
└─ Configuration/Setup
└─ See references/troubleshooting.md
$OMEKA_PATH/ # Server path (configurable)
├── application/ # Core (DON'T MODIFY)
│ ├── view/ # Default templates (COPY to theme to override)
│ └── src/ # PHP source
├── config/
│ ├── database.ini
│ └── local.config.php
├── modules/ # Custom modules here
│ └── MyModule/
│ ├── Module.php
│ ├── module.ini
│ ├── src/
│ └── view/
├── themes/ # Custom themes here
│ └── MyTheme/
│ ├── theme.ini
│ ├── asset/
│ │ ├── css/
│ │ └── sass/
│ └── view/
└── files/ # Uploaded media
├── original/
├── large/
├── medium/
└── square/
# Set your project variables first
OMEKA_HOST="[email protected]"
OMEKA_PATH="/var/www/html/omeka-s"
# Read theme file from server
ssh $OMEKA_HOST "cat $OMEKA_PATH/themes/THEME/theme.ini"
# Download theme for local editing (excludes files folder)
rsync -avz --exclude 'files' $OMEKA_HOST:$OMEKA_PATH/themes/THEME/ ./themes/THEME/
# Upload modified theme (excludes files folder)
rsync -avz --exclude 'files' ./themes/THEME/ $OMEKA_HOST:$OMEKA_PATH/themes/THEME/
# View logs
ssh $OMEKA_HOST "tail -50 $OMEKA_PATH/logs/application.log"
# Clear cache
ssh $OMEKA_HOST "rm -rf $OMEKA_PATH/application/data/cache/*"
First, get credentials from database.ini:
ssh $OMEKA_HOST "cat $OMEKA_PATH/config/database.ini"
Then run queries:
# Make all items public
ssh $OMEKA_HOST "mariadb -u USER -pPASS DBNAME -e \"UPDATE resource SET is_public = 1 WHERE resource_type = 'Omeka\\\\Entity\\\\Item';\""
# Assign all items to site 1
ssh $OMEKA_HOST "mariadb -u USER -pPASS DBNAME -e 'INSERT INTO item_site (item_id, site_id) SELECT id, 1 FROM item ON DUPLICATE KEY UPDATE site_id = VALUES(site_id);'"
# Check item visibility
ssh $OMEKA_HOST "mariadb -u USER -pPASS DBNAME -e \"SELECT r.id, r.title, r.is_public, COUNT(is_i.item_id) as in_site FROM resource r LEFT JOIN item_site is_i ON r.id = is_i.item_id WHERE r.resource_type = 'Omeka\\\\Entity\\\\Item' GROUP BY r.id;\""
For complete database operations, see references/database.md
mkdir -p my-theme/{asset/{css,js,sass},view/common}
[info]
name = "My Theme"
version = "1.0"
author = "Your Name"
theme_link = ""
author_link = ""
description = "Custom theme"
rsync -avz --exclude 'files' ./my-theme/ $OMEKA_HOST:$OMEKA_PATH/themes/my-theme/
application/view/, edit locally, sync to theme/view/For complete theme guide, see references/theme-development.md
<?php
namespace MyModule;
use Omeka\Module\AbstractModule;
use Laminas\EventManager\SharedEventManagerInterface;
class Module extends AbstractModule
{
public function attachListeners(SharedEventManagerInterface $sharedEventManager)
{
// Add event listeners here
}
}
[info]
name = "My Module"
version = "1.0.0"
author = "Your Name"
description = "Module description"
rsync -avz --exclude 'files' ./MyModule/ $OMEKA_HOST:$OMEKA_PATH/modules/MyModule/
For complete module guide, see references/module-development.md
rsync -avz --exclude 'files' $OMEKA_HOST:$OMEKA_PATH/themes/THEME/asset/sass/ ./sass/sass/_base.scss variables locallycd themes/THEME && npm install && npx gulp cssrsync -avz ./asset/css/style.css $OMEKA_HOST:$OMEKA_PATH/themes/THEME/asset/css/ssh $OMEKA_HOST "rm -rf $OMEKA_PATH/application/data/cache/*"rsync -avz $OMEKA_HOST:$OMEKA_PATH/application/view/omeka/site/item/show.phtml ./ssh $OMEKA_HOST "mkdir -p $OMEKA_PATH/themes/THEME/view/omeka/site/item"
rsync -avz ./show.phtml $OMEKA_HOST:$OMEKA_PATH/themes/THEME/view/omeka/site/item/
rsync -avz --exclude 'files' ./MyModule/ $OMEKA_HOST:$OMEKA_PATH/modules/MyModule/application/ directlyapplication/→ See references/database.md (Content Visibility section) → Run queries via mariadb over SSH
ssh $OMEKA_HOST "rm -rf $OMEKA_PATH/application/data/cache/*"→ See references/theme-development.md (Troubleshooting Build Issues)
php -l Module.phpssh $OMEKA_HOST "tail -50 $OMEKA_PATH/logs/application.log"→ See references/troubleshooting.md
→ See references/database.md (Media & Thumbnails)
Detailed documentation for each area:
Items need 4 conditions to be visible:
is_public = 1)item_site table)is_open = 1)site_item_set table)See references/database.md for complete details
Themes inherit from default theme:
application/view/asset/sass/See references/theme-development.md for template patterns
Modules extend Omeka via events:
api.search.query - Modify searchview.show.after - Add to page displayform.add_elements - Add form fieldsSee references/module-development.md for event list
For new Omeka S projects:
config/database.iniFor detailed guidance on any topic, see the appropriate reference file above.
Pattern A: Single File Changes
# Edit locally, sync single file
rsync -avz ./path/to/file.php $OMEKA_HOST:$OMEKA_PATH/path/to/file.php
ssh $OMEKA_HOST "rm -rf $OMEKA_PATH/application/data/cache/*"
Pattern B: Directory Sync
# Sync entire directory (excludes files folder with media)
rsync -avz --exclude 'files' ./my-module/ $OMEKA_HOST:$OMEKA_PATH/modules/my-module/
ssh $OMEKA_HOST "rm -rf $OMEKA_PATH/application/data/cache/*"
Omeka S has two distinct types of block layouts that are registered differently:
block_layouts)Used for site pages (like Time Periods, About, etc.)
// In module.config.php
'block_layouts' => [
'invokables' => [
'timePeriodsGrid' => MyModule\Site\BlockLayout\TimePeriodsGrid::class,
],
],
resource_page_block_layouts)Used for item/media/item-set display pages
// In module.config.php
'resource_page_block_layouts' => [
'invokables' => [
'relatedItems' => MyModule\Site\ResourcePageBlockLayout\RelatedItems::class,
],
],
Key Difference: Site page blocks go in regular pages; resource page blocks appear when viewing an item.
NEVER use direct database connections in blocks. Use the API instead:
// WRONG - Will fail
$connection = $view->getHelperPluginManager()->get('api')->getManager()->getConnection();
// CORRECT - Use the API
$view->api()->search('items', [
'site_id' => $site->id(),
'property' => [[
'joiner' => 'and',
'property' => 'dcterms:coverage',
'type' => 'eq',
'text' => 'some value',
]],
]);
Getting unique property values dynamically:
// Get property first
$coverageProperty = $view->api()->searchOne('properties', [
'term' => 'dcterms:coverage',
])->getContent();
// Get all items with that property
$allItems = $view->api()->search('items', [
'site_id' => $site->id(),
'has_property' => [$coverageProperty->id()],
])->getContent();
// Extract unique values
$uniqueValues = [];
foreach ($allItems as $item) {
$values = $item->value('dcterms:coverage', ['all' => true]);
foreach ($values as $val) {
$textValue = (string)$val;
if (!in_array($textValue, $uniqueValues)) {
$uniqueValues[] = $textValue;
}
}
}
Symptoms:
Diagnosis (via mariadb over SSH):
# Check database version
ssh $OMEKA_HOST "mariadb -u USER -pPASS DBNAME -e \"SELECT id, version FROM module WHERE id = 'ModuleName';\""
Compare with file version in module.ini.
Solution: Download the correct version from GitHub releases to match database expectations.
Full working example of a site page block:
<?php
namespace MyModule\Site\BlockLayout;
use Omeka\Api\Representation\SiteRepresentation;
use Omeka\Api\Representation\SitePageRepresentation;
use Omeka\Api\Representation\SitePageBlockRepresentation;
use Omeka\Site\BlockLayout\AbstractBlockLayout;
use Laminas\View\Renderer\PhpRenderer;
class MyCustomBlock extends AbstractBlockLayout
{
public function getLabel()
{
return 'My Custom Block'; // @translate
}
public function form(
PhpRenderer $view,
SiteRepresentation $site,
SitePageRepresentation $page = null,
SitePageBlockRepresentation $block = null
) {
// Get saved data
$data = $block ? $block->data() : [];
$myValue = $data['myValue'] ?? '';
// Return HTML form for admin
return sprintf(
'<div class="field"><label>My Setting</label><input type="text" name="o:block[__blockIndex__][o:data][myValue]" value="%s"></div>',
htmlspecialchars($myValue, ENT_QUOTES)
);
}
public function render(PhpRenderer $view, SitePageBlockRepresentation $block)
{
$site = $view->currentSite();
$data = $block->data();
// Query items dynamically
$items = $view->api()->search('items', [
'site_id' => $site->id(),
'limit' => 10,
])->getContent();
// Render template
return $view->partial('common/block-layout/my-custom-block', [
'items' => $items,
'data' => $data,
]);
}
}
Block configuration is stored as JSON in site_page_block.data (query via mariadb over SSH):
# View block data
ssh $OMEKA_HOST "mariadb -u USER -pPASS DBNAME -e 'SELECT id, layout, data FROM site_page_block WHERE page_id = 10;'"
# Update block data (be careful with escaping!)
ssh $OMEKA_HOST "mariadb -u USER -pPASS DBNAME -e 'UPDATE site_page_block SET data = \"{\\\"key\\\": \\\"value\\\"}\" WHERE id = 54;'"
Important: Data must be valid JSON. Use proper escaping when updating via SQL.
// Search items by property value
$response = $view->api()->search('items', [
'site_id' => $site->id(),
'property' => [[
'joiner' => 'and', // 'and' or 'or'
'property' => 'dcterms:title', // property term
'type' => 'eq', // 'eq', 'neq', 'in', 'nin', 'ex', 'nex'
'text' => 'search term',
]],
]);
$count = $response->getTotalResults();
$items = $response->getContent();
Omeka S uses the CSSEditor module for custom CSS. It provides two methods:
css_editor_css) - Written directly in admin interfacecss_editor_external_css) - URLs to external CSS filesDatabase Storage (check via mariadb over SSH):
# Check current CSS configuration
ssh $OMEKA_HOST "mariadb -u USER -pPASS DBNAME -e \"SELECT id, value FROM site_setting WHERE id LIKE '%css%';\""
# Example output:
# css_editor_css (empty or contains CSS code)
# css_editor_external_css ["/themes/foundation/asset/css/cdha-custom.css"]
CSS Loading Order (Cascade):
1. Core CSS (iconfonts.css, resource-page-blocks.css)
2. Module CSS (from installed modules)
3. Theme base CSS (default.css, inkwell.css, etc.)
4. Inline CSS from css_editor_css (via /s/SITE/css-editor endpoint)
5. External CSS from css_editor_external_css
6. Additional module CSS
Key Points:
Common Issue: CSS Conflicts (fix via mariadb over SSH)
# Clear conflicting inline CSS
ssh $OMEKA_HOST "mariadb -u USER -pPASS DBNAME -e \"UPDATE site_setting SET value = '' WHERE id = 'css_editor_css';\""
# Check what external files are referenced
ssh $OMEKA_HOST "mariadb -u USER -pPASS DBNAME -e \"SELECT value FROM site_setting WHERE id = 'css_editor_external_css';\""
Best Practice:
Omeka S loads templates in this order (highest to lowest priority):
themes/THEME/view/) - HIGHEST PRIORITYmodules/MODULE/view/)application/view/) - LOWEST PRIORITYThis means theme templates override EVERYTHING, including module templates.
Real-World Example:
# You edit this module template locally:
modules/CdhaBlocks/view/common/resource-page-block-layout/filtered-values-main.phtml
# But Omeka actually uses this theme template (if it exists on server):
themes/foundation/view/common/resource-page-block-layout/filtered-values-main.phtml
# Result: Your module changes don't appear, causing confusion!
Debugging Template Issues:
When template changes don't appear:
# 1. Check if theme has override on server
ssh $OMEKA_HOST "find $OMEKA_PATH/themes/ACTIVE_THEME -name 'template-name.phtml'"
# 2. If found, edit the THEME template, not the module template
# 3. Use error logging to confirm which file loads:
# Add to top of both templates:
<?php error_log('TEMPLATE: ' . __FILE__); ?>
Best Practice:
Common Symptoms:
ssh $OMEKA_HOST "rm -rf $OMEKA_PATH/application/data/cache/*"