This page is a draft.
The information on this page concerns an unreleased version of Omeka or is subject to change for some other reason. Stay away, if you know what's good for you.
Omeka 2.0 will include many changes that will affect how plugins are designed and how they accomplish various tasks. This is a running list, and will likely change greatly between now and the eventual release of 2.0 (no date is set for that yet).
Omeka an Archive?
While archivists can (and many do) use Omeka as a presentation layer to their digital holdings, Omeka is not archival management software. To underscore this fact, we've removed all mention of the word "archive" in the Omeka codebase and filesystem. This will require at least two additional steps when upgrading from earlier versions to 2.0:
- Rename the
archive/files/
directory to/archive/original/
:
$ mv /path/to/omeka/archive/files/ to /path/to/omeka/archive/original/
- Rename the
archive/
directory tofiles/
:
$ mv /path/to/omeka/archive/ /path/to/omeka/files/
Some changes to the API were necessary for this change. They include:
- Rename
ARCHIVE_DIR
toFILES_DIR
- Rename
WEB_ARCHIVE
toWEB_FILES
- Rename
File::$archive_filename
toFile::$filename
- Rename "archive" to "original" (when an argument that refers to path to files/original/)
- Rename
Omeka_Filter_Filename::renameFileForArchive()
toOmeka_Filter_Filename::renameFile()
- Rename
Omeka_View_Helper_Media::archive_image()
toOmeka_View_Helper_Media::image_tag()
- Rename
Installer_Requirements::_checkArchiveStorageSetup()
toInstaller_Requirements::_checkFileStorageSetup()
Abstract Plugin Class
The Omeka_Plugin_Abstract
class was introduced in Omeka 1.5 to assist plugin authors in setting up their plugin's hooks, filters, and options. In 2.0 this has been renamed to Omeka_Plugin_AbstractPlugin
. We highly recommend using this class because plugin.php may be deprecated in future versions of Omeka in favor of loading the plugin directly from this class. Also starting in 2.0, Omeka does not require the plugin.php file if a valid plugin class exists following this pattern:
// In plugins/YourPlugin/YourPluginPlugin.php class YourPluginPlugin extends Omeka_Plugin_AbstractPlugin {}
One change introduced in Omeka 2.0 is arbitrary hook and filter callback names. Before, all callback names followed a predetermined format. While this format remains an option, now a corresponding key in $_hooks
and $_filters
will be interpreted as the name of the callback method.
array('admin_navigation_main', 'public_navigation_main', 'changeSomething' => 'display_setting_site_title', 'displayItemDublinCoreTitle' => array( 'Display', 'Item', 'Dublin Core', 'Title', ))
Controllers
Your controllers should now extend Omeka_Controller_AbstractActionController
instead of Omeka_Controller_Action
. This is for forward compatibility with Zend Framework 2.0. For example:
class YourController extends Omeka_Controller_AbstractActionController {}
modelClass and modelName
Controllers used to have a _modelClass property that refers to the corresponding name of the model used by the controller. That changes to use setDefaultModelName.
// 1.x public function init() { $this->_modelClass = 'MyModel'; } // 2.0 public function init() { $this->_helper->db->setDefaultModelName('MyModel'); }
Db wrappers removed
getDb, getTable, and findById are replaced by equivalent calls to _helper->db->{methodName}.
$elementToRemove = $this->getDb()->getTable('Element')->find($elementId);
becomes
$elementToRemove = $this->_helper->db->getTable('Element')->find($elementId);
Models
Records
Your records should now extend Omeka_Record_AbstractRecord
instead of Omeka_Record
. This is for forward compatibility with Zend Framework 2.0. For example:
class YourRecord extends Omeka_Record_AbstractRecord {}
We've made some major changes to the record API. The following callbacks have been removed:
- beforeSaveForm
- afterSaveForm
- beforeInsert
- afterInsert
- beforeUpdate
- afterUpdate
- beforeValidate
- afterValidate
As such, the following plugin hooks have been removed, where * is "record" or a specific record name (e.g. "item", "collection"):
- before_save_form_*
- after_save_form_*
- before_insert_*
- after_insert_*
- before_update_*
- after_update_*
- before_validate_*
- after_validate_*
By removing these callbacks we give you full control over the timing of execution. Any logic that's currently in the SaveForm, Insert, Update callbacks should be moved to beforeSave() and afterSave(). The Any logic that's currently in the Validate callbacks should be moved to _validate()
. For example:
// Note the order of execution. public function afterSave($args) { if ($args['insert']) { // Do something after record insert. Equivalent to afterInsert. } else { // Do something after record update. Equivalent to afterUpdate. } // Do something after every record save. if ($args['post']) { // Do something with the POST data. Equivalent to afterSaveForm. } }
Note that the signature of the beforeSave() and afterSave() in Omeka_Record_AbstractRecord has changed to beforeSave($args) and afterSave($args), with no type specified for $args. To adhere to strict standards, existing beforeSave and afterSave methods should reflect that change.
Another change is that saveForm()
has been merged into save()
. Using save()
to handle a form in your controller is done like this:
public function editAction() { // Check if the form was submitted. if ($this->getRequest()->isPost()) { // Set the POST data to the record. $record->setPostData($_POST); // Save the record. Passing false prevents thrown exceptions. if ($record->save(false)) { $successMessage = $this->_getEditSuccessMessage($record); if ($successMessage) { $this->_helper->flashMessenger($successMessage, 'success'); } $this->_redirectAfterEdit($record); // Flash an error if the record does not validate. } else { $this->_helper->flashMessenger($record->getErrors()); } } }
Models Directory
The models directory has been reorganized to only have models that extend Omeka_Record_AbstractRecord
at the root level. Classes that extend Omeka_Db_Table
were moved to the models/Table/ directory, and renamed from *Table
to Table_*
. This change is transparent when using Omeka_Db::getTable()
, so you'll still pass the root name of the table, like so:
// This gets the Table_Item object. $itemTable = $db->getTable('Item');
When writing plugins we recommend following this directory pattern in your models directory:
- Classes that extend
Omeka_Record_AbstractRecord
should go directly in the models/ directory, as usual. - Classes that extend
Omeka_Db_Table
should go in the models/Table/ directory, and their class names should beTable_[record name]
.
For backwards compatibility we allow Omeka_Db_Table
classes to follow their old pattern, but this behavior is deprecated.
Database Aliases in Table_* Classes
Previously, when using the getSelect()
method, select statements could use a short alias for the database name, when adding clauses, for example , "e" in this query:
$select->where("e.id = ?", $exhibitId);
when querying the exhibits table. Those should no longer be used. Use the full name of the table instead (without the table prefix):
$select->where("exhibits.id = ?", $exhibitId);
User Record
The User
record no longer depends on an Entity
to store some data like name and email. The user's full name and email are directly part of the User
object now.
There are no longer separate fields for first, last, and middle name, just one for full name.
Entity Record
The Entity
, EntitiesRelations
, and EntityRelationships
records are gone, as is the Relatable
mixin.
Plugins that were relying on or referring to entities or entity ids should usually directly refer to users instead.
Plugins that had records using Relatable
should use the new Mixin_Owner
mixin instead for handling record ownership.
Mixins
The following mixins have been changed
Old Mixin | New Mixin |
---|---|
Ownable | Mixin_Owner |
Taggable | Mixin_Tag |
ActsAsElementText | Mixin_ElementText |
PublicFeatured | Mixin_PublicFeatured |
The Mixin_Timestamp
mixin has been added.
The Mixing_Search
mixin has been added.
The Orderable
mixin has been removed.
Acl
The Omeka_Acl
object no longer exists. References to Omeka_Acl
should be to Zend_Acl
instead.
Omeka_Acl
provided several non-standard methods which are gone along with it: loadRoleList()
, loadResourceList()
, and loadAllowList()
. All of these methods were shortcuts for passing arrays to the Acl object.
Now, just directly make individual calls to addRole()
, addResource()
, and allow()
. You no longer need to use loadResourceList()
to define the privileges for each resource.
checkUserPermission()
Pre-2.0:
$acl->checkUserPermission('ExhibitBuilder_Exhibits', 'showNotPublic');
Use isAllowed()
instead:
$acl->isAllowed(current_user(), 'ExhibitBuilder_Exhibits', 'showNotPublic');
Helper Functions
Many helper functions are removed or replaced. The table below lists previously used functions with their replacements
Function Replacements
Old Function | New Function |
---|---|
random_featured_item() | random_featured_items(1) |
has_tags() | metadata($record, 'has tags') |
item_has_tags() | metadata($item, 'has tags') |
item_has_files() | metadata($item, 'has files') |
item_has_thumbnail() | metadata($item, 'has thumbnail') |
item_citation() | metadata($item, 'citation') |
n/a | metadata($item, 'file count') |
item_fullsize() | item_image('fullsize') |
item_thumbnail() | item_image('thumbnail') |
item_square_thumbnail() | item_image('square_thumbnail') |
setting() | option() |
display_js() | head_js() |
display_css() | head_css() |
css() | css_src() |
js() | js_tag() |
queue_js() | queue_js_file() |
queue_css() | queue_css_file() |
display_random_featured_collection() | random_featured_collection() |
recent_collections() | get_recent_collections() |
random_featured_collection() | get_random_featured_collection() |
get_latest_omeka_version() | latest_omeka_version() |
display_file() | file_markup() |
display_files() | file_markup() |
recent_files() | get_recent_files() |
show_file_metadata() | all_element_texts() |
_tag_attributes() | tag_attributes() |
simple_search() | simple_search_form() |
display_form_input_for_element() | element_form() |
display_element_set_form() | element_set_form() |
label_options() | label_table_options() |
display_search_filters() | search_filters() |
current_action_contexts() | get_current_action_contexts() |
__v() | get_view() |
display_files_for_item() | files_for_item() |
display_random_featured_item() | random_featured_item() |
display_random_featured_items() | random_featured_items() |
recent_items() | get_recent_items() |
random_featured_items() | get_random_featured_items() |
random_featured_item() | get_random_featured_item() |
show_item_metadata() | all_element_texts() |
link_to_advanced_search() | link_to_item_search() |
link_to_file_metadata() | link_to_file_show() |
link_to_next_item() | link_to_next_item_show() |
link_to_previous_item() | link_to_previous_item_show() |
link_to_browse_items() | link_to_items_browse() |
nls2p() | text_to_paragraphs() |
recent_tags() | get_recent_tags() |
item_tags_as_string() | tag_string('item') |
item_tags_as_cloud() | tag_cloud('item') |
has_permission() | is_allowed() |
uri() | url() |
abs_uri() | absolute_url() |
abs_item_uri() | record_url($item, 'show', true) |
record_uri() | record_url() |
item_uri() | record_url($item) |
current_uri() | current_url() |
is_current_uri() | is_current_url() |
items_output_uri() | items_output_url() |
file_display_uri() | file_display_url() |
public_uri() | public_url() |
admin_uri() | admin_url() |
set_theme_base_uri() | set_theme_base_url() |
revert_theme_base_uri() | revert_theme_base_url |
loop_records() | loop() |
loop_files() | loop('files') |
loop_collections() | loop('collections') |
loop_items() | loop('items') |
loop_item_types() | loop('item_types') |
loop_items_in_collection() | loop('items') |
loop_files_for_item() | loop('files') |
set_current_file() | set_current_record('file', $file) |
set_current_collection() | set_current_record('collection', $collection) |
set_current_item() | set_current_record('item', $item) |
get_current_item() | get_current_record('item') |
get_current_collection() | get_current_record('collection') |
get_current_file() | get_current_record('file') |
get_current_item_type() | get_current_record('item_type') |
set_current_item_type() | set_current_record('item_type') |
set_items_for_loop() | set_loop_records('items', $items) |
get_items_for_loop() | get_loop_records('items') |
has_items_for_loop() | has_loop_records('items') |
set_collections_for_loop() | set_loop_records('collections', $items) |
get_collections_for_loop() | get_loop_records('collections') |
has_collections_for_loop() | has_loop_records('collections') |
set_files_for_loop() | set_loop_records('files', $items) |
get_files_for_loop() | get_loop_records('files') |
has_files_for_loop() | has_loop_records('files') |
set_item_types_for_loop() | set_loop_records('item_types', $itemTypes) |
get_item_types_for_loop() | get_loop_records('item_types') |
has_item_types_for_loop() | has_loop_records('item_types') |
get_item_by_id() | get_record_by_id('item', $id) |
get_collection_by_id() | get_record_by_id('collection', $id) |
get_user_by_id() | get_record_by_id('user', $id) |
_select_from_table() | get_table_options() with Zend's formSelect() |
is_odd($num) | n/a; use $num & 1 |
form_error() | n/a; Use Zend Form Validations |
collection_is_featured | metadata('collection', 'featured') |
collection_is_public | metadata('collection', 'public') |
button_to | n/a; use Zend's form helpers or hand-write HTML buttons. |
delete_button | link_to($record, 'delete-confirm', 'Delete', ...) |
Return Value
All functions that previously echoed output will now return the same output. For instance, instead of this:
<?php head(); ?>
you must echo the return value in your script:
<?php echo head(); ?>
Looping Records
All functions that used loop_records()
have been consolidated into one function, loop()
. This includes loop_items()
, loop_files()
, loop_collections()
, etc. The basic structure of a loop has changed as well, so this:
<?php while ($item = loop_items()): ?> <!-- do something --> <?php endwhile; ?>
should now be written like this:
<?php foreach (loop('items') as $item): ?> <!-- do something --> <?php endforeach; ?>
Set/Get/Has Records for Loop
All functions that set, returned, or checked the existence of records for loop have been consolidated into three functions: set_loop_records()
, get_loop_records()
, and has_loop_records()
. So, for example, this:
set_items_for_loop($items); if (has_items_for_loop()) { $items = get_items_for_loop(); }
becomes this:
set_loop_records('items', $items); if (has_loop_records('items')) { $items = get_loop_records('items'); }
Set/Get Current Record
All functions that set or returned the current record have been consolidated into two functions: set_current_record()
and get_current_record()
. So, for example, this:
set_current_item($item); $item = get_current_item();
becomes this:
set_current_record('item', $item); $item = get_current_record('item');
Get Record by ID
All functions that got a record by its ID have been consolidated into one function: get_record_by_id()
. So, for example, this:
$item = get_item_by_id($id);
becomes this:
$item = get_record_by_id('item', $id);
Form Functions
In addition to the above functions, the form functions like text(), label(), select(), radio(), etc. have been removed in Omeka 2.0. Instead, use the corresponding Zend Form Helpers. So, for example, this:
echo text(array('name'=>'title', 'class'=>'textinput', 'id'=>'title'), 'default title', 'Title');
becomes this:
echo $this->formLabel('title', 'Title'); echo $this->formText('title', 'default title', array('class'=>'textinput'));
Or, of course, it might be easier to just write the HTML yourself.
Omeka_Context
We no longer use Omeka_Context
. Instead get the resource via Zend_Registry
. So, for example,
$acl = Omeka_Context::getInstance()->acl;
becomes
$acl = Zend_Registry::get('bootstrap')->getResource('Acl');
Jobs
We highly recommend that all processes that may run longer than a typical web process are sent to a job. The job will mediate the process, reducing the chance of timeout and memory usage errors that can happen even with the best written code. To run a job just write a class that contains the code to run, like so:
class YourJob extends Omeka_Job_AbstractJob { public function perform() { // code to run } }
You have two options on how to run the code: default and long-running. The default way is intended to run processes that, though are more processor-intensive than the typical web process, are usually not in danger of timing out. You can run these processes like so,
Zend_Registry::get('bootstrap')->getResource('jobs')->send('YourJob');
Your other option is intended for processes that will most likely result in a timeout error if run as a normal web script. Processes that import thousands of records or convert hundreds of images are examples of such processes. You can run these processes like so,
Zend_Registry::get('bootstrap')->getResource('jobs')->sendLongRunning('YourJob');
It's important to note that nothing that uses the job system should assume or require synchronicity with the web process. If your process has to be synchronous, it shouldn't be a job.
Phased Loading
In previous versions, long running processes were fired directly through a background process via ProcessDispatcher::startProcess()
, which loaded resources (e.g. Db, Option, Pluginbroker) in phases. Phased loading is now removed in favor of loading resources when needed.
When using the background process adapter for your jobs (typically used for long running jobs), the following resources are pre-loaded for you: Autoloader, Config, Db, Options, Pluginbroker, Plugins, Jobs, Storage, Mail. If you need other resources, load them like so in your job:
Zend_Registry::get('bootstrap')->bootstrap('Db');
Hooks and Filters
New Hooks
- navigation_form
-
search_sql
- public_append_to_home
Renamed Hooks
primary and secondary renamed content and sidebar for item and form show pages
after_upload_file no after_ingest_file
Removed Hooks
insert, update, save_form, and validate hooks have been removed (this supercedes what might be listed below)
In previous versions of Omeka, hook and filter callbacks accepted any number of arguments that could be useful during implementation. Now, in 2.0, we've consolidated these arguments into one array. Because of this change, writing your callbacks will be easier and self-documenting:
// Add a hook. add_plugin_hook('after_save_item', 'my_hook_callback'); // Define the hook callback. Note the single $args array instead of // the list of function arguments used in previous versions. function my_hook_callback($args) { // Extract the arguments only as needed. $item = $args['record']; // Do something. } // Add a filter. add_filter(array('Display', 'Item', 'Dublin Core', 'Title'), 'my_filter_callback'); // Define the filter callback. Note the single $args array instead // of the list of function arguments used in previous versions. function my_filter_callback($title, $args) { // Extract the arguments only as needed. $itemRecord = $args['record']; $titleElementTextRecord = $args['element_text']; // Filter the value and return it... return $title; }
The following hooks have been modified to accommodate the above changes. Hook names are followed by their available arguments (accessed via the $args keys).
Hooks that previously received a request object should now obtain that object within the callback itself like so:
$request = Zend_Controller_Front::getInstance()->getRequest();
- admin_append_to_users_form: form, user
- general_settings_form: form
- browse_tags: tags, for
- *_browse_sql: select, params
- define_routes: router
- html_purifier_form_submission: purifier
- browse_*: records
- show_*: record
- after_upload_file: file, item
- after_ingest_file: file, item
- admin_theme_footer
- admin_theme_header
- public_theme_footer
- public_theme_header
- public_theme_body
- public_theme_page_header
- public_theme_page_content
- before_upload_files: item
- add_*_tag: record, added
- remove_*_tag: record, removed
- make_*_public: record
- make_*_not_public: record
- make_*_featured: record
- make_*_not_featured: record
- items_batch_edit_custom: item, custom
- admin_append_to_collections_form: collection
- admin_append_to_collections_browse_primary: collections
- admin_append_to_collections_show_primary: collection
- admin_append_to_items_form_files: item
- append_to_item_form: item
- admin_append_to_items_browse_primary: items
- admin_append_to_items_batch_edit_form
- admin_append_to_items_show_secondary: item
- admin_append_to_items_show_secondary: item
- admin_append_to_items_form_tags: item
- admin_append_to_items_form_collection: item
- admin_append_to_files_form: file
- admin_append_to_files_show_primary: file
- admin_append_to_files_show_secondary: file
- admin_append_to_item_types_form: item_type
- admin_append_to_item_types_browse_primary: item_types
- admin_append_to_item_types_show_primary: item_type
- config: post
- define_acl: acl
- upgrade: old_version, new_version
- install: plugin_id
- before_save_form_record: record, post
- after_save_form_record: record, post
- before_insert_record: record
- before_update_record: record
- before_save_record: record
- after_insert_record: record
- after_update_record: record
- after_save_record: record
- before_delete_record: record
- after_delete_record: record
- before_save_form_*: record, post
- after_save_form_*: record, post
- before_insert_*: record
- before_update_*: record
- before_save_*: record
- after_insert_*: record
- after_update_*: record
- after_save_*: record
- before_delete_*: record
- after_delete_*: record
The following filters have been modified to accommodate the above changes. Filter names are followed by their available arguments (accessed via the $args keys):
- login_adapter: login_form
- items_batch_edit_error: metadata, custom, item_ids
- storage_path: filename, type
- item_citation: item
- display_search_filters: request_array
- array(Display, Record): record, element_text
- array(Form, Record): input_name_stem, value, record, element
- element_form_display_html_flag: element
- display_file: file, callback, options, wrapper_attributes
- array(Save, Record): record, element
- array(Flatten, Record): post_array, element
- array(Validate, Record): text, record, element
- theme_options: theme_name
- admin_items_form_tabs: item
- define_action_contexts: controller, context_switcher
New Filters
-
<model>_browse_params
-
record_metadata_elements
-
public_navigation_admin_bar
-
search_record_types
-
admin_navigation_global
- admin_dashboard_stats
- admin_dashboard_panels
- item_search_filters
Changed Filters
Old Filter | New Filter |
---|---|
display_setting_* | display_option_* |
array('Form' ....) | array('ElementInput' . . . ) |
Removed Filters
- admin_navigation_items_browse
- admin_navigation_tags
Search
Omeka 2.0 allows any record to be full-text searchable, not just items, but also files, collections, exhibits, etc.
Individual record indexing and bulk-indexing will only work on record types that have been registered via the new "search_record_types" filter:
// Tell Omeka where your search_record_types callback is. add_filter('search_record_types', 'your_search_record_types_callback'); // Accept an array and return an array. function your_search_record_types_callback(array $searchableRecordTypes) { // Register the name of your record class. The key should be the name // of the record class; the value should be the human readable and // internationalized version of the record type. $searchableRecordTypes['YourRecord'] = __('Your Record'); return $searchableRecordTypes; }
Follow this template to make your record searchable:
class YourRecord extends Omeka_Record_AbstractRecord { // Add the search mixin during _initializeMixins() and after any mixins // that can add search text, such as Mixin_ElementText. Doing this // tells Omeka that you want this record to be searchable. protected function _initializeMixins() { // Add the search mixin. $this->_mixins[] = new Mixin_Search($this); } // Use the afterSave() hook to set the record's search text data. protected function afterSave($args) { // A record's search text is public by default, but there are times // when this is not desired, e.g. when an item is marked as // private. Make a check to see if the record is public or private. if ($private) { // Setting the search text to private makes it invisible to // most users. $this->setSearchTextPrivate(); } // Set the record's title. This will be used to identify the record // in the search results. $this->setSearchTextTitle($recordTitle); // Set the record's search text. Records that implement the // Mixin_ElementText mixin during _initializeMixins() will // automatically have all element texts added. Note that you // can add multiple search texts, which simply appends them. $this->addSearchText($recordTitle); $this->addSearchText($recordText); } // The search results need a route to the record show page, so build // a routing array here. You can also assemble the URL yourself using // the URL view helper and return the entire URL as a string. // The abstract Omeka_Record_AbstractRecord class has this method, // so you only need to override it if the route to your record does // something unusual public function getRecordUrl($action) { if ('your-show-action' == $action) { return array( 'module' => 'your-module', 'controller' => 'your-controller', 'action' => $action, 'id' => $this->id, ); } return parent::getRecordUrl($action); } }
Indexing Your Records
Indexing means to collect, parse, and store data to facilitate fast and accurate searches. Omeka will index individual records as they are saved, but there are circumstances when your records are not indexed; for instance, when updating from an earlier version of Omeka.
In 2.0 we've added a feature that lets you index all your records at one time. Just go to the new search settings page on the settings tab and press the index button. This is done using a background process so you may continue administering your site while it indexes. The process may take a while to complete.
Customizing Search Type
Omeka now comes with three search query types: full text, boolean, and exact match. Full text and boolean use MySQL's native full text engine, while exact match searches for all strings identical to the query.
Plugin authors may customize the type of search by implementing the search_query_types
filter. For example, if you want to implement a "ends with" query type that searches for records that contain at least one word that ends with a string:
// Tell Omeka where your search_query_types callback is. add_filter('search_query_types', 'your_search_query_types_callback'); // Accept an array and return an array. function your_search_query_types_callback($queryTypes) { // Register the name of your custom query type. The key should be the // type's GET query value; the values should be the human readable and // internationalized version of the query type. $queryTypes['ends_with'] = __('Ends with'); return $queryTypes; }
Then you must modify the search SQL, like so:
add_plugin_hook('search_sql', 'your_search_sql_callback'); function your_search_sql_callback($args) { $params = $args['params']; if ('ends_with' == $params['query_type']) { $select = $args['select']; // Make sure to reset the existing WHERE clause. $select->reset(Zend_Db_Select::WHERE); $select->where('`text` REGEXP ?', $params['query'] . '[[:>:]]'); } }
Remember that you're searching against an aggregate of all texts associated with a record, not structured data about the record.
Plugin Hooks
The functions that theme developers should use to fire plugin hooks have been removed. Instead of, for example,
echo plugin_append_to_collections_show()
use
fire_plugin_hook('append_to_collections_show');
Testing
Previously, plugins had to add setUp code to make sure that their hooks and filters were reloaded on each test run. This is no longer required. Simply using the Plugin test helper will correctly re-load the hooks and filters every time.
As a side effect, plugin.php can no longer contain declarative code (declaring functions, classes, etc.). This was already an informal rule, with many plugins separating into plugin.php and functions.php files, but now the plugin loader assumes plugins are written this way. Using the Omeka_Plugin_AbstractPlugin class in a separate file will naturally cause this separation (and even allows you to omit plugin.php entirely).
Development Environment
The .htaccess file has added a
setEnv APPLICATION_ENV development
directive. Make sure that you update your .htaccess file.
Admin Views
Example structure of a fieldset within a form:
<div class="field"> <div id="administrator_email-label" class="two columns alpha"><label for="administrator_email" class="required">Administrator Email</label></div> <div class="inputs five columns omega"><input type="text" name="administrator_email" id="administrator_email" value="knguye27@gmu.edu <mailto:value=%22knguye27@gmu.edu>"></div> </div>
Follows these guides: