diff --git a/.travis.yml b/.travis.yml index 3b13734..ef0ee5b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,19 +1,28 @@ +# Travis doesn't pass php 5.3 anymore, neither Omeka 2.1. language: php php: - - 5.4 - - 5.5 - - 5.6 + #- '5.2' + #- '5.3' + - '5.4' + - '5.5' + - '5.6' + - '7.0' + - '7.1' env: #- OMEKA_BRANCH=stable-1.5 - - OMEKA_BRANCH=stable-2.0 + #- OMEKA_BRANCH=stable-2.0 + #- OMEKA_BRANCH=stable-2.1 + - OMEKA_BRANCH=stable-2.2 + - OMEKA_BRANCH=stable-2.3 + - OMEKA_BRANCH=stable-2.4 + - OMEKA_BRANCH=stable-2.5 before_script: - - ./travis_setup.sh - #- export OMEKA_DIR=/home/travis/builds/scholarslab/NeatlineTime/omeka + - ./tests/travis_setup.sh -script: ./travis_tests.sh +script: ./tests/travis_tests.sh notifications: diff --git a/NeatlineTimePlugin.php b/NeatlineTimePlugin.php index 55c497a..6e7a79e 100644 --- a/NeatlineTimePlugin.php +++ b/NeatlineTimePlugin.php @@ -1,19 +1,11 @@ 'simile', + 'neatline_time_internal_assets' => false, + // Can be "browse", "main" or empty. + 'neatline_time_link_to_nav' => 'browse', + 'neatline_time_link_to_nav_main' => '', + 'neatline_time_defaults' => array( + // Numbers are the id of elements of a standard install of Omeka. + 'item_title' => 50, + 'item_description' => 41, + 'item_date' => 40, + 'item_date_end' => '', + 'render_year' => 'january_1', + 'center_date' => '9999-99-99', + 'viewer' => '{}', + ), + ); + + /** + * Timeline initialize hook + */ + public function hookInitialize() + { + add_translation_source(dirname(__FILE__) . '/languages'); + } + /** * Timeline install hook */ public function hookInstall() { $sqlNeatlineTimeline = "CREATE TABLE IF NOT EXISTS `{$this->_db->prefix}neatline_time_timelines` ( - `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, - `title` TINYTEXT COLLATE utf8_unicode_ci DEFAULT NULL, - `description` TEXT COLLATE utf8_unicode_ci DEFAULT NULL, - `query` TEXT COLLATE utf8_unicode_ci DEFAULT NULL, - `creator_id` INT UNSIGNED NOT NULL, + `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `title` TINYTEXT COLLATE utf8_unicode_ci NOT NULL, + `description` TEXT COLLATE utf8_unicode_ci NOT NULL, + `owner_id` INT(10) UNSIGNED NOT NULL DEFAULT '0', `public` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0', `featured` TINYINT(1) NOT NULL DEFAULT '0', - `center_date` date NOT NULL default '0000-00-00', - `added` timestamp NOT NULL default '0000-00-00 00:00:00', + `parameters` TEXT COLLATE utf8_unicode_ci NOT NULL, + `query` TEXT COLLATE utf8_unicode_ci NOT NULL, + `added` timestamp NOT NULL default '2000-01-01 00:00:00', `modified` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) - ) ENGINE=innodb DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci"; + PRIMARY KEY (`id`), + KEY `public` (`public`), + KEY `featured` (`featured`), + KEY `owner_id` (`owner_id`) + ) ENGINE=innodb DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci"; $this->_db->query($sqlNeatlineTimeline); - $this->setDefaultOptions(); - - } - - /** - * Timeline uninstall hook - */ - public function hookUninstall() - { - - $sql = "DROP TABLE IF EXISTS - `{$this->_db->prefix}neatline_time_timelines`"; - - $this->_db->query($sql); - - delete_option('neatlinetime'); - + $this->_options['neatline_time_defaults'] = json_encode($this->_options['neatline_time_defaults']); + $this->_installOptions(); } /** @@ -91,47 +104,36 @@ public function hookUpgrade($args) { $oldVersion = $args['old_version']; $newVersion = $args['new_version']; + $db = $this->_db; - // Earlier than version 1.1. - if (version_compare($oldVersion, '1.1', '<')) { - if (!get_option('neatlinetime')) { - $this->setDefaultOptions(); - } - } + require_once dirname(__FILE__) . DIRECTORY_SEPARATOR . 'upgrade.php'; + } - if (version_compare($oldVersion, '2.0.2', '<') && version_compare($oldVersion, '2.0', '>') ) { - if ($timelines = get_records('NeatlineTimeTimeline')) { - foreach ($timelines as $timeline) { - $query = unserialize($timeline->query); - while (!is_array($query)) { - $query = unserialize($query); - } - $timeline->query = serialize($query); - $timeline->save(); - } - } - } + /** + * Timeline uninstall hook + */ + public function hookUninstall() + { + $sql = "DROP TABLE IF EXISTS + `{$this->_db->prefix}neatline_time_timelines`"; - if (version_compare($oldversion, '2.1', '<')) { - $rows = $this->_db->query( - "show columns from {$this->_db->prefix}neatline_time_timelines where field='center_date';" - ); + $this->_db->query($sql); - if ($rows->rowCount() === 0) { - $sqlNeatlineTimeline = "ALTER TABLE `{$this->_db->prefix}neatline_time_timelines` - ADD COLUMN `center_date` date NOT NULL default '0000-00-00'"; + // Remove old options. + delete_option('neatlinetime'); + delete_option('neatline_time_render_year'); - $this->_db->query($sqlNeatlineTimeline); - } - } + $this->_uninstallOptions(); } /** - * Timeline initialize hook + * Display the uninstall message. */ - public function hookInitialize() + public function hookUninstallMessage() { - add_translation_source(dirname(__FILE__) . '/languages'); + $string = __('Warning: Uninstalling the Neatline Time plugin + will remove all custom Timeline records.'); + echo '

' . $string . '

'; } /** @@ -144,12 +146,14 @@ public function hookDefineAcl($args) $acl->addResource('NeatlineTime_Timelines'); // All everyone access to browse, show, and items. - $acl->allow(null, 'NeatlineTime_Timelines', array('show', 'browse', 'items')); - - $acl->allow('researcher', 'NeatlineTime_Timelines', 'showNotPublic'); - $acl->allow('contributor', 'NeatlineTime_Timelines', array('add', 'editSelf', 'querySelf', 'itemsSelf', 'deleteSelf', 'showNotPublic')); - $acl->allow(array('super', 'admin', 'contributor', 'researcher'), 'NeatlineTime_Timelines', array('edit', 'query', 'items', 'delete'), new Omeka_Acl_Assert_Ownership); - + $acl->allow(null, 'NeatlineTime_Timelines', + array('show', 'browse', 'items')); + $acl->allow('researcher', 'NeatlineTime_Timelines', + 'showNotPublic'); + $acl->allow('contributor', 'NeatlineTime_Timelines', + array('add', 'editSelf', 'querySelf', 'itemsSelf', 'deleteSelf', 'showNotPublic')); + $acl->allow(array('super', 'admin', 'contributor', 'researcher'), 'NeatlineTime_Timelines', + array('edit', 'query', 'items', 'delete'), new Omeka_Acl_Assert_Ownership); } /** @@ -164,102 +168,114 @@ public function hookDefineRoutes($args) new Zend_Controller_Router_Route( 'neatline-time/timelines/:action/:id', array( - 'module' => 'neatline-time', - 'controller' => 'timelines' - ), - array('id' => '\d+') - ) - ); + 'module' => 'neatline-time', + 'controller' => 'timelines' + ), + array('id' => '\d+') + ) + ); $router->addRoute( 'timelineDefaultRoute', new Zend_Controller_Router_Route( 'neatline-time/timelines/:action', array( - 'module' => 'neatline-time', - 'controller' => 'timelines' - ) + 'module' => 'neatline-time', + 'controller' => 'timelines' ) - ); + ) + ); $router->addRoute( 'timelineRedirectRoute', new Zend_Controller_Router_Route( 'neatline-time', array( - 'module' => 'neatline-time', - 'controller' => 'timelines', - 'action' => 'browse' - ) + 'module' => 'neatline-time', + 'controller' => 'timelines', + 'action' => 'browse' ) - ); + ) + ); $router->addRoute( 'timelinePaginationRoute', new Zend_Controller_Router_Route( 'neatline-time/timelines/:page', array( - 'module' => 'neatline-time', - 'controller' => 'timelines', - 'action' => 'browse', - 'page' => '1' - ), - array('page' => '\d+') - ) - ); - + 'module' => 'neatline-time', + 'controller' => 'timelines', + 'action' => 'browse', + 'page' => '1' + ), + array('page' => '\d+') + ) + ); } /** - * Timeline admin_append_to_plugin_uninstall_message hook + * Shows plugin configuration page. + * + * @return void */ - public function hookAdminAppendToPluginUninstallMessage() + public function hookConfigForm($args) { - $string = __('Warning: Uninstalling the Neatline Time plugin - will remove all custom Timeline records.'); - - echo '

'.$string.'

'; - + $defaults = get_option('neatline_time_defaults'); + $defaults = json_decode($defaults, true); + $defaults = empty($defaults) + // Set default parameters. + ? $this->_options['neatline_time_defaults'] + // Add possible new default parameters to avoid a notice. + : array_merge($this->_options['neatline_time_defaults'], $defaults); + + $view = $args['view']; + echo $view->partial( + 'plugins/neatline-time-config-form.php', + array( + 'defaults' => $defaults, + )); } /** - * Filter the items_browse_sql to return only items that have a non-empty - * value for the DC:Date field, when using the neatlinetime-json context. - * Uses the ItemSearch model (models/ItemSearch.php) to add the check for - * a non-empty DC:Date. + * Processes the configuration form. * - * @param Omeka_Db_Select $select + * @return void */ - public function hookItemBrowseSql() + public function hookConfig($args) { - - $context = Zend_Controller_Action_HelperBroker::getStaticHelper('ContextSwitch')->getCurrentContext(); - if ($context == 'neatlinetime-json') { - $search = new ItemSearch($select); - $newParams[0]['element_id'] = neatlinetime_get_option('item_date'); - $newParams[0]['type'] = 'is not empty'; - $search->advanced($newParams); + $post = $args['post']; + foreach ($this->_options as $optionKey => $optionValue) { + if (isset($post[$optionKey])) { + if (is_array($optionValue)) { + $post[$optionKey] = json_encode($post[$optionKey]); + } + set_option($optionKey, $post[$optionKey]); + } } - } - /** - * Plugin configuration. - */ - public function hookConfig() + public function hookAdminHead($args) { - $options = $_POST; - unset($options['install_plugin']); - $options = serialize($options); - set_option('neatlinetime', $options); + $requestParams = Zend_Controller_Front::getInstance()->getRequest()->getParams(); + $module = isset($requestParams['module']) ? $requestParams['module'] : 'default'; + $controller = isset($requestParams['controller']) ? $requestParams['controller'] : 'index'; + $action = isset($requestParams['action']) ? $requestParams['action'] : 'index'; + if ($module != 'neatline-time' || $controller != 'timelines' || $action != 'show') { + return; + } + $this->_head($args); } - /** - * Plugin configuration form. - */ - public function hookConfigForm() + public function hookPublicHead($args) { - include 'config_form.php'; + $requestParams = Zend_Controller_Front::getInstance()->getRequest()->getParams(); + $module = isset($requestParams['module']) ? $requestParams['module'] : 'default'; + $controller = isset($requestParams['controller']) ? $requestParams['controller'] : 'index'; + $action = isset($requestParams['action']) ? $requestParams['action'] : 'index'; + if ($module != 'neatline-time' || $controller != 'timelines' || $action != 'show') { + return; + } + $this->_head($args); } /** @@ -268,9 +284,58 @@ public function hookConfigForm() public function hookExhibitBuilderPageHead($args) { if (array_key_exists('neatline-time', $args['layouts'])) { - queue_timeline_assets(); + $this->_head($args); } } + + /** + * Load all assets. + * + * Replace queue_timeline_assets() + * + * @return void + */ + private function _head($args) + { + $library = get_option('neatline_time_library'); + if ($library == 'knightlab') { + queue_css_url('//cdn.knightlab.com/libs/timeline3/latest/css/timeline.css'); + queue_js_url('//cdn.knightlab.com/libs/timeline3/latest/js/timeline.js'); + return; + } + + // Default simile library. + queue_css_file('neatlinetime-timeline'); + + queue_js_file('neatline-time-scripts'); + + $internalAssets = get_option('neatline_time_internal_assets'); + if ($internalAssets) { + $useInternalJs = true; + } else { + // Check useInternalJavascripts in config.ini. + $config = Zend_Registry::get('bootstrap')->getResource('Config'); + $useInternalJs = isset($config->theme->useInternalJavascripts) + ? (bool) $config->theme->useInternalJavascripts + : false; + $useInternalJs = isset($config->theme->useInternalAssets) + ? (bool) $config->theme->useInternalAssets + : $useInternalJs; + } + + if ($useInternalJs) { + $timelineVariables = 'Timeline_ajax_url="' . src('simile-ajax-api.js', 'javascripts/simile/ajax-api') . '"; + Timeline_urlPrefix="' . dirname(src('timeline-api.js', 'javascripts/simile/timeline-api')) . '/"; + Timeline_parameters="bundle=true";'; + queue_js_string($timelineVariables); + queue_js_file('timeline-api', 'javascripts/simile/timeline-api'); + queue_js_string('SimileAjax.History.enabled = false; // window.jQuery = SimileAjax.jQuery;'); + } else { + queue_js_url('//api.simile-widgets.org/timeline/2.3.1/timeline-api.js?bundle=true'); + queue_js_string('SimileAjax.History.enabled = false; window.jQuery = SimileAjax.jQuery;'); + } + } + /** * Timeline admin_navigation_main filter. * @@ -281,7 +346,6 @@ public function hookExhibitBuilderPageHead($args) */ public function filterAdminNavigationMain($nav) { - $nav[] = array( 'label' => __('Neatline Time'), 'uri' => url('neatline-time'), @@ -289,7 +353,6 @@ public function filterAdminNavigationMain($nav) 'privilege' => 'browse' ); return $nav; - } /** @@ -302,13 +365,35 @@ public function filterAdminNavigationMain($nav) */ public function filterPublicNavigationMain($nav) { - $nav[] = array( 'label' => __('Neatline Time'), 'uri' => url('neatline-time') ); return $nav; + } + public function filterPublicNavigationItems($navArray) + { + $linkToNav = get_option('neatline_time_link_to_nav'); + switch ($linkToNav) { + case 'browse': + $navArray['Browse Timeline'] = array( + 'label' => __('Browse Timelines'), + 'uri' => url('neatline-time'), + ); + break; + case 'main': + $linkToNavMain = get_option('neatline_time_link_to_nav_main'); + if ($linkToNavMain) { + $navArray['Browse Timeline'] = array( + 'label' => __('Browse Timeline'), + 'uri' => url('neatline-time/timelines/show/' . $linkToNavMain), + ); + } + break; + default: + } + return $navArray; } /** @@ -316,14 +401,11 @@ public function filterPublicNavigationMain($nav) */ public function filterResponseContexts($contexts) { - $contexts['neatlinetime-json'] = array( 'suffix' => 'neatlinetime-json', - 'headers' => array('Content-Type' => 'text/javascript') + 'headers' => array('Content-Type' => 'application/json') ); - return $contexts; - } /** @@ -332,13 +414,10 @@ public function filterResponseContexts($contexts) */ public function filterActionContexts($contexts, $args) { - if ($args['controller'] instanceof NeatlineTime_TimelinesController) { $contexts['items'][''] = 'neatlinetime-json'; } - return $contexts; - } /** @@ -356,18 +435,39 @@ public function filterExhibitLayouts($layouts) return $layouts; } - protected function setDefaultOptions() + /** + * Filter items browse params. + * + * @param array $params + * @return array + */ + public function filterItemsBrowseParams($params) { - $options = array(); - $fields = array('Title', 'Description', 'Date'); - - foreach ($fields as $field) { - $key = 'item_'.strtolower($field); - $element = $this->_db->getTable('Element')->findByElementSetNameAndElementName("Dublin Core", "$field"); - $options[$key] = $element->id; + // Filter the items to return only items that have a non-empty value for + // the DC:Date or the specified field when using the neatlinetime-json + // context. + $context = Zend_Controller_Action_HelperBroker::getStaticHelper('ContextSwitch')->getCurrentContext(); + if ($context != 'neatlinetime-json') { + return $params; } - - $options = serialize($options); - set_option('neatlinetime', $options); + // Check if this is a request (don't filter if this a background process). + $request = Zend_Controller_Front::getInstance()->getRequest(); + if (empty($request)) { + return $params; + } + $id = (integer) $request->getParam('id'); + if (empty($id)) { + return $params; + } + $timeline = $this->_db->getTable('NeatlineTime_Timeline')->find($id); + if (empty($timeline)) { + return $params; + } + $params['advanced'][] = array( + 'joiner' => 'and', + 'element_id' => $timeline->getProperty('item_date'), + 'type' =>'is not empty', + ); + return $params; } } diff --git a/README.md b/README.md index 97b39c1..26f6372 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,79 @@ -# NeatlineTime plugin for Omeka +NeatlineTime (plugin for Omeka) +=============================== -[![Build -Status](https://secure.travis-ci.org/scholarslab/NeatlineTime.png?branch=develop,master)](http://travis-ci.org/scholarslab/NeatlineTime) +[![Build Status](https://travis-ci.org/scholarslab/NeatlineTime.svg?branch=develop,master)](https://travis-ci.org/scholarslab/NeatlineTime) -The NeatlineTime plugin, by the [Scholars' Lab][scholarslab] at the University of Virginia Library, allows you to create timelines for the [Omeka][omeka] publishing platform. It uses the [SIMILE Timeline plugin][simile-timeline]. +The NeatlineTime plugin, by the [Scholars' Lab][scholarslab] at the University +of Virginia Library, allows you to create timelines for the [Omeka][omeka] +publishing platform. It uses the [SIMILE Timeline plugin][simile-timeline] or +the [Knightlab timeline]. -## Installation +This plugin is upgradable to [Omeka S] via the plugin [Upgrade to Omeka S], that +installs the module [Timeline for Omeka S]. -1. Upload the 'NeatlineTime' plugin directory to your Omeka installation's 'plugins' directory. See [Installing a Plugin][installing-a-plugin]. + +Installation +------------ + +1. Upload the 'NeatlineTime' plugin directory to your Omeka installation's +'plugins' directory. See [Installing a Plugin][installing-a-plugin]. 2. Activate the plugin from the Admin → Settings → Plugins page. 3. Configure the plugin to choose which fields you want the plugin to use on - the timeline. + the timeline by default. - * Item Date: The field you would like to use for item dates on the - timeline. The default is DC:Date. * Item Title: The field you would like displayed for the item's title in its information bubble. The default is DC:Title * Item Description: The field you would like displayed for the item's description in its information bubble. The default is DC:Description. + * Item Date: The field you would like to use for item dates on the + timeline. The default is DC:Date. + * Render Year: Date entered as a single number, like "1066", can be skipped, + plotted as a single event or marked as a full year. + * Center Date: The date that is displayed by the viewer when loaded. It can + be any date with the format (YYYY-MM-DD). An empty string means now, a + "0000-00-00" the earliest date and "9999-99-99" the latest date. + +All these parameters can be customized for each timeline. + +Note: If Omeka is https, if external assets are used, and if the Simile library +is used, the library will not load on recent browsers, because the online +library contains an url with unsecure http. In that case, you need to set the +option "Use Internal library for Simile". -## Usage +Usage +----- -Once installed, NeatlineTime will add a tab to the Omeka admin panel. From here, you can browse existing timelines, and add, edit, and delete timelines. +Once installed, NeatlineTime will add a tab to the Omeka admin panel. From here, +you can browse existing timelines, and add, edit, and delete timelines. -Uninstalling the plugin will only remove timelines added to your Omeka archive, not any items displayed on those timelines. +Uninstalling the plugin will only remove timelines added to your Omeka archive, +not any items displayed on those timelines. ### Add a Timeline Creating a timeline is a two-step process: -1. From the admin → NeatlineTime page, click the "Add New Timeline" button to begin creating a timeline. +1. From the admin → NeatlineTime page, click the "Add New Timeline" button to + begin creating a timeline. ![Browse Timelines](http://neatline.org/wp-content/uploads/2014/01/neatlinetime-browse.png) -2. Give your timeline a title and description, and choose whether you wish to make the timeline public and featured. Save your changes. +2. Give your timeline a title and description, and choose whether you wish to + make the timeline public and featured. Save your changes. ![Add a Timeline Form](http://neatline.org/wp-content/uploads/2014/01/neatlinetime-add-timeline.png) -3. To choose which items appear on your timeline, click the "Edit Query" link beside your existing timeline. +3. To choose which items appear on your timeline, click the "Edit Query" link + beside your existing timeline. ![Edit Query Link](http://neatline.org/wp-content/uploads/2014/01/neatlinetime-timeline-saved.png) -4. This will take you to a form similar to Omeka's advanced search form. From here, you can perform a search for any items in your archive, and if those items contain a valid date in their Dublin Core:Date field, they will be displayed on the timeline. +4. This will take you to a form similar to Omeka's advanced search form. From + here, you can perform a search for any items in your archive, and if those + items contain a valid date in their Dublin Core:Date field, they will be + displayed on the timeline. ![Edit Query](http://neatline.org/wp-content/uploads/2014/01/neatlinetime-item-query.png) @@ -51,9 +81,10 @@ Creating a timeline is a two-step process: ![Timeline](http://neatline.org/wp-content/uploads/2014/01/neatlinetime-admin-show.png) -#### Dates for Items +### Dates for Items -NeatlineTime will attempt to convert the value for a date string into an ISO-8601 date format. Some example date values you can use: +NeatlineTime will attempt to convert the value for a date string into an +ISO-8601 date format. Some example date values you can use: * January 1, 2012 * 2012-01-01 @@ -64,8 +95,6 @@ To denote spans of time, separate the start and end date with a '/': * January 1, 2012/February 1, 2012 -NeatlineTime doesn't accept just years (*1066*, for example) because it's not clear what that means. Should that translate to the range *January 1, 1066/December 31, 1066*? Should it be *January 1, 1066*? *June 31, 1066*? Instead of us picking an arbitrary point in the year or marking the entire year, we simply ask that you be more specific. - NeatlineTime handles dates with years shorter than 4 digits. For these you'll need to pad the years with enough zeros to make them have four digits. For example, `476` should be written `0476`. @@ -81,41 +110,138 @@ So here are some more examples of dates. * -0002-01-01 * -2013-01-01 +When a date is a single number, like "1066", a parameter in the config page +allows to choose its rendering: + + * skip the record (default) + * 1st January + * 1st July + * full year (range period) + +This parameter applies with a range of dates too, for example "1939/1945". + +In all cases, it's recommended to follow the standard [ISO 8601] as much as +possible and to be as specific as possible. + +### Parameters of the viewer + +Some parameters of the viewer may be customized for each timeline. Currently, +only the `CenterDate` and the `bandInfos` are managed for the Simile timeline. +The default is automatically included when the field is empty. + +```javascript +{ +bandInfos: + [ + { + width: "80%", + intervalUnit: Timeline.DateTime.MONTH, + intervalPixels: 100, + zoomIndex: 10, + zoomSteps: new Array( + {pixelsPerInterval: 280, unit: Timeline.DateTime.HOUR}, + {pixelsPerInterval: 140, unit: Timeline.DateTime.HOUR}, + {pixelsPerInterval: 70, unit: Timeline.DateTime.HOUR}, + {pixelsPerInterval: 35, unit: Timeline.DateTime.HOUR}, + {pixelsPerInterval: 400, unit: Timeline.DateTime.DAY}, + {pixelsPerInterval: 200, unit: Timeline.DateTime.DAY}, + {pixelsPerInterval: 100, unit: Timeline.DateTime.DAY}, + {pixelsPerInterval: 50, unit: Timeline.DateTime.DAY}, + {pixelsPerInterval: 400, unit: Timeline.DateTime.MONTH}, + {pixelsPerInterval: 200, unit: Timeline.DateTime.MONTH}, + {pixelsPerInterval: 100, unit: Timeline.DateTime.MONTH} // DEFAULT zoomIndex + ) + }, + { + overview: true, + width: "20%", + intervalUnit: Timeline.DateTime.YEAR, + intervalPixels: 200 + } + ] +} +``` + + ### Browsing timelines -You can browse existing timelines by clicking on the "Browse Timelines" from your public theme, or the "NeatlineTime" tab in the admin panel. +You can browse existing timelines by clicking on the "Browse Timelines" from +your public theme, or the "NeatlineTime" tab in the admin panel. ### Viewing specific timelines -You can always see your timeline by click the title of the timeline in the admin. The URL for your timelines will be 'neatline-time/timelines/show/[id]', where [id] is the ID number for your timeline. +You can always see your timeline by click the title of the timeline in the +admin. The URL for your timelines will be 'neatline-time/timelines/show/[id]', +where [id] is the ID number for your timeline. ![Public Show](http://neatline.org/wp-content/uploads/2014/01/neatlinetime-public-show.png) ### Modifying theme templates for Neatline Time -Neatline Time contains theme templates that control how its various pages are displayed in your public theme. As with other Omeka plugins, you can override these using the instructions on the [Theming Plugin Pages][themeing-plugin-pages] codex page. +Neatline Time contains theme templates that control how its various pages are +displayed in your public theme. As with other Omeka plugins, you can override +these using the instructions on the [Theming Plugin Pages][themeing-plugin-pages] +codex page. The template files available in NeatlineTime include: * timelines/browse.php - The template for browsing existing timelines. * timelines/show.php - The template for showing a specific timeline. -## Contributing to the Project +### Modifying the viewer + +The template file used to load the timeline is `views/shared/javascripts/neatline-time-script.js`. + +You can copy it in your themes/my_theme/javascripts folder to customize it. The +same for the default css. See the main [wiki][simile-timeline], an [example of use] +with Neatline, and the [examples] of customization on the wiki. + + +Contributing to the Project +--------------------------- ### Feedback -We rely on the [Github issues tracker][issues] for feedback on issues and improvements. +We rely on the [Github issues tracker][issues] for feedback on issues and +improvements. ### Patches/Pull Requests * Fork the project. * Make your feature addition or bug fix. -* Add tests for it, and make sure all the tests pass. This is important so we don't unknowingly break your changes in a future release. If you're fixing a bug, it helps us to verify that your bug does in fact exist. Both NeatlineTime and Omeka use [PHPUnit][phpunit] to ensure the quality of the software. +* Add tests for it, and make sure all the tests pass. This is important so we + don't unknowingly break your changes in a future release. If you're fixing a + bug, it helps us to verify that your bug does in fact exist. Both NeatlineTime + and Omeka use [PHPUnit][phpunit] to ensure the quality of the software. * Commit your changes to your own fork. -* Send us a pull request, with a clear explanation of the changes. Bonus - points for topic branches. +* Send us a pull request, with a clear explanation of the changes. Bonus points + for topic branches. -## Credits + +Warning +------- + +Use it at your own risk. + +It’s always recommended to backup your files and your databases and to check +your archives regularly so you can roll back if needed. + + +Troubleshooting +--------------- + +See online issues on the plugin [issues] page on GitHub. + + +License +------- + +This plugin is published under [Apache licence v2]. +See [LICENSE][license] for more information. + + +Credits +------- ### Translations @@ -124,15 +250,32 @@ We rely on the [Github issues tracker][issues] for feedback on issues and improv * Oguljan Reyimbaeva (Russian) * Katina Rogers (French) -## Copyright +### Contact + +* Scholar's Lab (see [ScholarLab] on GitHub) +* Daniel Berthereau (see [Daniel-KM] on GitHub) + +### Copyright + +* Copyright (c) 2010–2012 The Board and Visitors of the University of Virginia. +* Copyright Daniel Berthereau, 2016-2018 -Copyright (c) 2010–2012 The Board and Visitors of the University of Virginia. See [LICENSE][license] for more information. -[scholarslab]: http://scholarslab.org/ +[scholarslab]: http://scholarslab.org [omeka]: http://omeka.org [simile-timeline]: http://www.simile-widgets.org/wiki/Timeline +[Knightlab timeline]: https://timeline.knightlab.com +[Omeka S]: https://omeka.org/s +[Upgrade to Omeka S]: https://github.com/Daniel-KM/Omeka-plugin-UpgradeToOmekaS +[Timeline for Omeka S]: https://github.com/Daniel-KM/Omeka-S-module-Timeline [installing-a-plugin]: http://omeka.org/codex/Installing_a_Plugin +[example of use]: https://docs.neatline.org/working-with-the-simile-timeline-widget.html +[examples]: http://www.simile-widgets.org/timeline/examples/index.html +[Apache licence v2]: https://www.apache.org/licenses/LICENSE-2.0.html [license]: http://www.apache.org/licenses/LICENSE-2.0.html "Apache License, Version 2.0" [issues]: http://github.com/scholarslab/NeatlineTime/issues/ "Issues for Neatline Time" [phpunit]: http://www.phpunit.de/manual/current/en/ "PHP Unit" +[ISO 8601]: http://www.iso.org/iso/home/standards/iso8601.htm [themeing-plugin-pages]: http://omeka.org/codex/Theming_Plugin_Pages "Theming Plugin Pages" +[ScholarLab]: https://github.com/scholarslab "Scholar's Lab" +[Daniel-KM]: https://github.com/Daniel-KM "Daniel Berthereau" diff --git a/Rakefile b/Rakefile index 838a5d7..cd8ba52 100644 --- a/Rakefile +++ b/Rakefile @@ -5,10 +5,23 @@ require 'tempfile' require 'inifile' task :default => [ - # 'php:unit', - # 'jasmine:ci', + 'php:unit', ] +namespace :php do + desc 'Run unit tests.' + task :unit, [:filter] do |t, args| + filter_by = args[:filter] + if filter_by.nil? then + filter_params = "" + else + filter_params = " --filter #{filter_by}" + end + + sh %{cd tests/ && phpunit --configuration phpunit.xml #{filter_params}} + end +end + class PackageTask < Rake::PackageTask def package_dir_path() "#{package_dir}/#{@name}" diff --git a/config_form.php b/config_form.php deleted file mode 100644 index 190cc17..0000000 --- a/config_form.php +++ /dev/null @@ -1,36 +0,0 @@ -
- -
- - -

- -

-
-
- -
- -
- - - -

- -

-
-
- -
- -
- - -

- -

-
-
diff --git a/controllers/IndexController.php b/controllers/IndexController.php new file mode 100644 index 0000000..cc513f0 --- /dev/null +++ b/controllers/IndexController.php @@ -0,0 +1,16 @@ +_helper->redirector('browse', 'timelines'); + } +} diff --git a/controllers/TimelinesController.php b/controllers/TimelinesController.php index aa1e372..8f25d0e 100644 --- a/controllers/TimelinesController.php +++ b/controllers/TimelinesController.php @@ -4,6 +4,15 @@ */ class NeatlineTime_TimelinesController extends Omeka_Controller_AbstractActionController { + /** + * The number of records to browse per page. + * + * @var string + */ + protected $_browseRecordsPerPage = 100; + + protected $_autoCsrfProtection = true; + /** * Initialization. * @@ -12,16 +21,28 @@ class NeatlineTime_TimelinesController extends Omeka_Controller_AbstractActionCo */ public function init() { - $this->_helper->db->setDefaultModelName('NeatlineTimeTimeline'); + $this->_helper->db->setDefaultModelName('NeatlineTime_Timeline'); + } - $this->_browseRecordsPerPage = get_option('per_page_admin'); + /** + * The browse action. + * + */ + public function browseAction() + { + if (!$this->getParam('sort_field')) { + $this->setParam('sort_field', 'added'); + $this->setParam('sort_dir', 'd'); + } + parent::browseAction(); } public function addAction() { - require_once NEATLINE_TIME_FORMS_DIR . '/timeline.php'; - $form = new NeatlineTime_Form_Timeline; + $form = new NeatlineTime_Form_TimelineAdd; + $defaults = json_decode(get_option('neatline_time_defaults'), true) ?: array(); + $form->setDefaults($defaults); $this->view->form = $form; parent::addAction(); } @@ -30,13 +51,18 @@ public function editAction() { $timeline = $this->_helper->db->findById(); - require_once NEATLINE_TIME_FORMS_DIR . '/timeline.php'; - $form = new NeatlineTime_Form_Timeline; - $form->setDefaults(array('title' => $timeline->title, 'description' => $timeline->description, 'public' => $timeline->public, 'featured' => $timeline->featured, 'center_date' => $timeline->center_date)); - + $form = new NeatlineTime_Form_TimelineAdd; + // Set the existings values. + $parameters = $timeline->getParameters(); + $existing = array( + 'title' => $timeline->title, + 'description' => $timeline->description, + 'public' => $timeline->public, + 'featured' => $timeline->featured, + ); + $form->setDefaults(array_merge($parameters, $existing)); $this->view->form = $form; parent::editAction(); - } public function queryAction() @@ -44,17 +70,17 @@ public function queryAction() $timeline = $this->_helper->db->findById(); if(isset($_GET['search'])) { - $timeline->query = $_GET; + $timeline->setQuery($_GET); $timeline->save(); $this->_helper->flashMessenger($this->_getEditSuccessMessage($timeline), 'success'); $this->_helper->redirector->gotoRoute(array('action' => 'show')); } else { - $queryArray = unserialize($timeline->query); + $query = $timeline->getQuery(); // Some parts of the advanced search check $_GET, others check // $_REQUEST, so we set both to be able to edit a previous query. - $_GET = $queryArray; - $_REQUEST = $queryArray; + $_GET = $query; + $_REQUEST = $query; } $this->view->neatline_time_timeline = $timeline; @@ -63,10 +89,9 @@ public function queryAction() public function itemsAction() { $timeline = $this->_helper->db->findById(); + $items = $timeline->getItems(); - $query = $timeline->query ? unserialize($timeline->query) : array(); - $items = get_db()->getTable('Item')->findBy($query, null); - + $this->getResponse()->setHeader('Content-Type', 'application/json'); $this->view->neatline_time_timeline = $timeline; $this->view->items = $items; } @@ -102,5 +127,4 @@ protected function _getDeleteConfirmMessage($timeline) { return __('This will delete the timeline "%s" and its associated metadata. This will not delete any items associated with this timeline.', $timeline->title); } - } diff --git a/forms/timeline.php b/forms/timeline.php deleted file mode 100644 index e7c4af4..0000000 --- a/forms/timeline.php +++ /dev/null @@ -1,69 +0,0 @@ -setMethod('post'); - $this->setAttrib('id', 'timeline-form'); - - // Title - $this->addElement('text', 'title', array( - 'label' => __('Title'), - 'description' => __('A title for your timeline.') - )); - - // Description - $this->addElement('textarea', 'description', array( - 'label' => __('Description'), - 'description' => __('A description for your timeline.'), - 'attribs' => array('class' => 'html-editor', 'rows' => '15') - )); - - // Public/Not Public - $this->addElement('select', 'public', array( - 'label' => __('Status'), - 'description' => __('Whether the timeline is public or not.'), - 'multiOptions' => array('0' => 'Not Public', '1' => 'Public') - )); - - // Featured/Not Featured - $this->addElement('select', 'featured', array( - 'label' => __('Featured'), - 'description' => __('Whether the timeline is featured or not.'), - 'multiOptions' => array('0' => 'Not Featured', '1' => 'Featured') - )); - - - // Set the center date for the timeline - $this->addElement('text', 'center_date', array( - 'label' => __('Center Date'), - 'description' => __('Set the center date of your timeline. Please use format YYYY-MM-DD.'), - 'validator' => array('date') - )); - - // Submit - $this->addElement('submit', 'submit', array( - 'label' => __('Save Timeline') - )); - - // Group the title, description, and public fields - $this->addDisplayGroup( - array('title', - 'description', - 'public', - 'featured', - 'center_date', - ), - 'timeline_info' - ); - - // Add the submit to a separate display group. - $this->addDisplayGroup(array('submit'), 'timeline_submit'); - } - -} diff --git a/helpers/NeatlineTimeFunctions.php b/helpers/NeatlineTimeFunctions.php deleted file mode 100644 index baa1e13..0000000 --- a/helpers/NeatlineTimeFunctions.php +++ /dev/null @@ -1,245 +0,0 @@ - tag. (optional) - * @param string The action for the link. Default is 'show'. - * @param NeatlineTimeTimeline|null - * @return string HTML - * @deprecated - **/ -function link_to_timeline($text = null, $props = array(), $action = 'show', $timeline = null) -{ - - $timeline = $timeline ? $timeline : get_current_record('neatline_time_timeline'); - - $text = $text ? $text : $timeline->title; - - return link_to($timeline, $action, $text, $props); - -} - -/** - * Queues JavaScript and CSS for NeatlineTime in the page header. - * - * @since 1.0 - * @return void. - */ -function queue_timeline_assets() -{ - $headScript = get_view()->headScript(); - $headScript->appendFile(src('neatline-time-scripts.js', 'javascripts')); - - // Check useInternalJavascripts in config.ini. - $config = Zend_Registry::get('bootstrap')->getResource('Config'); - $useInternalJs = isset($config->theme->useInternalJavascripts) - ? (bool) $config->theme->useInternalJavascripts - : false; - - if ($useInternalJs) { - $timelineVariables = 'Timeline_ajax_url="'.src('simile-ajax-api.js', 'javascripts/simile/ajax-api').'"; ' - . 'Timeline_urlPrefix="'.dirname(src('timeline-api.js', 'javascripts/simile/timeline-api')).'/"; ' - . 'Timeline_parameters="bundle=true";'; - - $headScript->appendScript($timelineVariables); - $headScript->appendFile(src('timeline-api.js', 'javascripts/simile/timeline-api')); - } else { - $headScript->appendFile('http://api.simile-widgets.org/timeline/2.3.1/timeline-api.js?bundle=true'); - } - - $headScript->appendScript('SimileAjax.History.enabled = false; window.jQuery = SimileAjax.jQuery'); - - queue_css_file('neatlinetime-timeline'); -} - -/** - * Returns the URI for a timeline's json output. - * - * @since 1.0 - * @param NeatlineTimeTimeline|null - * @return string URL the items output uri for the neatlinetime-json output. - */ -function neatlinetime_json_uri_for_timeline($timeline = null) -{ - $timeline = $timeline ? $timeline : get_current_record('neatline_time_timeline'); - return record_url($timeline, 'items') . '?output=neatlinetime-json'; -} - -/** - * Construct id for container div. - * - * @since 1.0 - * @param NeatlineTimeTimeline|null - * @return string HTML - */ -function neatlinetime_timeline_id($timeline = null) -{ - $timeline = $timeline ? $timeline : get_current_record('neatline_time_timeline'); - return text_to_id(html_escape($timeline->title) . ' ' . $timeline->id, 'neatlinetime'); -} - -/** - * Displays random featured timelines - * - * @param int Maximum number of random featured timelines to display. - * @return string HTML - */ -function neatlinetime_display_random_featured_timelines($num = 1) { - $html = ''; - - $timelines = get_db()->getTable('NeatlineTimeTimeline')->findBy(array('random' => 1, 'featured' => 1), $num); - - if ($timelines) { - foreach ($timelines as $timeline) { - $html .= '

' . link_to_timeline(null, array(), 'show', $timeline) . '

' - . '
' - . timeline('description', array('snippet' => 150), $timeline) - . '
'; - } - return $html; - } -} - -/** - * Returns a string for neatline_json 'classname' attribute for an item. - * - * Default fields included are: 'item', item type name, all DC:Type values. - * - * Output can be filtered using the 'neatlinetime_item_class' filter. - * - * @return string - */ -function neatlinetime_item_class($item = null) { - $classArray = array('item'); - - if ($itemTypeName = metadata($item, 'item_type_name')) { - $classArray[] = text_to_id($itemTypeName); - } - - if ($dcTypes = metadata($item, array('Dublin Core', 'Type'), array('all' => true))) { - foreach ($dcTypes as $type) { - $classArray[] = text_to_id($type); - } - } - - $classAttribute = implode(' ', $classArray); - $classAttribute = apply_filters('neatlinetime_item_class', $classAttribute); - return $classAttribute; -} - -/** - * Generates an ISO-8601 date from a date string - * - * @see Zend_Date - * @return string ISO-8601 date - */ -function neatlinetime_convert_date($date) { - if (preg_match('/^\d{4}$/', $date) > 0) { - return false; - } - - $newDate = null; - try { - $newDate = new Zend_Date($date, Zend_Date::ISO_8601); - } catch (Exception $e) { - try { - $newDate = new Zend_Date($date); - } catch (Exception $e) { - } - } - - if (is_null($newDate)) { - $date_out = false; - } else { - $date_out = $newDate->get('c'); - $date_out = preg_replace('/^(-?)(\d{3}-)/', '${1}0\2', $date_out); - $date_out = preg_replace('/^(-?)(\d{2}-)/', '${1}00\2', $date_out); - $date_out = preg_replace('/^(-?)(\d{1}-)/', '${1}000\2', $date_out); - } - return $date_out; - -} - -/** - * Generates a form select populated by all elements and element sets. - * - * @param string The NeatlineTime option name. - * @return string HTML. - */ -function neatlinetime_option_select($name = null) { - - if ($name) { - return get_view()->formSelect( - $name, - neatlinetime_get_option($name), - array(), - get_table_options('Element', null, array( - 'record_types' => array('Item', 'All'), - 'sort' => 'alphaBySet') - ) - ); - - } - - return false; - -} - -/** - * Gets the value for an option set in the neatlinetime option array. - * - * @param string The NeatlineTime option name. - * @return string - */ -function neatlinetime_get_option($name = null) { - - if ($name) { - $options = get_option('neatlinetime'); - $options = unserialize($options); - return $options[$name]; - } - - return false; - -} - -/** - * Returns the value of an element set in the NeatlineTime config options. - * - * @param string The NeatlineTime option name. - * @param array An array of options. - * @param Item - * @return string|array|null - */ -function neatlinetime_get_item_text($optionName, $options = array(), $item = null) { - - $element = get_db()->getTable('Element')->find(neatlinetime_get_option($optionName)); - - return metadata($item, array($element->getElementSet()->name, $element->name), $options); - -} diff --git a/libraries/NeatlineTime/Form/TimelineAdd.php b/libraries/NeatlineTime/Form/TimelineAdd.php new file mode 100644 index 0000000..2a85486 --- /dev/null +++ b/libraries/NeatlineTime/Form/TimelineAdd.php @@ -0,0 +1,137 @@ +setMethod('post'); + $this->setAttrib('id', 'timeline-form'); + + // Title + $this->addElement('text', 'title', array( + 'label' => __('Title'), + 'description' => __('A title for this timeline.'), + )); + + // Description + $this->addElement('textarea', 'description', array( + 'label' => __('Description'), + 'description' => __('A description for this timeline.'), + 'attribs' => array('class' => 'html-editor', 'rows' => '15'), + )); + + // Public/Not Public + $this->addElement('checkbox', 'public', array( + 'label' => __('Status'), + 'description' => __('Whether the timeline is public or not.'), + 'value' => false, + )); + + // Featured/Not Featured + $this->addElement('checkbox', 'featured', array( + 'label' => __('Featured'), + 'description' => __('Whether the timeline is featured or not.'), + 'value' => false, + )); + + $values = get_table_options('Element', null, array( + 'record_types' => array('Item', 'All'), + 'sort' => 'alphaBySet', + )); + unset($values['']); + foreach (array( + 'item_title' => array(__('Item Title')), + 'item_description' => array(__('Item Description')), + 'item_date' => array(__('Item Date')), + ) as $parameterName => $parameterOptions) { + $this->addElement('select', $parameterName, array( + 'label' => $parameterOptions[0], + 'multiOptions' => $values, + 'value' => false, + )); + } + + $values = array('' =>__('None')) + $values; + $this->addElement('select', 'item_date_end', array( + 'label' => __('Item End Date'), + 'description' => __('If set, the process will use the other date as a start date.'), + 'multiOptions' => $values, + 'value' => false, + )); + + // Set fhe mode to render a year. + $values = array( + 'skip' => __('Skip the record'), + 'january_1' => __('Pick first January'), + 'july_1' => __('Pick first July'), + 'full_year' => __('Mark entire year'), + ); + $this->addElement('radio', 'render_year', array( + 'label' => __('Render Year'), + 'description' => __('When a date is a single year, like "1066", the value should be interpreted to be displayed on the timeline.'), + 'multiOptions' => $values, + 'value' => false, + )); + + // Set the center date for the timeline. + $this->addElement('text', 'center_date', array( + 'label' => __('Center Date'), + 'description' => __('Set the center date of the timeline.') + . ' ' . __('The format should be "YYYY-MM-DD".') + . ' ' . __('An empty value means "now", "0000-00-00" the earliest date, and "9999-99-99" the latest date.'), + 'validators' => array('date'), + )); + + // Set the params of the viewer. + $this->addElement('textarea', 'viewer', array( + 'label' => __('Viewer'), + 'description' => __('Set the params of the viewer as json, or let empty for the included default.') + . ' ' . __('Currently, only "bandInfos" and "centerDate" are managed.'), + 'attribs' => array('rows' => '10'), + )); + + // Submit + $this->addElement('submit', 'submit', array( + 'label' => __('Save Timeline'), + )); + + // Group the title, description, and public/featured fields. + $this->addDisplayGroup( + array( + 'title', + 'description', + 'public', + 'featured', + ), + 'timeline_info', + array( + 'legend' => __('About the timeline'), + 'description' => __('Set the main metadata of the timeline.'), + )); + $this->addDisplayGroup( + array( + 'item_title', + 'item_description', + 'item_date', + 'item_date_end', + 'render_year', + 'center_date', + 'viewer', + ), + 'timeline_parameters', + array( + 'legend' => __('Specific parameters'), + 'description' => __('Set the specific parameters of the timeline.') + . ' ' . __('If not set, the defaults set in the config page will apply.'), + )); + + // Add the submit to a separate display group. + $this->addDisplayGroup(array('submit'), 'timeline_submit'); + + $this->addElement('sessionCsrfToken', 'csrf_token'); + } +} diff --git a/libraries/NeatlineTime/Functions.php b/libraries/NeatlineTime/Functions.php new file mode 100644 index 0000000..e0afb58 --- /dev/null +++ b/libraries/NeatlineTime/Functions.php @@ -0,0 +1,459 @@ + tag. (optional) + * @param string The action for the link. Default is 'show'. + * @param NeatlineTime_Timeline|null + * @return string HTML + * @deprecated + */ +function link_to_timeline($text = null, $props = array(), $action = 'show', $timeline = null) +{ + $timeline = $timeline ?: get_current_record('neatline_time_timeline'); + $text = $text ?: $timeline->title; + return link_to($timeline, $action, $text, $props); +} + +/** + * Queues JavaScript and CSS for NeatlineTime in the page header. + * + * @deprecated Uses hooks for header instead. + * @see NeatlineTimePlugin::_head() + * @since 1.0 + * @return void. + */ +function queue_timeline_assets() +{ + $library = get_option('neatline_time_library'); + if ($library == 'knightlab') { + queue_css_url('//cdn.knightlab.com/libs/timeline3/latest/css/timeline.css'); + queue_js_url('//cdn.knightlab.com/libs/timeline3/latest/js/timeline.js'); + return; + } + + // Default simile library. + queue_css_file('neatlinetime-timeline'); + + queue_js_file('neatline-time-scripts'); + + $internalAssets = get_option('neatline_time_internal_assets'); + if ($internalAssets) { + $useInternalJs = true; + } else { + // Check useInternalJavascripts in config.ini. + $config = Zend_Registry::get('bootstrap')->getResource('Config'); + $useInternalJs = isset($config->theme->useInternalJavascripts) + ? (bool) $config->theme->useInternalJavascripts + : false; + $useInternalJs = isset($config->theme->useInternalAssets) + ? (bool) $config->theme->useInternalAssets + : $useInternalJs; + } + + if ($useInternalJs) { + $timelineVariables = 'Timeline_ajax_url="' . src('simile-ajax-api.js', 'javascripts/simile/ajax-api') . '"; + Timeline_urlPrefix="' . dirname(src('timeline-api.js', 'javascripts/simile/timeline-api')) . '/"; + Timeline_parameters="bundle=true";'; + queue_js_string($timelineVariables); + queue_js_file('timeline-api', 'javascripts/simile/timeline-api'); + queue_js_string('SimileAjax.History.enabled = false; // window.jQuery = SimileAjax.jQuery;'); + } else { + queue_js_url('//api.simile-widgets.org/timeline/2.3.1/timeline-api.js?bundle=true'); + queue_js_string('SimileAjax.History.enabled = false; window.jQuery = SimileAjax.jQuery;'); + } +} + +/** + * Get metadata for a record according to the parameters of a timeline. + * + * @uses Omeka_View_Helper_Metadata::metadata() + * @param Omeka_Record_AbstractRecord|string $record The record to get metadata + * for. If an Omeka_Record_AbstractRecord, that record is used. If a string, + * that string is used to look up a record in the current view. + * @param mixed $metadata The metadata to get. If an array is given, this is + * Element metadata, identified by array('Element Set', 'Element'). If a string, + * the metadata is a record-specific "property." + * @param array $options Options for getting the metadata. + * @param NeatlineTime_Timeline|null $timeline The timeline where the parameters + * are set. If null, use the current timeline. + * @return mixed + */ +function neatlinetime_metadata($record, $metadata, $options = array(), $timeline = null) +{ + $timeline = $timeline ?: get_current_record('neatline_time_timeline'); + if (is_string($metadata)) { + $elementId = $timeline->getProperty($metadata); + if (!empty($elementId)) { + $element = $record->getElementById($elementId); + if (!empty($element)) { + $metadata = array($element->getElementSet()->name, $element->name); + } + } + // Avoid some useless process. + elseif ($metadata == 'item_date_end') { + return array(); + } + } + return metadata($record, $metadata, $options); +} + +/** + * Returns the URI for a timeline's json output. + * + * @since 1.0 + * @param NeatlineTime_Timeline|null + * @return string URL the items output uri for the neatlinetime-json output. + */ +function neatlinetime_json_uri_for_timeline($timeline = null) +{ + $timeline = $timeline ?: get_current_record('neatline_time_timeline'); + return record_url($timeline, 'items') . '?output=neatlinetime-json'; +} + +/** + * Construct id for container div. + * + * @since 1.0 + * @param NeatlineTime_Timeline|null + * @return string HTML + */ +function neatlinetime_timeline_id($timeline = null) +{ + $timeline = $timeline ?: get_current_record('neatline_time_timeline'); + return text_to_id(html_escape($timeline->title) . ' ' . $timeline->id, 'neatlinetime'); +} + +/** + * Displays random featured timelines + * + * @param int Maximum number of random featured timelines to display. + * @return string HTML + */ +function neatlinetime_display_random_featured_timelines($num = 1) +{ + $html = ''; + + $timelines = get_db()->getTable('NeatlineTime_Timeline')->findBy(array('sort_field' => 'random', 'featured' => 1), $num); + + if ($timelines) { + foreach ($timelines as $timeline) { + $html .= '

' . link_to_timeline(null, array(), 'show', $timeline) . '

' + . '
' + . timeline('description', array('snippet' => 150), $timeline) + . '
'; + } + return $html; + } +} + +/** + * Returns a string for neatline_json 'classname' attribute for an item. + * + * Default fields included are: 'item', item type name, all DC:Type values. + * + * Output can be filtered using the 'neatlinetime_item_class' filter. + * + * @return string + */ +function neatlinetime_item_class($item = null) +{ + $classes = array('item'); + + $type = metadata($item, 'item_type_name'); + if ($type) { + $classes[] = text_to_id($type); + } + + $dcTypes = metadata($item, array('Dublin Core', 'Type'), array('all' => true)); + if ($dcTypes) { + foreach ($dcTypes as $type) { + $classes[] = text_to_id($type); + } + } + + $classAttribute = implode(' ', $classes); + $classAttribute = apply_filters('neatlinetime_item_class', $classAttribute); + return $classAttribute; +} + +/** + * Generates an ISO-8601 date from a date string + * + * @see Zend_Date + * @param string $date + * @param string renderYear Force the format of a single number as a year. + * @return string ISO-8601 date + */ +function neatlinetime_convert_date($date, $renderYear = null) +{ + if (empty($renderYear)) { + $renderYear = neatlinetime_get_option('render_year'); + } + + // Check if the date is a single number. + if (preg_match('/^-?\d{1,4}$/', $date)) { + // Normalize the year. + $date = $date < 0 + ? '-' . str_pad(substr($date, 1), 4, '0', STR_PAD_LEFT) + : str_pad($date, 4, '0', STR_PAD_LEFT); + switch ($renderYear) { + case 'january_1': + $date_out = $date . '-01-01' . 'T00:00:00+00:00'; + break; + case 'july_1': + $date_out = $date . '-07-01' . 'T00:00:00+00:00'; + break; + case 'december_31': + $date_out = $date . '-12-31' . 'T00:00:00+00:00'; + break; + case 'june_30': + $date_out = $date . '-06-30' . 'T00:00:00+00:00'; + break; + case 'full_year': + // Render a year as a range: use neatlinetime_convert_single_date(). + case 'skip': + default: + $date_out = false; + break; + } + return $date_out; + } + + $newDate = null; + try { + $newDate = new Zend_Date($date, Zend_Date::ISO_8601); + } catch (Exception $e) { + try { + $newDate = new Zend_Date($date); + } catch (Exception $e) { + } + } + + if (is_null($newDate)) { + $date_out = false; + } else { + $date_out = $newDate->get('c'); + $date_out = preg_replace('/^(-?)(\d{3}-)/', '${1}0\2', $date_out); + $date_out = preg_replace('/^(-?)(\d{2}-)/', '${1}00\2', $date_out); + $date_out = preg_replace('/^(-?)(\d{1}-)/', '${1}000\2', $date_out); + } + return $date_out; +} + +/** + * Generates an array of one or two ISO-8601 dates from a string. + * + * @todo manage the case where the start is empty and the end is set. + * + * @param string $date + * @param string renderYear Force the format of a single number as a year. + * @return array Array of two dates. + */ +function neatlinetime_convert_any_date($date, $renderYear = null) +{ + return neatlinetime_convert_two_dates($date, null, $renderYear); +} + +/** + * Generates an array of one or two ISO-8601 dates from two strings. + * + * @todo manage the case where the start is empty and the end is set. + * + * @param string $date + * @param string $dateEnd + * @param string renderYear Force the format of a single number as a year. + * @return array Array of two dates. + */ +function neatlinetime_convert_two_dates($date, $dateEnd, $renderYear = null) +{ + if (empty($renderYear)) { + $renderYear = neatlinetime_get_option('render_year'); + } + + $dateArray = array_map('trim', explode('/', $date)); + + // A range of dates. + if (count($dateArray) == 2) { + return neatlinetime_convert_range_dates($dateArray, $renderYear); + } + + $dateEndArray = explode('/', $dateEnd); + $dateEnd = trim(reset($dateEndArray)); + + // A single date, or a range when the two dates are years and when the + // render is "full_year". + if (empty($dateEnd)) { + return neatlinetime_convert_single_date($dateArray[0], $renderYear); + } + + return neatlinetime_convert_range_dates(array($dateArray[0], $dateEnd), $renderYear); +} + +/** + * Generates an ISO-8601 date from a date string, with an exception for + * "full_year" render, that returns two dates. + * + * @param string $date + * @param string renderYear Force the format of a single number as a year. + * @return array Array of two dates. + */ +function neatlinetime_convert_single_date($date, $renderYear = null) +{ + if (empty($renderYear)) { + $renderYear = neatlinetime_get_option('render_year'); + } + + // Manage a special case for render "full_year" with a single number. + if ($renderYear == 'full_year' && preg_match('/^-?\d{1,4}$/', $date)) { + $dateStartValue = neatlinetime_convert_date($date, 'january_1'); + $dateEndValue = neatlinetime_convert_date($date, 'december_31'); + return array($dateStartValue, $dateEndValue); + } + + // Only one date. + $dateStartValue = neatlinetime_convert_date($date, $renderYear); + return array($dateStartValue, null); +} + +/** + * Generates two ISO-8601 dates from an array of two strings. + * + * By construction, no "full_year" is returned. + * + * @param array $dates + * @param string renderYear Force the format of a single number as a year. + * @return array $dates + */ +function neatlinetime_convert_range_dates($dates, $renderYear = null) +{ + if (!is_array($dates)) { + return array(null, null); + } + + if (empty($renderYear)) { + $renderYear = neatlinetime_get_option('render_year'); + } + + $dateStart = $dates[0]; + $dateEnd = $dates[1]; + + // Check if the date are two numbers (years). + if ($renderYear == 'skip') { + $dateStartValue = neatlinetime_convert_date($dateStart, $renderYear); + $dateEndValue = neatlinetime_convert_date($dateEnd, $renderYear); + return array($dateStartValue, $dateEndValue); + } + + // Check if there is one number and one date. + if (!preg_match('/^-?\d{1,4}$/', $dateStart)) { + if (!preg_match('/^-?\d{1,4}$/', $dateEnd)) { + // TODO Check order to force the start or the end. + $dateStartValue = neatlinetime_convert_date($dateStart, $renderYear); + $dateEndValue = neatlinetime_convert_date($dateEnd, $renderYear); + return array($dateStartValue, $dateEndValue); + } + // Force the format for the end. + $dateStartValue = neatlinetime_convert_date($dateStart, $renderYear); + if ($renderYear == 'full_year') $renderYear = 'december_31'; + $dateEndValue = neatlinetime_convert_date($dateEnd, $renderYear); + return array($dateStartValue, $dateEndValue); + } + // The start is a year. + elseif (!preg_match('/^-?\d{1,4}$/', $dateEnd)) { + // Force the format of the start. + $dateEndValue = neatlinetime_convert_date($dateEnd, $renderYear); + if ($renderYear == 'full_year') $renderYear = 'january_1'; + $dateStartValue = neatlinetime_convert_date($dateStart, $renderYear); + return array($dateStartValue, $dateEndValue); + } + + $dateStart = (integer) $dateStart; + $dateEnd = (integer) $dateEnd; + + // Same years. + if ($dateStart == $dateEnd) { + $dateStartValue = neatlinetime_convert_date($dateStart, 'january_1'); + $dateEndValue = neatlinetime_convert_date($dateEnd, 'december_31'); + return array($dateStartValue, $dateEndValue); + } + + // The start and the end are years, so reorder them (may be useless). + if ($dateStart > $dateEnd) { + $kdate = $dateEnd; + $dateEnd = $dateStart; + $dateStart = $kdate; + } + + switch ($renderYear) { + case 'july_1': + $dateStartValue = neatlinetime_convert_date($dateStart, 'july_1'); + $dateEndValue = neatlinetime_convert_date($dateEnd, 'june_30'); + return array($dateStartValue, $dateEndValue); + case 'january_1': + $dateStartValue = neatlinetime_convert_date($dateStart, 'january_1'); + $dateEndValue = neatlinetime_convert_date($dateEnd, 'january_1'); + return array($dateStartValue, $dateEndValue); + case 'full_year': + default: + $dateStartValue = neatlinetime_convert_date($dateStart, 'january_1'); + $dateEndValue = neatlinetime_convert_date($dateEnd, 'december_31'); + return array($dateStartValue, $dateEndValue); + } +} + +/** + * Gets the value for an option set in the neatlinetime option array. + * + * Useless now because each timeline has parameters available via getProperty(). + * + * @param string The NeatlineTime option name. + * @return string + */ +function neatlinetime_get_option($name = null) +{ + if ($name) { + $options = get_option('neatline_time_defaults'); + $options = json_decode($options, true); + return isset($options[$name]) ? $options[$name] : null; + } + return false; +} + +/** + * Returns the value of an element set in the NeatlineTime config options. + * + * @deprecated since 2.1.9 + * @see neatlinetime_metadata() + * @param string The NeatlineTime option name. + * @param array An array of options. + * @param Item + * @return string|array|null + */ +function neatlinetime_get_item_text($optionName, $options = array(), $item = null) +{ + $element = get_db()->getTable('Element')->find(neatlinetime_get_option($optionName)); + return metadata($item, array($element->getElementSet()->name, $element->name), $options); +} diff --git a/OwnershipAclAssertion.php b/libraries/NeatlineTime/OwnershipAclAssertion.php similarity index 92% rename from OwnershipAclAssertion.php rename to libraries/NeatlineTime/OwnershipAclAssertion.php index 5e683ad..4bc49b1 100644 --- a/OwnershipAclAssertion.php +++ b/libraries/NeatlineTime/OwnershipAclAssertion.php @@ -1,9 +1,9 @@ isAllowed($role, $resource, $allPriv) || ($acl->isAllowed($role, $resource, $selfPriv) && $this->_userOwnsTimeline($role, $resource)); diff --git a/models/NeatlineTime/Timeline.php b/models/NeatlineTime/Timeline.php new file mode 100644 index 0000000..9287909 --- /dev/null +++ b/models/NeatlineTime/Timeline.php @@ -0,0 +1,329 @@ +_mixins[] = new Mixin_Owner($this, 'owner_id'); + $this->_mixins[] = new Mixin_PublicFeatured($this); + $this->_mixins[] = new Mixin_Timestamp($this); + } + + /** + * Get the user object. + * + * @return User|null + */ + public function getOwner() + { + if ($this->owner_id) { + return $this->getTable('User')->find($this->owner_id); + } + } + + /** + * Returns the parameters of the record. + * + * @throws UnexpectedValueException + * @return array The parameters of the folder. + */ + public function getParameters() + { + if (is_null($this->_parameters)) { + // Check if the parameters have been set directly. + $parameters = empty($this->parameters) ? array() : $this->parameters; + if (!is_array($parameters)) { + $parameters = json_decode($parameters, true); + if (!is_array($parameters)) { + throw new UnexpectedValueException(__('Parameters must be an array. ' + . 'Instead, the following was given: %s.', var_export($parameters, true))); + } + } + $this->_parameters = $parameters; + } + return $this->_parameters; + } + + /** + * Returns the specified parameter of the record. + * + * @param string $name + * @return string The specified parameter of the record. + */ + public function getParameter($name) + { + $parameters = $this->getParameters(); + return isset($parameters[$name]) ? $parameters[$name] : null; + } + + /** + * Returns the json parameters of the viewer. + * + * @uses ::getParameter() + * @return string The parameters of the viewer. + */ + public function getViewerParameters() + { + return $this->getParameter('viewer') ?: '{}'; + } + + /** + * Get the query. + */ + public function getQuery() + { + if (is_null($this->_query)) { + // Check if the query has been set directly. + $query = empty($this->query) ? array() : $this->query; + if (!is_array($query)) { + $query = json_decode($query, true); + if (!is_array($query)) { + throw new UnexpectedValueException(__('Query must be an array. ' + . 'Instead, the following was given: %s.', var_export($parameters, true))); + } + } + $this->_query = $query; + } + return $this->_query; + } + + /** + * Get a property about the record for display purposes. + * + * @param string $property Property to get. Always lowercase. + * @return mixed + */ + public function getProperty($property) + { + switch($property) { + case 'added_username': + $user = $this->getOwner(); + return $user + ? $user->username + : __('Anonymous'); + case 'parameters': + return $this->getParameters(); + case 'item_title': + case 'item_description': + case 'item_date': + case 'item_date_end': + case 'render_year': + case 'center_date': + case 'viewer': + return $this->getParameter($property); + default: + return parent::getProperty($property); + } + } + + /** + * Get the default options. + * + * @return array + */ + public function getDefaultOptions() + { + // For testing, the defaults should be checked if they are an array. + $defaults = get_option('neatline_time_defaults'); + return is_array($defaults) ? $defaults : json_decode($defaults, true); + } + + /** + * Sets parameters. + * + * @param array $parameters + */ + public function setParameters($parameters) + { + // Check null. + if (empty($parameters)) { + $parameters = array(); + } + elseif (!is_array($parameters)) { + throw new InvalidArgumentException(__('Parameters must be an array.')); + } + // This is required to manage all the cases and tests. + $this->parameters = json_encode($parameters); + $this->_parameters = $parameters; + } + + /** + * Set the specified parameter of the record. + * + * @param string $name + * @param mixed $value + * @return string The specified parameter of the record. + */ + public function setParameter($name, $value) + { + // Initialize parameters if needed. + $parameters = $this->getParameters(); + $this->_parameters[$name] = $value; + } + + /** + * Set the query. + * + * @param array $query + */ + public function setQuery($query) + { + // Check null. + if (empty($query)) { + $query = array(); + } + elseif (!is_array($query)) { + throw new InvalidArgumentException(__('Query must be an array.')); + } + // This is required to manage all the cases and tests. + $this->query = json_encode($query); + $this->_query = $query; + } + + /** + * Filter post data from form submissions. + * + * @param array Dirty post data + * @return array Clean post data + */ + protected function filterPostData($post) + { + $options = array('inputNamespace' => 'Omeka_Filter'); + $filters = array( + // Booleans + 'public' =>'Boolean', + 'featured' =>'Boolean' + ); + $filter = new Zend_Filter_Input($filters, null, $post, $options); + $post = $filter->getUnescaped(); + + $bootstrap = Zend_Registry::get('bootstrap'); + $acl = $bootstrap->getResource('Acl'); + $currentUser = $bootstrap->getResource('CurrentUser'); + // check permissions to make public and make featured + if (!$acl->isAllowed($currentUser, 'NeatlineTime_Timelines', 'makePublic')) { + unset($post['public']); + } + if (!$acl->isAllowed($currentUser, 'NeatlineTime_Timelines', 'makeFeatured')) { + unset($post['featured']); + } + + // This filter move all parameters inside 'parameters' of the record. + $defaults = $this->getDefaultOptions(); + $parameters = array_intersect_key($post, $defaults); + $this->setParameters($parameters); + + return $post; + } + + /** + * Get the routing parameters or the URL string to this record. + * + * @param string $action + * @return string|array A URL string or a routing array. + */ + public function getRecordUrl($action = 'show') + { + $urlHelper = new Omeka_View_Helper_Url; + $params = array('action' => $action, 'id' => $this->id); + return $urlHelper->url($params, 'timelineActionRoute'); + } + + /** + * Required by Zend_Acl_Resource_Interface. + * + * Identifies Timeline records as relating to the Timeline_Timelines ACL + * resource. + * + * @since 1.0 + * @return string + */ + public function getResourceId() + { + return 'NeatlineTime_Timelines'; + } + + /** + * Get the list of items according to the query. + */ + public function getItems() + { + $query = $this->getQuery(); + $items = $this->_db->getTable('Item')->findBy($query, null); + return $items; + } + + /** + * Executes before the record is saved. + */ + protected function beforeSave($args) + { + if (is_null($this->title)) { + $this->title = ''; + } + if (is_null($this->description)) { + $this->description = ''; + } + if (is_null($this->owner_id)) { + $this->owner_id = 0; + } + + // Be sure to set defaults parameters to simplify queries. + $parameters = $this->getParameters(); + $defaults = $this->getDefaultOptions(); + $parameters = array_merge($defaults, $parameters); + $this->parameters = version_compare(phpversion(), '5.4.0', '<') + ? json_encode($parameters) + : json_encode($parameters, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + + $query = $this->getQuery(); + unset($query['csrf_token']); + $this->query = version_compare(phpversion(), '5.4.0', '<') + ? json_encode($query) + : json_encode($query, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + /** + * Template method for defining record validation rules. + * + * Should be overridden by subclasses. + * + * @return void + */ + protected function _validate() + { + $centerDate = $this->getParameter('center_date'); + if (empty($centerDate) || $centerDate == '0000-00-00' || $centerDate == '9999-99-99') { + $this->setParameter('center_date', null); + } + // Validate the date. + else { + $validator = new Zend_Validate_Date(array('format' => 'yyyy-MM-dd')); + if (!$validator->isValid($centerDate)) { + $this->addError('center_date', __('The center date must be in the format YYYY-MM-DD.')); + } + else { + $this->setParameter('center_date', $centerDate); + } + } + } +} diff --git a/models/NeatlineTimeTimeline.php b/models/NeatlineTimeTimeline.php deleted file mode 100644 index 84d877a..0000000 --- a/models/NeatlineTimeTimeline.php +++ /dev/null @@ -1,72 +0,0 @@ -_mixins[] = new Mixin_Owner($this, 'creator_id'); - $this->_mixins[] = new Mixin_PublicFeatured($this); - $this->_mixins[] = new Mixin_Timestamp($this); - } - - /** - * Required by Zend_Acl_Resource_Interface. - * - * Identifies Timeline records as relating to the Timeline_Timelines ACL - * resource. - * - * @since 1.0 - * @return string - */ - public function getResourceId() - { - return 'NeatlineTime_Timelines'; - } - - protected function beforeSave() - { - $query = $this->query; - if (is_array($query)) { - $this->query = serialize($query); - } - } - - /** - * Get the routing parameters or the URL string to this record. - */ - public function getRecordUrl($action = 'show') - { - $urlHelper = new Omeka_View_Helper_Url; - $params = array('action' => $action, 'id' => $this->id); - return $urlHelper->url($params, 'timelineActionRoute'); - } - - /** - * - **/ - protected function _validate() - { - $validator = new Zend_Validate_Date(array('format' => 'yyyy-MM-dd')); - if ($this->center_date == '') { - $this->center_date = '0000-00-00'; - } elseif ($this->center_date !== '0000-00-00' && !$validator->isValid($this->center_date)) { - $this->addError(null, __('The center date must be in the format YYYY-MM-DD.')); - } - } -} diff --git a/models/NeatlineTimeTimelineTable.php b/models/NeatlineTimeTimelineTable.php deleted file mode 100644 index b1e8421..0000000 --- a/models/NeatlineTimeTimelineTable.php +++ /dev/null @@ -1,115 +0,0 @@ -where('neatline_time_timelines.public = 1'); - } else { - $select->where('neatline_time_timelines.public = 0'); - } - } - - /** - * Filter featured/not featured timelines. - * - * @param Zend_Db_Select - * @param boolean Whether to retrieve only featured timelines. - */ - public function filterByFeatured($select, $isFeatured) - { - $isFeatured = (bool) $isFeatured; - - if ($isFeatured) { - $select->where('neatline_time_timelines.featured = 1'); - } else { - $select->where('neatline_time_timelines.featured = 0'); - } - } - - /** - * Filter for timelines created by a specific user. - * - * @param Zend_Db_Select - * @param boolean Whether to retrieve only featured timelines. - */ - public function filterByUser($select, $userId) - { - $userId = (int) $userId; - - if ($userId) { - $select->where('neatline_time_timelines.creator_id = ?', $userId); - } - } - - /** - * Order SELECT results randomly. - * - * @param Zend_Db_Select - * @return void - */ - public function orderSelectByRandom($select) - { - $select->order('RAND()'); - } - - /** - * Possible options: 'public','user', and 'featured'. - * - * @param Omeka_Db_Select - * @param array - * @return void - */ - public function applySearchFilters($select, $params) - { - if (isset($params['user'])) { - $userId = $params['user']; - $this->filterByUser($select, $userId); - } - - if(isset($params['public'])) { - $this->filterByPublic($select, $params['public']); - } - - if(isset($params['featured'])) { - $this->filterByFeatured($select, $params['featured']); - } - - if(isset($params['random'])) { - $this->orderSelectByRandom($select); - } - - $select->group("neatline_time_timelines.id"); - } - - public function getSelect() - { - $select = parent::getSelect(); - $permissions = new Omeka_Db_Select_PublicPermissions('NeatlineTime_Timelines'); - $permissions->apply($select, 'neatline_time_timelines', null); - return $select; - } - - /** - * Return the columns to be used for creating an HTML select of timelines. - * - * @return array - */ - public function _getColumnPairs() - { - return array( - 'neatline_time_timelines.id', - 'neatline_time_timelines.title' - ); - } -} diff --git a/models/Table/NeatlineTime/Timeline.php b/models/Table/NeatlineTime/Timeline.php new file mode 100644 index 0000000..7c24361 --- /dev/null +++ b/models/Table/NeatlineTime/Timeline.php @@ -0,0 +1,68 @@ +getTableAlias(); + $boolean = new Omeka_Filter_Boolean; + $genericParams = array(); + foreach ($params as $key => $value) { + if ($value === null || (is_string($value) && trim($value) == '')) { + continue; + } + switch ($key) { + case 'user': + case 'owner': + case 'user_id': + case 'owner_id': + $this->filterByUser($select, $value, 'owner_id'); + break; + case 'public': + $this->filterByPublic($select, $boolean->filter($value)); + break; + case 'featured': + $this->filterByFeatured($select, $boolean->filter($value)); + break; + case 'added_since': + $this->filterBySince($select, $value, 'added'); + break; + case 'modified_since': + $this->filterBySince($select, $value, 'modified'); + break; + default: + $genericParams[$key] = $value; + } + } + + if (!empty($genericParams)) { + parent::applySearchFilters($select, $genericParams); + } + + $select->group($alias . '.id'); + } + + public function getSelect() + { + $select = parent::getSelect(); + $permissions = new Omeka_Db_Select_PublicPermissions('NeatlineTime_Timelines'); + $permissions->apply($select, 'neatline_time_timelines', null); + return $select; + } + + /** + * Return the columns to be used for creating an HTML select of timelines. + * + * @return array + */ + public function _getColumnPairs() + { + $alias = $this->getTableAlias(); + return array($alias . '.id', $alias . '.title'); + } +} diff --git a/plugin.ini b/plugin.ini index 4b3557d..46cf65d 100644 --- a/plugin.ini +++ b/plugin.ini @@ -1,12 +1,11 @@ [info] name="Neatline Time" - author="Scholars' Lab" + author="Scholars' Lab, Daniel Berthereau" description="Create timelines in Omeka." + license="Apache License, Version 2.0" link="http://neatline.org/" - omeka_minimum_version="2.0" - omeka_tested_up_to="2.3" - version="2.1.0" - tags="timelines, chronology" support_link="http://github.com/scholarslab/NeatlineTime/issues" - omeka_target_version="2.0" - license="Apache License, Version 2.0" + tags="timeline, chronology" + omeka_minimum_version="2.2.2" + omeka_target_version="2.6.1" + version="2.3.1" diff --git a/tests/NeatlineTime_Test_AppTestCase.php b/tests/NeatlineTime_Test_AppTestCase.php index 4639956..5c7a5da 100644 --- a/tests/NeatlineTime_Test_AppTestCase.php +++ b/tests/NeatlineTime_Test_AppTestCase.php @@ -2,13 +2,21 @@ /** * Testing helper class. */ -require_once '../NeatlineTimePlugin.php'; class NeatlineTime_Test_AppTestCase extends Omeka_Test_AppTestCase { - private $_dbHelper; + public static $options = array( + 'item_title' => 50, + 'item_description' => 41, + 'item_date' => 40, + 'item_date_end' => '', + 'render_year' => 'skip', + 'center_date' => '', + 'viewer' => '{}', + ); + /** * Spin up the plugins and prepare the database. * @@ -16,7 +24,6 @@ class NeatlineTime_Test_AppTestCase extends Omeka_Test_AppTestCase */ public function setUp() { - parent::setUp(); $this->user = $this->db->getTable('User')->find(1); @@ -27,13 +34,12 @@ public function setUp() $plugin_helper->setUp('NeatlineTime'); Omeka_Test_Resource_Db::$runInstaller = true; - } /** * Create a timeline for testing. * - * @return NeatlineTimeTimeline + * @return NeatlineTime_Timeline */ public function _createTimeline($data = array(), $user = null) { @@ -43,11 +49,14 @@ public function _createTimeline($data = array(), $user = null) $data['public'] = '1'; $data['featured'] = '1'; $data['query'] = array('range' => '1'); + $data['parameters'] = array( + 'render_year' => 'skip', + ); } - $data['creator_id'] = $user ? $user->id : $this->user->id; + $data['owner_id'] = $user ? $user->id : $this->user->id; - $timeline = new NeatlineTimeTimeline; + $timeline = new NeatlineTime_Timeline; foreach ($data as $k => $v) { $timeline->$k = $v; @@ -56,5 +65,4 @@ public function _createTimeline($data = array(), $user = null) $timeline->save(); return $timeline; } - } diff --git a/tests/integration/Helpers/ConvertDateTest.php b/tests/integration/Helpers/ConvertDateTest.php index 6364a2e..3d166db 100644 --- a/tests/integration/Helpers/ConvertDateTest.php +++ b/tests/integration/Helpers/ConvertDateTest.php @@ -3,7 +3,7 @@ * Tests the get_current_timeline() helper. */ class ConvertDateTest extends NeatlineTime_Test_AppTestCase -{ +{ protected $_isAdminTest = true; // https://github.com/scholarslab/NeatlineTime/issues/38 @@ -13,8 +13,26 @@ public function testIso8601Dates() { ); } - // https://github.com/scholarslab/NeatlineTime/issues/39 + /** + * By default, a single number is now accepted. + * + * https://github.com/scholarslab/NeatlineTime/issues/39 + */ public function testYearOnlyDate() { + $this->assertEquals('2012-01-01T00:00:00+00:00', + neatlinetime_convert_date('2012') + ); + } + + /** + * A single number may be not accepted. + * + * https://github.com/scholarslab/NeatlineTime/issues/39 + */ + public function testYearOnlyDateSkip() { + $defaults = json_decode(get_option('neatline_time_defaults'), true); + $defaults['render_year'] = 'skip'; + set_option('neatline_time_defaults', json_encode($defaults)); $this->assertFalse( neatlinetime_convert_date('2012') ); @@ -47,4 +65,89 @@ public function testBCE() { '-2013-01-01', neatlinetime_convert_date('-2013-01-01') ); } + + /** + * A single number may be allowed with the specified parameter. + */ + public function testYearOnlyDateWithParamJanuary1() + { + $this->assertContains( + '1066-01-01', neatlinetime_convert_date('1066', 'january_1') + ); + $this->assertFalse( + neatlinetime_convert_date('1066', 'skip') + ); + } + + /** + * A single number may be allowed with the specified parameter. + */ + public function testYearOnlyDateWithParamJuly1() + { + $this->assertContains( + '1066-07-01', neatlinetime_convert_date('1066', 'july_1') + ); + } + + /** + * A single number may be allowed with the specified parameter. + */ + public function testYearOnlyDateWithParamFullYear() + { + $this->assertFalse(neatlinetime_convert_date('1066', 'full_year')); + $result = neatlinetime_convert_single_date('1066', 'full_year'); + $this->assertContains('1066-01-01', $result[0]); + $this->assertContains('1066-12-31', $result[1]); + } + + public function testRangeDatesJanuary1() + { + $result = neatlinetime_convert_range_dates(array('1066', '1067'), 'january_1'); + $this->assertContains('1066-01-01', $result[0]); + $this->assertContains('1067-01-01', $result[1]); + $result = neatlinetime_convert_range_dates(array('1066-10-09', '1067'), 'january_1'); + $this->assertContains('1066-10-09', $result[0]); + $this->assertContains('1067-01-01', $result[1]); + $result = neatlinetime_convert_range_dates(array('1066', '1067-12-25'), 'january_1'); + $this->assertContains('1066-01-01', $result[0]); + $this->assertContains('1067-12-25', $result[1]); + $result = neatlinetime_convert_range_dates(array('1066', '1066'), 'january_1'); + $this->assertContains('1066-01-01', $result[0]); + $this->assertContains('1066-12-31', $result[1]); + } + + public function testRangeDatesJuly1() + { + $result = neatlinetime_convert_range_dates(array('1066', '1067'), 'july_1'); + $this->assertContains('1066-07-01', $result[0]); + $this->assertContains('1067-06-30', $result[1]); + $result = neatlinetime_convert_range_dates(array('1066', '1067-05-04'), 'july_1'); + $this->assertContains('1066-07-01', $result[0]); + $this->assertContains('1067-05-04', $result[1]); + $result = neatlinetime_convert_range_dates(array('1066-03-21', '1067'), 'july_1'); + $this->assertContains('1066-03-21', $result[0]); + $this->assertContains('1067-07-01', $result[1]); + $result = neatlinetime_convert_range_dates(array('1066', '1066'), 'july_1'); + $this->assertContains('1066-01-01', $result[0]); + $this->assertContains('1066-12-31', $result[1]); + } + + public function testRangeDatesFullYear() + { + $result = neatlinetime_convert_range_dates(array('1066', '1067'), 'full_year'); + $this->assertContains('1066-01-01', $result[0]); + $this->assertContains('1067-12-31', $result[1]); + $result = neatlinetime_convert_range_dates(array('1067', '1066'), 'full_year'); + $this->assertContains('1066-01-01', $result[0]); + $this->assertContains('1067-12-31', $result[1]); + $result = neatlinetime_convert_range_dates(array('1066', '1066'), 'full_year'); + $this->assertContains('1066-01-01', $result[0]); + $this->assertContains('1066-12-31', $result[1]); + $result = neatlinetime_convert_range_dates(array('1067-04-21', '1067'), 'full_year'); + $this->assertContains('1067-04-21', $result[0]); + $this->assertContains('1067-12-31', $result[1]); + $result = neatlinetime_convert_range_dates(array('1067', '1067-04-21'), 'full_year'); + $this->assertContains('1067-01-01', $result[0]); + $this->assertContains('1067-04-21', $result[1]); + } } diff --git a/tests/integration/Helpers/LinkToTimelineTest.php b/tests/integration/Helpers/LinkToTimelineTest.php index 7c94ef9..82f8ffd 100644 --- a/tests/integration/Helpers/LinkToTimelineTest.php +++ b/tests/integration/Helpers/LinkToTimelineTest.php @@ -9,7 +9,7 @@ class LinkToTimelineTest extends NeatlineTime_Test_AppTestCase /** * Tests whether link_to_timeline() returns the correct link for a timeline. * - * @uses link_to_timeline() + * @uses ::link_to_timeline */ public function testLinkToTimeline() { @@ -18,13 +18,8 @@ public function testLinkToTimeline() $this->dispatch('neatline-time/timelines/show/1'); - $matcher = array( - 'tag' => 'a', - 'content' => $timeline->title, - 'attributes' => array( - 'href' => record_url($timeline) - ) - ); + $content = $timeline->title; + $url = record_url($timeline); $linkDefault = link_to_timeline(); $url = record_url($timeline); diff --git a/tests/integration/Helpers/NeatlineTimeGetOptionTest.php b/tests/integration/Helpers/NeatlineTimeGetOptionTest.php index f6e4bc4..a5d3d9a 100644 --- a/tests/integration/Helpers/NeatlineTimeGetOptionTest.php +++ b/tests/integration/Helpers/NeatlineTimeGetOptionTest.php @@ -1,10 +1,11 @@ db = get_db(); $this->_elementTable = $this->db->getTable('Element'); + + //The process don't use the default install process, so force options. + set_option('neatline_time_defaults', json_encode(NeatlineTime_Test_AppTestCase::$options)); } /** * Tests whether neatlinetime_get_option() returns the correct value. */ - public function testDefault() + public function testDefault() { - foreach ($this->_options as $option) { - $field = ucwords(str_replace('item_', '', $option)); - $element = $this->_elementTable->findByElementSetNameAndElementName("Dublin Core", "$field"); + $field = ucwords(str_replace('item_' , '', $option)); + $element = $this->_elementTable->findByElementSetNameAndElementName("Dublin Core", "$field"); $value = neatlinetime_get_option($option); - $this->assertEquals($value, $element->id); } - } public function testUpdatedOptions() { - - set_option('neatlinetime', serialize(array('item_title' => 1, 'item_date' => 2, 'item_description' => 3))); + set_option('neatline_time_defaults', json_encode(array( + 'item_title' => 1, + 'item_date' => 2, + 'item_description' => 3, + ))); $this->assertEquals(neatlinetime_get_option('item_title'), 1); $this->assertEquals(neatlinetime_get_option('item_date'), 2); $this->assertEquals(neatlinetime_get_option('item_description'), 3); - - } } diff --git a/tests/integration/Helpers/TimelineTest.php b/tests/integration/Helpers/TimelineTest.php index f9ee302..cfd0118 100644 --- a/tests/integration/Helpers/TimelineTest.php +++ b/tests/integration/Helpers/TimelineTest.php @@ -9,7 +9,7 @@ class TimelineTest extends NeatlineTime_Test_AppTestCase /** * Tests whether timeline() returns the correct value. * - * @uses timeline() + * @uses ::timeline **/ public function testTimelineValue() { diff --git a/tests/integration/NeatlineTimePluginTest.php b/tests/integration/NeatlineTimePluginTest.php index 3a0ec03..674fe79 100644 --- a/tests/integration/NeatlineTimePluginTest.php +++ b/tests/integration/NeatlineTimePluginTest.php @@ -3,16 +3,18 @@ /** * This tests for functionality in the Plugin object itself. - **/ + */ class NeatlineTimePluginTest extends NeatlineTime_Test_AppTestCase { - public function setUp() { parent::setUp(); $this->db = get_db(); $this->_elementTable = $this->db->getTable('Element'); + + //The process don't use the default install process, so force options. + set_option('neatline_time_defaults', json_encode(self::$options)); } /** @@ -20,18 +22,14 @@ public function setUp() */ public function testSetDefaultOptions() { - $optionNames = array('item_title', 'item_description', 'item_date'); - $options = unserialize(get_option('neatlinetime')); + $options = json_decode(get_option('neatline_time_defaults'), true); foreach ($optionNames as $optionName) { $field = ucwords(str_replace('item_', '', $optionName)); - $element = $this->_elementTable->findByElementSetNameAndElementName("Dublin Core", "$field"); + $element = $this->_elementTable->findByElementSetNameAndElementName("Dublin Core", "$field"); $this->assertEquals($options[$optionName], $element->id); } - } } - - diff --git a/tests/phpunit.xml b/tests/phpunit.xml index 00ed147..cff8445 100644 --- a/tests/phpunit.xml +++ b/tests/phpunit.xml @@ -16,4 +16,11 @@ highLowerBound="70" /> + + + . + + + + diff --git a/tests/phpunit_travis.xml b/tests/phpunit_travis.xml index 01a4e4d..879bb58 100644 --- a/tests/phpunit_travis.xml +++ b/tests/phpunit_travis.xml @@ -1,16 +1,14 @@ - - - - - - - - - integration - unit - - + + + integration + unit + + + + diff --git a/travis_setup.sh b/tests/travis_setup.sh similarity index 93% rename from travis_setup.sh rename to tests/travis_setup.sh index e8dee68..78fc990 100755 --- a/travis_setup.sh +++ b/tests/travis_setup.sh @@ -21,13 +21,12 @@ mv $OMEKA_DIR/application/tests/config.ini.changeme $OMEKA_DIR/application/tests # set up testing config sed -i 's/db.host = ""/db.host = "localhost"/' $OMEKA_DIR/application/tests/config.ini sed -i 's/db.username = ""/db.username = "root"/' $OMEKA_DIR/application/tests/config.ini -sed -i 's/db.dbname = ""/db.dbname = "omeka_test"/' $OMEKA_DIR/application/tests/config.ini +sed -i 's/db.dbname = ""/db.dbname = "omeka_test"/' $OMEKA_DIR/application/tests/config.ini sed -i 's/email.to = ""/email.to = "test@example.com"/' $OMEKA_DIR/application/tests/config.ini sed -i 's/email.administator = ""/email.administrator = "admin@example.com"/' $OMEKA_DIR/application/tests/config.ini sed -i 's/paths.maildir = ""/paths.maildir = "\/tmp"/' $OMEKA_DIR/application/tests/config.ini sed -i 's/paths.imagemagick = ""/paths.imagemagick = "\/usr\/bin\/"/' $OMEKA_DIR/application/tests/config.ini sed -i 's/256M/512M/' $OMEKA_DIR/application/tests/bootstrap.php -# symlink the plugin -cd $OMEKA_DIR/plugins && ln -s $PLUGIN_DIR - +# symlink the plugin +cd $OMEKA_DIR/plugins && ln -s $PLUGIN_DIR diff --git a/travis_tests.sh b/tests/travis_tests.sh similarity index 75% rename from travis_tests.sh rename to tests/travis_tests.sh index 679723f..8802bd4 100755 --- a/travis_tests.sh +++ b/tests/travis_tests.sh @@ -12,4 +12,5 @@ fi echo "Plugin Directory: $PLUGIN_DIR" echo "Omeka Directory: $OMEKA_DIR" -cd tests/ && phpunit --configuration phpunit_travis.xml --coverage-text +cat $(which phpunit) +cd tests/ && phpunit --configuration phpunit_travis.xml diff --git a/tests/unit/Models/NeatlineTimeTimelineTest.php b/tests/unit/Models/NeatlineTimeTimelineTest.php index ba74f5b..306e607 100644 --- a/tests/unit/Models/NeatlineTimeTimelineTest.php +++ b/tests/unit/Models/NeatlineTimeTimelineTest.php @@ -1,21 +1,22 @@ dbAdapter = new Zend_Test_DbAdapter; $this->db = new Omeka_Db($this->dbAdapter); - $this->timeline = new NeatlineTimeTimeline($this->db); + $this->timeline = new NeatlineTime_Timeline($this->db); $this->user = new User($this->db); $bootstrap = new Omeka_Test_Bootstrap; $bootstrap->getContainer()->db = $this->db; Zend_Registry::set('bootstrap', $bootstrap); + + //The process don't use the default install process, so force options. + set_option('neatline_time_defaults', json_encode(NeatlineTime_Test_AppTestCase::$options)); } public function tearDown() @@ -29,16 +30,26 @@ public function testGetSetProperties() $this->timeline->description = 'Timeline Description'; $this->timeline->public = '1'; $this->timeline->featured = '1'; - $this->timeline->creator_id = $this->user->id; - $this->timeline->query = serialize(array('range' => '1')); + $this->timeline->owner_id = $this->user->id; + $this->timeline->setQuery(array('range' => '1')); + $parameters = array( + 'item_title' => 50, + 'item_description' => 42, + 'item_date' => 43, + 'item_date_end' => '', + 'render_year' => 'january_1', + 'center_date' => '', + 'viewer' => '{}', + ); + $this->timeline->setParameters($parameters); $this->assertEquals('Timeline Title', $this->timeline->title); $this->assertEquals('Timeline Description', $this->timeline->description); $this->assertEquals('1', $this->timeline->public); $this->assertEquals('1', $this->timeline->featured); - $this->assertEquals($this->user->id, $this->timeline->creator_id); - $this->assertEquals(array('range' => '1'), unserialize($this->timeline->query)); - + $this->assertEquals($this->user->id, $this->timeline->owner_id); + $this->assertEquals(array('range' => '1'), json_decode($this->timeline->query, true)); + $this->assertEquals($parameters, json_decode($this->timeline->parameters, true)); } public function testInsertSetsAddedDate() @@ -60,7 +71,7 @@ public function testInsertSetsModifiedDate() $this->assertNotNull($this->timeline->modified); $this->assertThat(new Zend_Date($this->timeline->modified), $this->isInstanceOf('Zend_Date'), - "'modified' column should contain a valid date (signified by validity as constructor for Zend_Date)"); + "'modified' column should contain a valid date (signified by validity as constructor for Zend_Date)"); } public function testUpdateSetsModifiedDate() @@ -74,32 +85,33 @@ public function testUpdateSetsModifiedDate() "'modified' column should contain a valid date (signified by validity as constructor for Zend_Date)"); } - public function testQuerySerialization() { + public function testQueryJsonization() + { $this->dbAdapter->appendLastInsertIdToStack('1'); $this->timeline->title = 'Timeline Title'; $this->timeline->query = array('range' => '1'); $this->timeline->save(); - $this->assertTrue(is_array(unserialize($this->timeline->query))); - $this->assertEquals(array('range' => '1'), unserialize($this->timeline->query)); - + $this->assertTrue(is_array(json_decode($this->timeline->query, true))); + $this->assertEquals(array('range' => '1'), json_decode($this->timeline->query, true)); + $this->assertEquals(array('range' => '1'), $this->timeline->getQuery()); } - public function testDontReserializeQuery() + public function testDontRejsonizeQuery() { $this->dbAdapter->appendLastInsertIdToStack('1'); $this->timeline->title = 'Timeline Title'; - $this->timeline->query = serialize(array('range' => '1')); + $this->timeline->query = array('range' => '1'); $this->timeline->save(); - $this->assertTrue(is_array(unserialize($this->timeline->query))); - $this->assertEquals(array('range' => '1'), unserialize($this->timeline->query)); + $this->assertTrue(is_array(json_decode($this->timeline->query, true))); + $this->assertEquals(array('range' => '1'), json_decode($this->timeline->query, true)); $this->timeline->public = 1; $this->timeline->save(); - $this->assertTrue(is_array(unserialize($this->timeline->query))); - $this->assertEquals(array('range' => '1'), unserialize($this->timeline->query)); - + $this->assertTrue(is_array(json_decode($this->timeline->query, true))); + $this->assertEquals(array('range' => '1'), json_decode($this->timeline->query, true)); + $this->assertEquals(array('range' => '1'), $this->timeline->getQuery()); } } diff --git a/upgrade.php b/upgrade.php new file mode 100644 index 0000000..c5ef2c2 --- /dev/null +++ b/upgrade.php @@ -0,0 +1,125 @@ +NeatlineTime_Timeline}` + MODIFY COLUMN `center_date` date NULL DEFAULT '2000-01-01', + MODIFY COLUMN `added` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00', + CHANGE COLUMN `id` `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + CHANGE COLUMN `creator_id` `owner_id` INT(10) UNSIGNED NOT NULL, + ADD `parameters` TEXT COLLATE utf8_unicode_ci NOT NULL AFTER `center_date`, + ADD KEY `public` (`public`), + ADD KEY `featured` (`featured`), + ADD KEY `owner_id` (`owner_id`) + "; + $db->query($sql); + + // Copy all the "center_date" values inside the parameters. + $sql = "UPDATE `{$db->NeatlineTime_Timeline}` + SET `center_date` = NULL + WHERE `center_date` IS NULL OR `center_date` = '0000-00-00';"; + $db->query($sql); + + $sql = "UPDATE `{$db->NeatlineTime_Timeline}` + SET `parameters` = CONCAT('{\"center_date\":\"', `center_date`, '\"}') + WHERE `center_date` IS NOT NULL AND `center_date` != '0000-00-00';"; + $db->query($sql); + + $sql = " + ALTER TABLE `{$db->NeatlineTime_Timeline}` + DROP `center_date` + "; + $db->query($sql); + + set_option('neatline_time_library', $this->_options['neatline_time_library']); + + // Set default options inside timelines. + $options = unserialize(get_option('neatlinetime')); + $options = array_merge($this->_options['neatline_time_defaults'], $options); + set_option('neatline_time_defaults', json_encode($options)); + + delete_option('neatline_time_render_year'); + delete_option('neatlinetime'); + + // Update all timelines. + $timelines = get_records('NeatlineTime_Timeline', array(), 0); + foreach ($timelines as $timeline) { + $parameters = $timeline->parameters ?: array(); + if (!empty($parameters)) { + $parameters = json_decode($parameters, true); + } + $parameters = array_merge($options, $parameters); + unset($parameters['csrf_token']); + $parameters = json_encode($parameters); + // Direct update of the table. + $sql = "UPDATE `{$db->NeatlineTime_Timeline}` + SET `parameters` = {$db->quote($parameters)} + WHERE `id` = $timeline->id;"; + $db->query($sql); + } +} + +if (version_compare($oldVersion, '2.2.1', '<')) { + // Because null is forbidden now, all null values should be replaced before. + $empty = serialize(array()); + $sql = "UPDATE `{$db->NeatlineTime_Timeline}` + SET `query` = '$empty' + WHERE `query` IS NULL;"; + $db->query($sql); + + $sql = " + ALTER TABLE `{$db->NeatlineTime_Timeline}` + CHANGE `query` `query` TEXT COLLATE utf8_unicode_ci NOT NULL AFTER `parameters` + "; + $db->query($sql); + + // Convert serialized queries into json. + $timelines = get_records('NeatlineTime_Timeline', array(), 0); + + foreach ($timelines as $timeline) { + // Queries may have been converted before during the upgrade process + // or in case of a bug. + $query = @unserialize($timeline->query); + if ($query === false) { + $query = json_decode($timeline->query, true) ?: array(); + } + unset($query['csrf_token']); + $timeline->setQuery($query); + $timeline->save(); + } +} + +if (version_compare($oldVersion, '2.2.2', '<')) { + // Replace null from title and description by an empty string. + $sql = "UPDATE `{$db->NeatlineTime_Timeline}` + SET `title` = '' + WHERE `title` IS NULL;"; + $db->query($sql); + $sql = "UPDATE `{$db->NeatlineTime_Timeline}` + SET `description` = '' + WHERE `description` IS NULL;"; + $db->query($sql); + + $sql = " + ALTER TABLE `{$db->NeatlineTime_Timeline}` + CHANGE `title` `title` TINYTEXT COLLATE utf8_unicode_ci NOT NULL, + CHANGE `description` `description` TEXT COLLATE utf8_unicode_ci NOT NULL, + CHANGE `owner_id` `owner_id` INT(10) UNSIGNED NOT NULL DEFAULT '0' + "; + $db->query($sql); +} + +if (version_compare($oldVersion, '2.2.5', '<')) { + set_option('neatline_time_link_to_nav', $this->_options['neatline_time_link_to_nav']); + set_option('neatline_time_link_to_nav_main', $this->_options['neatline_time_link_to_nav_main']); +} diff --git a/views/admin/plugins/neatline-time-config-form.php b/views/admin/plugins/neatline-time-config-form.php new file mode 100644 index 0000000..98d79e7 --- /dev/null +++ b/views/admin/plugins/neatline-time-config-form.php @@ -0,0 +1,217 @@ + array('Item', 'All'), + 'sort' => 'alphaBySet', +)); +// Remove the "Select Below" label. +unset($elements['']); +?> +
+
+
+ formLabel('neatline_time_library', + __('Timeline library')); ?> +
+
+ 'Simile', 'knightlab' => 'Knightlab'); + echo $this->formSelect('neatline_time_library', + get_option('neatline_time_library') ?: 'simile', + array(), + $options); + ?> +

+ +

+
+
+
+
+ formLabel('neatline_time_internal_assets', + __('Use Internal library for Simile')); ?> +
+
+ formCheckbox('neatline_time_internal_assets', true, + array('checked' => (boolean) get_option('neatline_time_internal_assets'))); ?> +

+ +

+
+
+
+
+
+
+ formLabel('neatline_time_link_to_nav', + __('Add secondary link')); ?> +
+
+ __('None'), + 'browse' => __('Browse timelines'), + 'main' => __('Display main timeline'), + ); + echo $this->formSelect('neatline_time_link_to_nav', + get_option('neatline_time_link_to_nav') ?: 'browse', + array(), + $options); + ?> +

+ + +

+
+
+
+
+ formLabel('neatline_time_link_to_nav_main', + __('Main timeline')); ?> +
+
+ formSelect('neatline_time_link_to_nav_main', + get_option('neatline_time_link_to_nav_main') ?: '', + array(), + $options); + ?> +

+ +

+
+
+
+
+

+ + +

+
+
+ formLabel('neatline_time_defaults[item_title]', __('Item Title')); ?> +
+
+ formSelect('neatline_time_defaults[item_title]', + $defaults['item_title'], + array(), + $elements); + ?> +

+ +

+
+
+
+
+ formLabel('neatline_time_defaults[item_description]', __('Item Description')); ?> +
+
+ formSelect('neatline_time_defaults[item_description]', + $defaults['item_description'], + array(), + $elements); + ?> +

+ +

+
+
+
+
+ formLabel('neatline_time_defaults[item_date]', __('Item Date')); ?> +
+
+ formSelect('neatline_time_defaults[item_date]', + $defaults['item_date'], + array(), + $elements); + ?> +

+ + +

+
+
+
+
+ formLabel('neatline_time_defaults[item_date_end]', __('Item End Date')); ?> +
+
+ __('None')) + $elements; + echo $this->formSelect('neatline_time_defaults[item_date_end]', + $defaults['item_date_end'], + array(), + $elements); + ?> +

+ + + + +

+
+
+
+
+ formLabel('neatline_time_defaults[render_year]', __('Render Year')); ?> +
+
+ __('Pick first January'), + 'july_1' => __('Pick first July'), + 'full_year' => __('Mark entire year'), + 'skip' => __('Skip the record'), + ); + echo $this->formRadio('neatline_time_defaults[render_year]', + $defaults['render_year'], + null, + $values); + ?> +

+ +

+
+
+
+
+ formLabel('neatline_time_defaults[center_date]', __('Center Date')); ?> +
+
+ formText('neatline_time_defaults[center_date]', + $defaults['center_date'], + null); + ?> +

+ + + +

+
+
+
+
+ formLabel('neatline_time_defaults[viewer]', __('Viewer')); ?> +
+
+ formTextarea('neatline_time_defaults[viewer]', + $defaults['viewer'], + array( + 'rows' => 10, + 'cols' => 60, + )); + ?> +

+ + +

+
+
+
diff --git a/views/admin/timelines/browse.php b/views/admin/timelines/browse.php index cc80b10..e5a4592 100644 --- a/views/admin/timelines/browse.php +++ b/views/admin/timelines/browse.php @@ -3,7 +3,7 @@ * The browse view for the Timelines administrative panel. */ -$head = array('bodyclass' => 'timelines primary', +$head = array('bodyclass' => 'timelines primary', 'title' => html_escape(__('Neatline Time | Browse Timelines'))); echo head($head); ?> @@ -58,4 +58,4 @@

.

- + 'timelines primary', 'title' => html_escape($title)); diff --git a/views/admin/timelines/query.php b/views/admin/timelines/query.php index 2dab8ce..5c9f652 100644 --- a/views/admin/timelines/query.php +++ b/views/admin/timelines/query.php @@ -10,12 +10,12 @@ ?> query); -if ($query && is_array($query)) { +$query = $neatline_time_timeline->getQuery(); +if ($query) { ?>

'timelines primary', - 'title' => __('Neatline Time | %s', strip_formatting($timelineTitle)) - ); +$head = array( + 'bodyclass' => 'timelines primary', + 'title' => __('Neatline Time | %s', strip_formatting($timelineTitle)), +); echo head($head); +echo flash(); ?>
- partial('timelines/_timeline.php', array('center_date' => metadata($neatline_time_timeline, 'center_date'))); ?> + partial('timelines/' . $libraryPartial . '.php', + array( + 'timeline' => $neatline_time_timeline, + )); ?> query); -if ($query && is_array($query)) { +$query = $neatline_time_timeline->getQuery(); +if ($query) { ?> -

-

- +

+
-
- - 'big green button')); ?> - 'big green button')); ?> - - - 'delete-confirm big red button')); ?> +
+ + 'big green button')); ?> + 'big green button')); ?> + + + 'delete-confirm big red button')); ?> +
-
- + + 'timelines primary', 'title' => html_escape(__('Browse Timelines'))); echo head($head); ?>
-

+

+ + +
@@ -24,4 +31,4 @@

- + 'timelines primary', - 'title' => metadata($neatline_time_timeline, 'title') - ); +$timelineTitle = metadata($neatline_time_timeline, 'title'); +$head = array( + 'bodyclass' => 'timelines primary', + 'title' => $timelineTitle, +); echo head($head); ?> -

- - - partial('timelines/_timeline.php', array('center_date' => metadata($neatline_time_timeline, 'center_date'))); ?> - +

+ partial('timelines/' . $libraryPartial . '.php', + array( + 'timeline' => $neatline_time_timeline, + )); ?> - - +formSelect($formStem . '[options][timeline-id]', @$options['timeline-id'], array(), - get_table_options('NeatlineTimeTimeline')); + get_table_options('NeatlineTime_Timeline')); ?>
diff --git a/views/shared/exhibit_layouts/neatline-time/layout.php b/views/shared/exhibit_layouts/neatline-time/layout.php index 6b6a8e4..99c04b1 100644 --- a/views/shared/exhibit_layouts/neatline-time/layout.php +++ b/views/shared/exhibit_layouts/neatline-time/layout.php @@ -1,11 +1,15 @@ partial('timelines/_timeline.php', array('center_date' => metadata($timeline, 'center_date'))); -?> +if (!array_key_exists('timeline-id', $options)) { + return; +} +$timeline = get_db()->getTable('NeatlineTime_Timeline')->find($options['timeline-id']); +if (empty($timeline)) { + return; +} +set_current_record('neatline_time_timeline', $timeline); +$library = get_option('neatline_time_library') ?: 'simile'; +$libraryPartial = $library == 'simile' ? '_timeline' : '_timeline_' . $library; +echo $this->partial('timelines/' . $libraryPartial . '.php', + array( + 'timeline' => $timeline, +)); diff --git a/views/shared/javascripts/neatline-time-scripts.js b/views/shared/javascripts/neatline-time-scripts.js index 019ca07..7a55c1d 100644 --- a/views/shared/javascripts/neatline-time-scripts.js +++ b/views/shared/javascripts/neatline-time-scripts.js @@ -1,122 +1,141 @@ var NeatlineTime = { - resizeTimerID: null, + resizeTimerID: null, - resizeTimeline: function() { - if (resizeTimerID == null) { - resizeTimerID = window.setTimeout(function() { - resizeTimerID = null; - tl.layout(); - }, 500); - } - }, - - _monkeyPatchFillInfoBubble: function() { - var oldFillInfoBubble = - Timeline.DefaultEventSource.Event.prototype.fillInfoBubble; - Timeline.DefaultEventSource.Event.prototype.fillInfoBubble = - function(elmt, theme, labeller) { - var doc = elmt.ownerDocument; + resizeTimeline: function() { + if (resizeTimerID == null) { + resizeTimerID = window.setTimeout(function() { + resizeTimerID = null; + tl.layout(); + }, 500); + } + }, - var title = this.getText(); - var link = this.getLink(); - var image = this.getImage(); + _monkeyPatchFillInfoBubble: function() { + var oldFillInfoBubble = + Timeline.DefaultEventSource.Event.prototype.fillInfoBubble; + Timeline.DefaultEventSource.Event.prototype.fillInfoBubble = + function(elmt, theme, labeller) { + var doc = elmt.ownerDocument; - if (image != null) { - var img = doc.createElement("img"); - img.src = image; + var title = this.getText(); + var link = this.getLink(); + var image = this.getImage(); - theme.event.bubble.imageStyler(img); - elmt.appendChild(img); - } + if (image != null) { + var img = doc.createElement("img"); + img.src = image; - var divTitle = doc.createElement("div"); - var textTitle = doc.createElement("span"); - textTitle.innerHTML = title; - if (link != null) { - var a = doc.createElement("a"); - a.href = link; - a.appendChild(textTitle); - divTitle.appendChild(a); - } else { - divTitle.appendChild(textTitle); - } - theme.event.bubble.titleStyler(divTitle); - elmt.appendChild(divTitle); + theme.event.bubble.imageStyler(img); + elmt.appendChild(img); + } - var divBody = doc.createElement("div"); - this.fillDescription(divBody); - theme.event.bubble.bodyStyler(divBody); - elmt.appendChild(divBody); + var divTitle = doc.createElement("div"); + var textTitle = doc.createElement("span"); + textTitle.innerHTML = title; + if (link != null) { + var a = doc.createElement("a"); + a.href = link; + a.appendChild(textTitle); + divTitle.appendChild(a); + } else { + divTitle.appendChild(textTitle); + } + theme.event.bubble.titleStyler(divTitle); + elmt.appendChild(divTitle); - var divTime = doc.createElement("div"); - this.fillTime(divTime, labeller); - theme.event.bubble.timeStyler(divTime); - elmt.appendChild(divTime); + var divBody = doc.createElement("div"); + this.fillDescription(divBody); + theme.event.bubble.bodyStyler(divBody); + elmt.appendChild(divBody); - var divWiki = doc.createElement("div"); - this.fillWikiInfo(divWiki); - theme.event.bubble.wikiStyler(divWiki); - elmt.appendChild(divWiki); - }; - }, -// may need to set a default value of none/false to centerDate - loadTimeline: function(timelineId, timelineData, centerDate) { - NeatlineTime._monkeyPatchFillInfoBubble(); - var eventSource = new Timeline.DefaultEventSource(); + var divTime = doc.createElement("div"); + this.fillTime(divTime, labeller); + theme.event.bubble.timeStyler(divTime); + elmt.appendChild(divTime); - var defaultTheme = Timeline.getDefaultTheme(); - defaultTheme.mouseWheel = 'zoom'; + var divWiki = doc.createElement("div"); + this.fillWikiInfo(divWiki); + theme.event.bubble.wikiStyler(divWiki); + elmt.appendChild(divWiki); + }; + }, - var bandInfos = [ - Timeline.createBandInfo({ - eventSource: eventSource, - width: "80%", - intervalUnit: Timeline.DateTime.MONTH, - intervalPixels: 100, - zoomIndex: 10, - zoomSteps: new Array( - {pixelsPerInterval: 280, unit: Timeline.DateTime.HOUR}, - {pixelsPerInterval: 140, unit: Timeline.DateTime.HOUR}, - {pixelsPerInterval: 70, unit: Timeline.DateTime.HOUR}, - {pixelsPerInterval: 35, unit: Timeline.DateTime.HOUR}, - {pixelsPerInterval: 400, unit: Timeline.DateTime.DAY}, - {pixelsPerInterval: 200, unit: Timeline.DateTime.DAY}, - {pixelsPerInterval: 100, unit: Timeline.DateTime.DAY}, - {pixelsPerInterval: 50, unit: Timeline.DateTime.DAY}, - {pixelsPerInterval: 400, unit: Timeline.DateTime.MONTH}, - {pixelsPerInterval: 200, unit: Timeline.DateTime.MONTH}, - {pixelsPerInterval: 100, unit: Timeline.DateTime.MONTH} // DEFAULT zoomIndex - ) - }), - Timeline.createBandInfo({ - overview: true, - eventSource: eventSource, - width: "20%", - intervalUnit: Timeline.DateTime.YEAR, - intervalPixels: 200 - }) - ]; + loadTimeline: function(timelineId, timelineData, params) { + NeatlineTime._monkeyPatchFillInfoBubble(); + var eventSource = new Timeline.DefaultEventSource(); - bandInfos[1].syncWith = 0; - bandInfos[1].highlight = true; + var defaultTheme = Timeline.getDefaultTheme(); + defaultTheme.mouseWheel = 'zoom'; + // defaultTheme.autoWidth = true; - var tl = Timeline.create(document.getElementById(timelineId), bandInfos); - tl.loadJSON(timelineData, function(json, url) { - if (json.events.length > 0) { - eventSource.loadJSON(json, url); - // If centerDate is set, use it, otherwise use the earliest date - console.log(centerDate); - var earliestDate = eventSource.getEarliestDate(); - console.log("earliestDate: " + earliestDate); - if (centerDate === '0000-00-00') { - centerDate = earliestDate; - console.log("centerDate: " + centerDate); + var bandInfos = []; + if (typeof params.bandInfos !== 'undefined' && params.bandInfos.length) { + for (i = 0; i < params.bandInfos.length; ++i) { + if (typeof params.bandInfos[i].eventSource === 'undefined') { + params.bandInfos[i].eventSource = eventSource; + } + bandInfos[i] = Timeline.createBandInfo(params.bandInfos[i]); } - var parsedDate = Timeline.DateTime.parseGregorianDateTime(centerDate); - console.log('parseddate: ', parsedDate); - tl.getBand(0).setCenterVisibleDate(parsedDate); + } else { + bandInfos = [ + Timeline.createBandInfo({ + eventSource: eventSource, + width: "80%", + intervalUnit: Timeline.DateTime.MONTH, + intervalPixels: 100, + zoomIndex: 10, + zoomSteps: new Array( + {pixelsPerInterval: 280, unit: Timeline.DateTime.HOUR}, + {pixelsPerInterval: 140, unit: Timeline.DateTime.HOUR}, + {pixelsPerInterval: 70, unit: Timeline.DateTime.HOUR}, + {pixelsPerInterval: 35, unit: Timeline.DateTime.HOUR}, + {pixelsPerInterval: 400, unit: Timeline.DateTime.DAY}, + {pixelsPerInterval: 200, unit: Timeline.DateTime.DAY}, + {pixelsPerInterval: 100, unit: Timeline.DateTime.DAY}, + {pixelsPerInterval: 50, unit: Timeline.DateTime.DAY}, + {pixelsPerInterval: 400, unit: Timeline.DateTime.MONTH}, + {pixelsPerInterval: 200, unit: Timeline.DateTime.MONTH}, + {pixelsPerInterval: 100, unit: Timeline.DateTime.MONTH} // DEFAULT zoomIndex + ) + }), + Timeline.createBandInfo({ + overview: true, + eventSource: eventSource, + width: "20%", + intervalUnit: Timeline.DateTime.YEAR, + intervalPixels: 200 + }) + ]; } - }); - } + // All bands are synchronized with the first. + for (i = 1; i < bandInfos.length; ++i) { + bandInfos[i].syncWith = 0; + bandInfos[i].highlight = true; + } + + var tl = Timeline.create(document.getElementById(timelineId), bandInfos); + tl.loadJSON(timelineData, function(json, url) { + // log the timelineData, and see what's there + // figure out what's creating the timelineData json + // console.log("json: ", json); + // console.log("timelineData: ", timelineData); + if (json.events.length > 0) { + eventSource.loadJSON(json, url); + var centerDate = params.centerDate; + // console.log("centerDate: " + centerDate); + if (!centerDate) { + centerDate = new Date().toJSON().slice(0,10); + } else if (centerDate === '0000-00-00') { + centerDate = eventSource.getEarliestDate(); + } else if (centerDate === '9999-99-99') { + centerDate = eventSource.getLatestDate(); + } + // console.log("centerDate: " + centerDate); + var parsedDate = Timeline.DateTime.parseGregorianDateTime(centerDate); + // console.log('parseddate: ', parsedDate); + tl.getBand(0).setCenterVisibleDate(parsedDate); + } + }); + } }; diff --git a/views/shared/javascripts/simile/timeline-api/timeline-api.js b/views/shared/javascripts/simile/timeline-api/timeline-api.js index 08c4749..dd05572 100644 --- a/views/shared/javascripts/simile/timeline-api/timeline-api.js +++ b/views/shared/javascripts/simile/timeline-api/timeline-api.js @@ -8,7 +8,7 @@ * To run from the MIT copy of Timeline: * Include this file in your HTML file as follows: * - * * * @@ -254,7 +254,7 @@ var url = useLocalResources ? "http://127.0.0.1:9999/ajax/api/simile-ajax-api.js?bundle=false" : - "http://static.simile.mit.edu/ajax/api-2.2.0/simile-ajax-api.js"; + "http://api.simile-widgets.org/ajax/2.2.1/simile-ajax-api.js"; if (typeof Timeline_ajax_url == "string") { url = Timeline_ajax_url; } diff --git a/views/shared/timelines/_timeline.php b/views/shared/timelines/_timeline.php index d755509..f805bec 100644 --- a/views/shared/timelines/_timeline.php +++ b/views/shared/timelines/_timeline.php @@ -2,19 +2,25 @@ /** * Timeline display partial. */ + +// Get the timeline +if (empty($timeline)) $timeline = get_current_record('neatline_time_timeline'); ?>
diff --git a/views/shared/timelines/_timeline_knightlab.php b/views/shared/timelines/_timeline_knightlab.php new file mode 100644 index 0000000..943ba62 --- /dev/null +++ b/views/shared/timelines/_timeline_knightlab.php @@ -0,0 +1,97 @@ + + +
+ + diff --git a/views/shared/timelines/items.neatlinetime-json.php b/views/shared/timelines/items.neatlinetime-json.php index 5c9aaf6..d797afb 100644 --- a/views/shared/timelines/items.neatlinetime-json.php +++ b/views/shared/timelines/items.neatlinetime-json.php @@ -1,55 +1,58 @@ '200'), $item); - - $itemDates = neatlinetime_get_item_text('item_date', array('all' => true), $item); +$timeline = $neatline_time_timeline; +if (empty($timeline)) { + return; +} - $fileUrl = null; - if ($file = get_db()->getTable('File')->findWithImages(metadata($item, 'id'), 0)) { - $fileUrl = metadata($file, 'square_thumbnail_uri'); +$events = array(); +foreach ($items as $item) { + $itemDates = neatlinetime_metadata($item, 'item_date', array('all' => true, 'no_filter' => true), $timeline); + if (empty($itemDates)) { + continue; } - - if (!empty($itemDates)) { - foreach ($itemDates as $itemDate) { - $itemDate = $itemDate; - - $neatlineTimeEvent = array(); - $dateArray = explode('/', $itemDate); - - if ($dateStart = neatlinetime_convert_date(trim($dateArray[0]))) { - $neatlineTimeEvent['start'] = $dateStart; - - if (count($dateArray) == 2) { - $neatlineTimeEvent['end'] = neatlinetime_convert_date(trim($dateArray[1])); - } - - $neatlineTimeEvent['title'] = $itemTitle; - $neatlineTimeEvent['link'] = $itemLink; - $neatlineTimeEvent['classname'] = neatlinetime_item_class($item); - - if ($fileUrl) { - $neatlineTimeEvent['image'] = $fileUrl; - } - - $neatlineTimeEvent['description'] = $itemDescription; - $neatlineTimeEvents[] = $neatlineTimeEvent; - } + $itemTitle = strip_formatting(neatlinetime_metadata($item, 'item_title', array(), $timeline)); + $itemDescription = neatlinetime_metadata($item, 'item_description', array('snippet' => '200'), $timeline); + $itemDatesEnd = neatlinetime_metadata($item, 'item_date_end', array('all' => true, 'no_filter' => true), $timeline) ?: []; + $itemLink = record_url($item); + $file = get_db()->getTable('File')->findWithImages($item->id, 0); + $fileUrl = $file ? metadata($file, 'square_thumbnail_uri') : null; + foreach ($itemDates as $key => $itemDate) { + $event = array(); + if (empty($itemDatesEnd[$key])) { + list($dateStart, $dateEnd) = neatlinetime_convert_any_date($itemDate, $timeline->getProperty('render_year')); + } else { + list($dateStart, $dateEnd) = neatlinetime_convert_two_dates($itemDate, $itemDatesEnd[$key], $timeline->getProperty('render_year')); } + if (!$dateStart) { + continue; + } + $event['start'] = $dateStart; + if (!is_null($dateEnd)) { + $event['end'] = $dateEnd; + } + $event['title'] = $itemTitle; + $event['link'] = $itemLink; + $event['classname'] = neatlinetime_item_class($item); + if ($fileUrl) { + $event['image'] = $fileUrl; + } + $event['description'] = $itemDescription; + $events[] = $event; } } -$neatlineTimeArray = array(); -$neatlineTimeArray['dateTimeFormat'] = "iso8601"; -$neatlineTimeArray['events'] = $neatlineTimeEvents; - -$neatlinetimeJson = json_encode($neatlineTimeArray); +$data = array(); +$data['dateTimeFormat'] = 'iso8601'; +$data['events'] = $events; -echo $neatlinetimeJson; +$dataJson = version_compare(phpversion(), '5.4.0', '<') + ? json_encode($data) + : json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); +echo $dataJson;