Commit 1b0c1134 authored by Ghislain Loas's avatar Ghislain Loas
Browse files

Merge remote-tracking branch 'refs/remotes/origin/stable' into origin-hotline-master

Conflicts:
	cosmogramme/sql/patch/patch_337.php
	tests/db/UpgradeDBTest.php
parents 8109a04f 705ed9f0
- ticket #49315 : AFI Multimédia : Les notes renseignées sur un poste sont visible lors de la réservation.
\ No newline at end of file
- ticket #65552 : AFI-Multiumédia : les réservations sont maintenant affichées dès la sélection du site.
\ No newline at end of file
- ticket #66943 : AFI Multimédia : Les réservations remplacées par une autre sont mise à jour.
......@@ -33,8 +33,46 @@ class Admin_MultimediaController extends ZendAfi_Controller_Action {
}
$devices = $location->getDevices();
$holds = [];
foreach($devices as $device)
$holds = array_merge($holds, $device->getHoldsForTimeline());
$devices_ids = array_unique((new Storm_Model_Collection($devices))->collect('id')->getArrayCopy());
$holds = empty($holds)
? []
: array_map(function($hold) use ($devices_ids)
{
return (new Class_Entity())
->setId($hold->getId())
->setStart($hold->getStart())
->setEnd($hold->getEnd())
->setLabel($hold->getLibelleDevice())
->setContent(
$this->view->tag('p',
sprintf('Le poste "%s" (OS: %s) a été réservé par %s dans le site de "%s"',
$hold->getLibelleDevice(),
$hold->getOs(),
$hold->getUsername(),
$hold->getLibelleBib()))
. $this->view->tagAnchor(['action' => 'delete-hold',
'id' => $hold->getId()],
$this->_('Supprimer'),
['onclick' => 'return confirm('' . $this->_('Supprimer la réservation ?') . ' ')']))
->setRow((array_search($hold->getIdDevice(), $devices_ids)) + 1);
},
$holds);
$holds = (new Class_Entity())
->setNodes($holds)
->setEmptyMessage($this->_('Aucune réservation pour ce site.'))
->setId('holds');
$this->view->subview = $this->view->partial('multimedia/browse.phtml',
['titre' => sprintf('Postes du site multimédia "%s"', $location->getLibelle()),
'location' => $location,
'holds' => $holds,
'devices' => $devices]);
$this->_forward('index');
}
......
<h2><?php echo $this->titre;?></h2>
<?php if (0 == count($this->devices)) { ?>
<div class="infos"><?php echo $this->_('Aucun poste dans ce groupe');?></div>
<?php } else { ?>
<?php echo $this->tagModelTable(
$this->devices,
[$this->_('Libellé'), $this->_('Os')],
['libelle', 'os'],
[ ['action' => 'holds',
'content' => function($model) {
return $this->boutonIco('type=show').$model->numberOfNextHolds().' rés.';
}]
],
'multimedia_devices',
'group_libelle');?>
</table>
<?php } ?>
\ No newline at end of file
<?php
if (0 == count($this->devices)) {
echo $this->tag('div', $this->_('Aucun poste dans ce groupe'), ['class' => 'infos']);
return;
}
echo $this->button((new Class_Entity())
->setText('Changer d\'affichage')
->setAttribs(['onclick' => '$(this).siblings(\'div.table-view, div.timeline-view\').toggle()']));
echo $this->tag('div',
$this->tag('h2', $this->titre)
. $this->tagModelTable(
$this->devices,
[$this->_('Libellé'), $this->_('Os')],
['libelle', 'os'],
[ ['action' => 'holds',
'content' => function($model) {
return $this->boutonIco('type=show').$model->numberOfNextHolds().' rés.';
}]
],
'multimedia_devices',
'group_libelle'),
['class' => 'table-view',
'style' => 'display: none']);
echo $this->tag('div',
$this->tag('h2', $this->_('Liste des réservations du site "%s"', $this->location->getLibelle()))
. $this->tagTimeline($this->holds),
['class' => 'timeline-view']);
\ No newline at end of file
......@@ -17,7 +17,13 @@
<ul>
<?php
foreach ($this->devices as $device)
echo '<li>'.$this->tagAnchor(array('device' => $device->getId()), $this->escape($device->getLibelle() . ' (' . $device->getOs() . ')')) . '</li>';
echo '<li>'
. $this->tagAnchor(
array('device' => $device->getId()),
$this->escape($device->getLibelle()
. ' (' . $device->getOs() . ')'))
. $this->tag('p', $device->getNote())
. '</li>';
?>
</ul>
</div>
......
......@@ -15,4 +15,4 @@ try {
$adapter->query('alter table album drop column auteur');
$adapter->query('alter table album drop column editeur');
} catch(Exception $e) {
}
}
\ No newline at end of file
<?php
$adapter = Zend_Db_Table_Abstract::getDefaultAdapter();
try{
$adapter = Zend_Db_Table_Abstract::getDefaultAdapter();
$adapter->query('alter table multimedia_device add column note text not null');
} catch(Exception $e) {}
......@@ -41,3 +41,5 @@
| CodeMirror | MIT Licence | | Edition code source javascript | | http://codemirror.net |
| Butterfly Lighbox | GPL | - | Accessible lightbox plugin | | http://irama.org/web/dhtml/butterfly/ |
| php-redmine-api | MIT | - | A simple Object Oriented wrapper for Redmine API, written with PHP5. | | https://github.com/kbsali/php-redmine-api |
| jquery.timeline | MIT | - | Lister les réservations des postes multimédia. | | https://github.com/ka215/jquery.timeline |
......@@ -28,11 +28,16 @@ class Multimedia_DeviceLoader extends Storm_Model_Loader {
public function fromJsonModelWithGroup($json_model, $device_group) {
if (!$model = $this->findByIdOrigineAndLocation($json_model->id, $device_group->getLocation()))
$model = $this->newInstance()->setIdOrigine($this->getIdOrigineWithLocation($json_model->id, $device_group->getLocation()));
$note = isset($json_model->note)
? $json_model->note
: '';
$model
->setLibelle($json_model->libelle)
->setOs($json_model->os)
->setGroup($device_group)
->setDisabled($json_model->maintenance)
->setNote($note)
->save();
return $model;
}
......@@ -71,6 +76,7 @@ class Class_Multimedia_Device extends Storm_Model_Abstract {
'order' => 'start',
'dependents' => 'delete']];
protected $_default_attribute_values = ['note' => ''];
/**
* @param $start int
......@@ -131,14 +137,17 @@ class Class_Multimedia_Device extends Storm_Model_Abstract {
if (null == ($start = $this->getPreviousStartTime()))
return null;
return $this->createHoldWithStartTimeAndDuration($user, $start, $temps);
return $this->createHoldWithStartTimeAndDuration($user, $start, $temps, $current_hold);
}
public function createHoldWithStartTimeAndDuration($user, $start, $duration){
public function createHoldWithStartTimeAndDuration($user, $start, $duration, $current_hold = null){
$end = $this->findHoldEndForTodayFrom($start, $duration );
if ($end <= $start)
return null;
if($current_hold)
$current_hold->setEnd($start)->save();
$hold = Class_Multimedia_DeviceHold::getLoader()
->newInstance()
......@@ -206,12 +215,12 @@ class Class_Multimedia_Device extends Storm_Model_Abstract {
* @return timestamp
*/
public function findHoldEndForTodayFrom($start, $temps=null) {
$nbSlotMax = (!isset($temps)) ? $this->getAutoholdSlotsMax()
: ceil($temps / $this->getSlotSize());
$nbSlotMax = (!isset($temps))
? (int) $this->getAutoholdSlotsMax()
: (int) ceil($temps / $this->getSlotSize());
// fin de créneau par défaut selon config
$end = $start + (60 * $nbSlotMax * $this->getSlotSize());
$end = (int) $start + (60 * $nbSlotMax * $this->getSlotSize());
// si on dépasse la fin de journée on se limite à la fin de journée
if ($end > ($next_closing = $this->getMaxTimeForToday()))
......@@ -221,6 +230,7 @@ class Class_Multimedia_Device extends Storm_Model_Abstract {
if (null != ($next_start = $this->getNextHoldStart())
and $end > $next_start)
$end = $next_start;
return $end;
}
......@@ -323,4 +333,12 @@ class Class_Multimedia_Device extends Storm_Model_Abstract {
'where' => 'start>='.$this->getTimeSource()->date(),
'order' => 'start']);
}
public function getHoldsForTimeline() {
return Class_Multimedia_DeviceHold::findAllBy(['role' => 'device',
'model' => $this,
'where' => 'start >= ' . $this->getTimeSource()->nextMonths(-1),
'order' => 'start']);
}
}
\ No newline at end of file
......@@ -202,6 +202,8 @@ class Class_Multimedia_DeviceHold extends Storm_Model_Abstract {
* @return string
*/
public function getUsername() {
if(!$this->getUser())
return '';
return $this->getUser()->getNomComplet();
}
......
......@@ -24,8 +24,6 @@ class Class_Multimedia_Opening {
protected $_matched, $_range_matched, $_range_closed=false;
public function getForDateAndLocation($date, $location) {
$this->_matched;
if (!$openings = $location->getOuverturesMultimedia())
return;
......
<?php
/**
* Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
*
* BOKEH is free software; you can redistribute it and/or modify
* it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
* the Free Software Foundation.
*
* There are special exceptions to the terms and conditions of the AGPL as it
* is applied to this software (see README file).
*
* BOKEH is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
* along with BOKEH; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
class ZendAfi_View_Helper_TagTimeline extends ZendAfi_View_Helper_BaseHelper {
use Trait_TimeSource;
protected $_bg_colors = [];
public function tagTimeline($instance) {
if(!$id = $instance->getId())
return '';
if(!$nodes = $instance->getNodes())
return $this->_tag('p', $instance->getEmptyMessage(), ['class' => 'info']);
$nodes_data = [];
foreach($nodes as $node)
$nodes_data[] = $this->_tag('li',
$node->getLabel(),
['data-timeline-node' => $this->_getNodeData($node)]);
Class_ScriptLoader::getInstance()
->addOPACPluginStyleSheet('jquery.timeline-master/dist/timeline.min')
->addOPACPluginScript('jquery.timeline-master/src/timeline')
->addJQueryReady(sprintf('$("#%s").timeline(%s);',
$instance->getId(),
json_encode(['startDatetime' => $this->_getFirstDate($nodes),
'range' => $this->_getDaysToLastNode($nodes),
'rangeAlign' => 'current',
'minGridSize' => 40,
'minuteInterval' => 15,
'scale' => 'days',
'rows' => count($nodes_data) + 1,
'langsDir' => Class_Url::baseUrl() . '/public/opac/java/jquery.timeline-master/dist/langs/',
'httpLnaguage' => true])));
return $this->_tag('div',
$this->_tag('ul',
implode($nodes_data),
['class' => 'timeline-events']),
['id' => $instance->getId()])
. $this->_tag('div', '', ['class' => 'timeline-event-view']);
}
protected function _getNodeData($node) {
return str_replace('"',
"'",
json_encode(['start' => date('Y-m-d H:i:s', $node->getStart()),
'end' => date('Y-m-d H:i:s', $node->getEnd()),
'row' => $node->getRow(),
'bgColor' => $this->_getBgColor($node),
'content' => $this->_tag('p',
$node->getContent())]));
}
protected function _getBgColor($node) {
if($color = $node->getBgColor())
return $color;
return isset($this->_bg_colors[$node->getRow()])
? $this->_bg_colors[$node->getRow()]
: $this->_addBgColor(sprintf('#%06X', mt_rand(0, 0xFFFFFF)), $node->getRow());
}
protected function _addBgColor($color, $key) {
$this->_bg_colors[$key] = $color;
return $color;
}
protected function _getDaysToLastNode($nodes) {
$timestamps = (new Storm_Collection($nodes))
->collect(function($model)
{
return $model->getEnd();
})
->getArrayCopy();
$today = $this->getTimeSource()->time();
$max = ($max = max($timestamps) > $today)
? $max
: $today;
$min = min($timestamps);
$days = ceil(abs($max - $min) / 86400);
return ($days ? $days : 1) +1 ;
}
protected function _getFirstDate($nodes) {
$timestamps = (new Storm_Collection($nodes))
->collect(function($model)
{
return $model->getStart();
})
->getArrayCopy();
return date('Y-m-d', min($timestamps));
}
}
\ No newline at end of file
{
"extends": "eslint:recommended",
"env": {
"browser": true,
"jquery": true
},
"rules": {
"no-unused-vars": ["warn", { "vars": "all", "args": "after-used", "ignoreRestSiblings": false }],
"no-console" : 1
}
}
\ No newline at end of file
node_modules/
dist/imgs/
*.org.*
docs/debug*
MIT License
Copyright (c) 2017 ka2
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
[![Packagist](https://img.shields.io/packagist/l/doctrine/orm.svg)](https://raw.githubusercontent.com/ka215/jquery.timeline/master/LICENSE)
jQuery.Timeline
===
You are able to easily create two types of horizontal timeline with this jQuery plugin.
![jQuery.Timeline Image](https://ka215.github.io/jquery.timeline/imgs/jquery_timeline_image.png)
## DEMO
For various examples, please see here:
* [Bar Type Timeline](https://ka215.github.io/jquery.timeline/index.html)
* [Point Type Timeline](https://ka215.github.io/jquery.timeline/index2.html)
* [Multi Languages](https://ka215.github.io/jquery.timeline/index3.html)
## Browser Support
jQuery.Timeline supports the following browsers:
* **Chrome** : Recommended latest version as active support
* **Firefox** : Recommended latest version as active support
* **Safari** : Recommended latest version as active support
* **Opera** : Recommended latest version as active support
* **Edge** : Recommended latest version as active support
* **IE** : Recommended latest version as active support
## Dependencies
JQuery.Timeline works normal operation with jQuery version **1.9.0** or later. We recommend that you use the latest version **3.x**.
## Usage
Use resources in the dist directory in the repository package.
```html
<link rel="stylesheet" href="./dist/timeline.min.css">
<script src="./dist/timeline.min.js"></script>
```
### HTML:
```html
<!-- Timeline Block -->
<div id="myTimeline">
<ul class="timeline-events">
<li data-timeline-node="{ start:'2017-05-26 10:00',end:'2017-05-26 13:00',content:'text text text text ...' }">Event Label</li>
<li data-timeline-node="{ start:'2017-05-26 23:10',end:'2017-05-27 1:30',content:'<p>In this way, you can include <em>HTML tags</em> in the event body.<br>:<br>:</p>' }">Event Label</li>
</ul>
</div>
<!-- Timeline Event Detail View Area (optional) -->
<div class="timeline-event-view"></div>
```
**Note**: The tag of the event list wrapped in the timeline block is not "**ul**" fixed. If the class name is a "**timeline-events**", it can be a "**div**" tag or the like.
### jQuery:
```javascript
$(function () {
$("#myTimeline").timeline();
});
```
## Options
You can pass options on plugin initialization. For example:
```javascript
$("#myTimeline").timeline({
startDatetime: '2017-05-25',
rows: 6,
datetimeFormat: { meta: 'g:i A, D F j, Y' },
rangeAlign: 'center'
});
```
| Option | Type | Default | Description |
|--------|------|:--------|-------------|
| type | String | bar | View type of timeline event is either "**bar**" or "**point**" |
| scale | String | days | Timetable's top level scale is either "**years**" or "**months**" or "**days**" |
| startDatetime | String | currently | Default set datetime as viewing timetable; format is `"^[-+]d{4}(/\|-)d{2}(/\|-)d{2}\sd{2}:d{2}:d{2}$"` or "**currently**" |
| datetimePrefix | String | | The prefix of the date and time notation displayed in the headline |
| showHeadline | Boolean | true | Whether to display headline |
| datetimeFormat | Object | `{full:"j M Y", year:"Y", month:"M", day:"D, j M", years:"Y", months:"F", days:"j", meta:"Y/m/d H:i", metato:""}` | Available formats are here: [fn.date.js](https://gist.github.com/ka215/20cbab58e4f7d4e5508a07cff8d64b00); Since version 1.0.3, it's able to define the date format displayed in the meta field of the event detail. In addition, it can be specified as a language JSON file for multilingual support. |
| minuteInterval | Integer | 30 | Recommend more than 5 minutes; only if top scale is "days" (Deprecated) |
| zerofillYear | Boolean | false | It's outputted at the "0099" if true, the "99" if false |
| range | Integer | 3 | The default view range of the timetable starting from the "startDatetime" |
| rows | Integer | 5 | Rows of timeline event area |
| rowHeight | Integer | 40 | Height of one row |
| height | Mixed | auto | Fixed height (pixel) of timeline event area; default "auto" is (rows * rowHeight)px |
| minGridPer | Integer | 2 | Minimum grid per |
| minGridSize | Integer | 30 | Minimum size (pixel) of timeline grid; It needs 5 pixels or more |
| rangeAlign | String | current | Possible values are "**left**", "**center**", "**right**", "**current**", "**latest**" and **specific event id**
| naviIcon | Object | `{left:"jqtl-circle-left", right:"jqtl-circle-right"}` | Define class name |
| showPointer | Boolean | true | Whether to display needle pointer on the current datetime |
| i18n | Object | (omission) | Define translated text for internationalization of datetime format converted by datetime format. For details, refer to the section on [Internationalization](#Internationalization). |
| langsDir | String | ./langs/ | Since ver.1.0.3, you can specify the path that stores the language files for multilingualization. Please specify by relative path or absolute URL from HTML where js script is loaded. |
| httpLanguage | Boolean | false | Whether to obtain the language setting of the browsing environment from the server side header. |
## Methods
The initialized Timeline object can do various operations by using method. It is also possible to execute multiple methods by chaining each method.
```javascript
$("#myTimeline").timeline({
type : "bar",
range : 5
}).timeline("initialized", function( self, data ){
console.log([ "user's callback", self, data ]);
});
```
| Method | Description | Arguments | Usage |
|--------|:------------|-----------|:-------|
| initialized | Called after plugin initialization, just before timeline block is rendered. | Callback Function | `$.timeline('initialized', function( self, data ){ alert('initialization complete!'); });` |
| destroy | Destroy the timeline object created by the plugin. | - | `$.timeline('destroy');` |
| show | Display hidden timeline objects. | - | `$.timeline('show');` |
| hide | Hide the displayed timeline object. | - | `$.timeline('hide');` |
| render | Re-render the timeline block. At this time, the event operated by the method is discarded and only the initial event is placed. | - | |
| dateback | Put the timeline back to the past. It is the same as clicking on the left navigation icon. | - | `$.timeline('dateback');` |
| dateforth | Go forth the timeline to the future. It is the same as clicking the right navigation icon. | - | `$.timeline('dateforth');` |
| alignment | Adjust the center position of the timeline. | - | `$.timeline('alignment', 'center');` |
| getOptions | Get all option values for timeline. | - | `var tlOptions = $('#myTimeline').timeline('getOptions');` |
| addEvent | Add any events to the timeline. You can also specify a callback function after adding an event. | Array, Callback Function (optional) | `$.timeline('addEvent', [ {start:'2017-6-1 00:00',end:'2017-6-1 02:00',row:2,label:'Add Event',content:'test'} ], function( self, data ){ alert('Added Event!'); });` |
| removeEvent | Removes the specified event from the timeline. To specify the event, use the event ID. | Array, Callback Function (optional) | `$.timeline('removeEvent', [ 6, 7 ]);`
| updateEvent | Updates the contents of the specified event. It is possible to update multiple events simultaneously. | Array, Callback Function (optional) | `$.timeline('updateEvent', [ {eventId:3, start:'2017-5-29 13:00',end:'2017-5-29 15:30', row:1, label:'Updated Event', bgColor:'#aaaab0', color:'#d70035'} ]);` |
| openEvent | Called back when an event is clicked. | (object) | Unlike other methods, the processing specified by the event parameter callback ([to be described later](#Event Parameters)) is invoked. |
## Handling Events (since v1.0.5)
Since version 1.0.5, the custom events are triggered according to the state of timeline objects. This allows you to bind your own processing to a custom event.
```javascript
$("#myTimeline").on("afterRender.timeline", function( e, options ) {
// Do something
});
```
| Event | Description | Arguments |
|--------|:------------|-----------|
| afterRender.timeline | Fired after rendering completely a timeline object. | options (object) |
## Event Parameters
Events placed on the timeline have parameters for display. You can specify this event parameter either by directly marking up in HTML or by using a method.
### Directly markup on HTML
```html
<div class="timeline-events">
<div>This event is ignored because it is an invalid event</div>
<div data-timeline-node="{ start:'2017-01-01 00:00',end:'2017-01-01 13:00',content:'Fill in the text of the event.' }">This is a valid event</div>
<div data-timeline-node="{ start:'2017-01-01 00:00',end:'2017-01-01 13:00',content:'Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...' }">Lorem Ipsum</div>
</div>
```
> **Note**: Event lists that do not have a "data-timeline-node" attribute are ignored when initializing timeline objects and are not placed on the timeline.
### Using methods
```javascript
$('myTimeline').timeline()
.timeline( 'addEvent', [
{start:'2017-1-1 08:00',end:'2017-1-1 10:00',label:'Event 1',content:'Event body'},
{start:'2017-1-1 09:30',end:'2017-1-1 10:15',label:'Event 2',content:'Event body'}
],
function( self, data ){
console.log('Events addition successfully!');
});
```
### Event Parameter List
| Parameter | Type | Timeline Type | Description |
|------------|---------|-----------------|-----|
| eventId | Integer | bar/point | It must be an integer value equal to or greater than 1 and it must be a unique value in the event list. It can be omitted as a parameter, and if omitted, it will be automatically issued at plugin initialization. |
| start | String | bar/point | Event start datetime. Specify it like `2017-1-1 0:00` or `2017/01/02 01:23` |
| end | String | bar | Event end datetime. Ignored if the timeline type is point. The usable format is the same as start. |
| row | Integer | bar/point | The display line (vertical position) on the timeline. An integer value of 1 or more. If not specified, this will be the first line (top row) event. |