1 --- eventum-2.2/htdocs/ajax/order.php 1970-01-01 02:00:00.000000000 +0200
2 +++ eventum-2.2-order/htdocs/ajax/order.php 2009-10-12 22:10:36.429185594 +0300
5 +require_once(dirname(__FILE__) . '/../init.php');
6 +require_once(APP_INC_PATH . "class.auth.php");
7 +require_once(APP_INC_PATH . "class.issue.php");
10 +if (!Auth::hasValidCookie(APP_COOKIE)) {
16 +if (!isset($_POST['before']) || !isset($_POST['after'])) {
20 +parse_str($_POST['before'], $before);
21 +parse_str($_POST['after'], $after);
23 +$before = $before['issue_list_table'];
24 +$after = $after['issue_list_table'];
27 +$before = array_slice($before, 2, count($before)-3);
28 +$after = array_slice($after, 2, count($after)-3);
30 +if (count($before) != count($after) or count($before) < 1) {
34 +$usr_id = Auth::getUserID();
36 +$order = Issue::getIssueOrderByUser($usr_id);
38 +if (!count($order)) {
39 + // no prev order list
43 +$after_filterd = array();
44 +$before_filterd = array();
46 +// remove issues that are not assigned to me
47 +foreach ($after as $id) {
48 + if (isset($order[$id])) {
49 + $after_filterd[] = $id;
52 +foreach ($before as $id) {
53 + if (isset($order[$id])) {
54 + $before_filterd[] = $id;
58 +foreach ($after_filterd as $key => $nID) {
59 + if ($nID != $before_filterd[$key]) {
62 + " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_user
64 + isu_order = " . $order[$before_filterd[$key]] . "
66 + isu_iss_id = $nID AND
67 + isu_usr_id = $usr_id";
68 + $res = DB_Helper::getInstance()->query($stmt);
69 + if (PEAR::isError($res)) {
70 + Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__);
71 + die('update failed');
76 --- eventum-2.2/htdocs/css/style.css~ 2009-10-12 22:27:13.000000000 +0300
77 +++ eventum-2.2/htdocs/css/style.css 2009-10-12 22:28:31.402712101 +0300
84 + background-color: #ffffdd;
87 + border: 1px solid red;
97 + background-image: url(../images/updown2.gif);
98 + background-repeat: no-repeat;
99 + background-position: center center;
101 --- eventum-2.2/htdocs/js/jquery/jquery.tablednd.js 1970-01-01 02:00:00.000000000 +0200
102 +++ eventum-2.2-order/htdocs/js/jquery/jquery.tablednd.js 2009-10-12 22:10:36.435851675 +0300
105 + * TableDnD plug-in for JQuery, allows you to drag and drop table rows
106 + * You can set up various options to control how the system will work
107 + * Copyright (c) Denis Howlett <denish@isocra.com>
108 + * Licensed like jQuery, see http://docs.jquery.com/License.
110 + * Configuration options:
113 + * This is the style that is assigned to the row during drag. There are limitations to the styles that can be
114 + * associated with a row (such as you can't assign a border--well you can, but it won't be
115 + * displayed). (So instead consider using onDragClass.) The CSS style to apply is specified as
116 + * a map (as used in the jQuery css(...) function).
118 + * This is the style that is assigned to the row when it is dropped. As for onDragStyle, there are limitations
119 + * to what you can do. Also this replaces the original style, so again consider using onDragClass which
120 + * is simply added and then removed on drop.
122 + * This class is added for the duration of the drag and then removed when the row is dropped. It is more
123 + * flexible than using onDragStyle since it can be inherited by the row cells and other content. The default
124 + * is class is tDnD_whileDrag. So to use the default, simply customise this CSS class in your
127 + * Pass a function that will be called when the row is dropped. The function takes 2 parameters: the table
128 + * and the row that was dropped. You can work out the new order of the rows by using
131 + * Pass a function that will be called when the user starts dragging. The function takes 2 parameters: the
132 + * table and the row which the user has started to drag.
134 + * Pass a function that will be called as a row is over another row. If the function returns true, allow
135 + * dropping on that row, otherwise not. The function takes 2 parameters: the dragged row and the row under
136 + * the cursor. It returns a boolean: true allows the drop, false doesn't allow it.
138 + * This is the number of pixels to scroll if the user moves the mouse cursor to the top or bottom of the
139 + * window. The page should automatically scroll up or down as appropriate (tested in IE6, IE7, Safari, FF2,
142 + * This is the name of a class that you assign to one or more cells in each row that is draggable. If you
143 + * specify this class, then you are responsible for setting cursor: move in the CSS and only these cells
144 + * will have the drag behaviour. If you do not specify a dragHandle, then you get the old behaviour where
145 + * the whole row is draggable.
147 + * Other ways to control behaviour:
149 + * Add class="nodrop" to any rows for which you don't want to allow dropping, and class="nodrag" to any rows
150 + * that you don't want to be draggable.
152 + * Inside the onDrop method you can also call $.tableDnD.serialize() this returns a string of the form
153 + * <tableID>[]=<rowID1>&<tableID>[]=<rowID2> so that you can send this back to the server. The table must have
154 + * an ID as must all the rows.
158 + * $("...").tableDnDUpdate()
159 + * Will update all the matching tables, that is it will reapply the mousedown method to the rows (or handle cells).
160 + * This is useful if you have updated the table rows using Ajax and you want to make the table draggable again.
161 + * The table maintains the original configuration (so you don't have to specify it again).
163 + * $("...").tableDnDSerialize()
164 + * Will serialize and return the serialized string as above, but for each of the matching tables--so it can be
165 + * called from anywhere and isn't dependent on the currentTable being set up correctly before calling
168 + * - Auto-scoll has some problems with IE7 (it scrolls even when it shouldn't), work-around: set scrollAmount to 0
170 + * Version 0.2: 2008-02-20 First public version
171 + * Version 0.3: 2008-02-07 Added onDragStart option
172 + * Made the scroll amount configurable (default is 5 as before)
173 + * Version 0.4: 2008-03-15 Changed the noDrag/noDrop attributes to nodrag/nodrop classes
174 + * Added onAllowDrop to control dropping
175 + * Fixed a bug which meant that you couldn't set the scroll amount in both directions
176 + * Added serialize method
177 + * Version 0.5: 2008-05-16 Changed so that if you specify a dragHandle class it doesn't make the whole row
179 + * Improved the serialize method to use a default (and settable) regular expression.
180 + * Added tableDnDupate() and tableDnDSerialize() to be called when you are outside the table
183 + /** Keep hold of the current table being dragged */
184 + currentTable : null,
185 + /** Keep hold of the current drag object if any */
187 + /** The current mouse offset */
189 + /** Remember the old value of Y so that we don't do too much processing */
192 + /** Actually build the structure */
193 + build: function(options) {
194 + // Set up the defaults if any
196 + this.each(function() {
197 + // This is bound to each matching table, set up the defaults and override with user options
198 + this.tableDnDConfig = jQuery.extend({
201 + // Add in the default class for whileDragging
202 + onDragClass: "tDnD_whileDrag",
206 + serializeRegexp: /[^\-]*$/, // The regular expression to use to trim row IDs
207 + serializeParamName: null, // If you want to specify another parameter name instead of the table ID
208 + dragHandle: null // If you give the name of a class here, then only Cells with this class will be draggable
210 + // Now make the rows draggable
211 + jQuery.tableDnD.makeDraggable(this);
214 + // Now we need to capture the mouse up and mouse move event
215 + // We can use bind so that we don't interfere with other event handlers
217 + .bind('mousemove', jQuery.tableDnD.mousemove)
218 + .bind('mouseup', jQuery.tableDnD.mouseup);
220 + // Don't break the chain
224 + /** This function makes all the rows on the table draggable apart from those marked as "NoDrag" */
225 + makeDraggable: function(table) {
226 + var config = table.tableDnDConfig;
227 + if (table.tableDnDConfig.dragHandle) {
228 + // We only need to add the event to the specified cells
229 + var cells = jQuery("td."+table.tableDnDConfig.dragHandle, table);
230 + cells.each(function() {
231 + // The cell is bound to "this"
232 + jQuery(this).mousedown(function(ev) {
233 + jQuery.tableDnD.dragObject = this.parentNode;
234 + jQuery.tableDnD.currentTable = table;
235 + jQuery.tableDnD.mouseOffset = jQuery.tableDnD.getMouseOffset(this, ev);
236 + if (config.onDragStart) {
237 + // Call the onDrop method if there is one
238 + config.onDragStart(table, this);
244 + // For backwards compatibility, we add the event to the whole row
245 + var rows = jQuery("tr", table); // get all the rows as a wrapped set
246 + rows.each(function() {
247 + // Iterate through each row, the row is bound to "this"
248 + var row = jQuery(this);
249 + if (! row.hasClass("nodrag")) {
250 + row.mousedown(function(ev) {
251 + if (ev.target.tagName == "TD") {
252 + jQuery.tableDnD.dragObject = this;
253 + jQuery.tableDnD.currentTable = table;
254 + jQuery.tableDnD.mouseOffset = jQuery.tableDnD.getMouseOffset(this, ev);
255 + if (config.onDragStart) {
256 + // Call the onDrop method if there is one
257 + config.onDragStart(table, this);
261 + }).css("cursor", "move"); // Store the tableDnD object
267 + updateTables: function() {
268 + this.each(function() {
269 + // this is now bound to each matching table
270 + if (this.tableDnDConfig) {
271 + jQuery.tableDnD.makeDraggable(this);
276 + /** Get the mouse coordinates from the event (allowing for browser differences) */
277 + mouseCoords: function(ev){
278 + if(ev.pageX || ev.pageY){
279 + return {x:ev.pageX, y:ev.pageY};
282 + x:ev.clientX + document.body.scrollLeft - document.body.clientLeft,
283 + y:ev.clientY + document.body.scrollTop - document.body.clientTop
287 + /** Given a target element and a mouse event, get the mouse offset from that element.
288 + To do this we need the element's position and the mouse position */
289 + getMouseOffset: function(target, ev) {
290 + ev = ev || window.event;
292 + var docPos = this.getPosition(target);
293 + var mousePos = this.mouseCoords(ev);
294 + return {x:mousePos.x - docPos.x, y:mousePos.y - docPos.y};
297 + /** Get the position of an element by going up the DOM tree and adding up all the offsets */
298 + getPosition: function(e){
301 + /** Safari fix -- thanks to Luis Chato for this! */
302 + if (e.offsetHeight == 0) {
303 + /** Safari 2 doesn't correctly grab the offsetTop of a table row
304 + this is detailed here:
305 + http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari/
306 + the solution is likewise noted there, grab the offset of a table cell in the row - the firstChild.
307 + note that firefox will return a text node as a first child, so designing a more thorough
308 + solution may need to take that into account, for now this seems to work in firefox, safari, ie */
309 + e = e.firstChild; // a table cell
312 + while (e.offsetParent){
313 + left += e.offsetLeft;
314 + top += e.offsetTop;
315 + e = e.offsetParent;
318 + left += e.offsetLeft;
319 + top += e.offsetTop;
321 + return {x:left, y:top};
324 + mousemove: function(ev) {
325 + if (jQuery.tableDnD.dragObject == null) {
329 + var dragObj = jQuery(jQuery.tableDnD.dragObject);
330 + var config = jQuery.tableDnD.currentTable.tableDnDConfig;
331 + var mousePos = jQuery.tableDnD.mouseCoords(ev);
332 + var y = mousePos.y - jQuery.tableDnD.mouseOffset.y;
333 + //auto scroll the window
334 + var yOffset = window.pageYOffset;
335 + if (document.all) {
337 + //yOffset=document.body.scrollTop;
338 + if (typeof document.compatMode != 'undefined' &&
339 + document.compatMode != 'BackCompat') {
340 + yOffset = document.documentElement.scrollTop;
342 + else if (typeof document.body != 'undefined') {
343 + yOffset=document.body.scrollTop;
348 + if (mousePos.y-yOffset < config.scrollAmount) {
349 + window.scrollBy(0, -config.scrollAmount);
351 + var windowHeight = window.innerHeight ? window.innerHeight
352 + : document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight;
353 + if (windowHeight-(mousePos.y-yOffset) < config.scrollAmount) {
354 + window.scrollBy(0, config.scrollAmount);
359 + if (y != jQuery.tableDnD.oldY) {
360 + // work out if we're going up or down...
361 + var movingDown = y > jQuery.tableDnD.oldY;
362 + // update the old value
363 + jQuery.tableDnD.oldY = y;
364 + // update the style to show we're dragging
365 + if (config.onDragClass) {
366 + dragObj.addClass(config.onDragClass);
368 + dragObj.css(config.onDragStyle);
370 + // If we're over a row then move the dragged row to there so that the user sees the
371 + // effect dynamically
372 + var currentRow = jQuery.tableDnD.findDropTargetRow(dragObj, y);
374 + // TODO worry about what happens when there are multiple TBODIES
375 + if (movingDown && jQuery.tableDnD.dragObject != currentRow) {
376 + jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject, currentRow.nextSibling);
377 + } else if (! movingDown && jQuery.tableDnD.dragObject != currentRow) {
378 + jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject, currentRow);
386 + /** We're only worried about the y position really, because we can only move rows up and down */
387 + findDropTargetRow: function(draggedRow, y) {
388 + var rows = jQuery.tableDnD.currentTable.rows;
389 + for (var i=0; i<rows.length; i++) {
391 + var rowY = this.getPosition(row).y;
392 + var rowHeight = parseInt(row.offsetHeight)/2;
393 + if (row.offsetHeight == 0) {
394 + rowY = this.getPosition(row.firstChild).y;
395 + rowHeight = parseInt(row.firstChild.offsetHeight)/2;
397 + // Because we always have to insert before, we need to offset the height a bit
398 + if ((y > rowY - rowHeight) && (y < (rowY + rowHeight))) {
399 + // that's the row we're over
400 + // If it's the same as the current row, ignore it
401 + if (row == draggedRow) {return null;}
402 + var config = jQuery.tableDnD.currentTable.tableDnDConfig;
403 + if (config.onAllowDrop) {
404 + if (config.onAllowDrop(draggedRow, row)) {
410 + // If a row has nodrop class, then don't allow dropping (inspired by John Tarr and Famic)
411 + var nodrop = jQuery(row).hasClass("nodrop");
424 + mouseup: function(e) {
425 + if (jQuery.tableDnD.currentTable && jQuery.tableDnD.dragObject) {
426 + var droppedRow = jQuery.tableDnD.dragObject;
427 + var config = jQuery.tableDnD.currentTable.tableDnDConfig;
428 + // If we have a dragObject, then we need to release it,
429 + // The row will already have been moved to the right place so we just reset stuff
430 + if (config.onDragClass) {
431 + jQuery(droppedRow).removeClass(config.onDragClass);
433 + jQuery(droppedRow).css(config.onDropStyle);
435 + jQuery.tableDnD.dragObject = null;
436 + if (config.onDrop) {
437 + // Call the onDrop method if there is one
438 + config.onDrop(jQuery.tableDnD.currentTable, droppedRow);
440 + jQuery.tableDnD.currentTable = null; // let go of the table too
444 + serialize: function() {
445 + if (jQuery.tableDnD.currentTable) {
446 + return jQuery.tableDnD.serializeTable(jQuery.tableDnD.currentTable);
448 + return "Error: No Table id set, you need to set an id on your table and every row";
452 + serializeTable: function(table) {
454 + var tableId = table.id;
455 + var rows = table.rows;
456 + for (var i=0; i<rows.length; i++) {
457 + if (result.length > 0) result += "&";
458 + var rowId = rows[i].id;
459 + if (rowId && rowId && table.tableDnDConfig && table.tableDnDConfig.serializeRegexp) {
460 + rowId = rowId.match(table.tableDnDConfig.serializeRegexp)[0];
463 + result += tableId + '[]=' + rowId;
468 + serializeTables: function() {
470 + this.each(function() {
471 + // this is now bound to each matching table
472 + result += jQuery.tableDnD.serializeTable(this);
481 + tableDnD : jQuery.tableDnD.build,
482 + tableDnDUpdate : jQuery.tableDnD.updateTables,
483 + tableDnDSerialize: jQuery.tableDnD.serializeTables
486 \ No newline at end of file
487 --- eventum-2.2/htdocs/list.php 2009-09-14 18:07:55.000000000 +0300
488 +++ eventum-2.2-order/htdocs/list.php 2009-10-12 22:10:36.435851675 +0300
490 $profile['sort_by'] . "&sort_order=" . $profile['sort_order']);
493 +@$reorder_usr_id = $_REQUEST["reorder_user"];
494 +@$reorder_issue_id = $_REQUEST["reorder_source"];
495 +@$reorder_neworder = $_REQUEST["reorder_neworder"];
496 +Issue::reorderUserIssues($reorder_usr_id, $reorder_issue_id, $reorder_neworder);
498 $options = Issue::saveSearchParams();
499 $tpl->assign("options", $options);
500 $tpl->assign("sorting", Issue::getSortingInfo($options));
503 $assign_options += $users;
505 +// get the isu_order (assignated users) ordering user
506 +if (!empty($options["users"])) {
507 + if ($options["users"] == -2) {
508 + $isu_order_user = $usr_id;
510 + if ($options["users"] > 0) {
511 + $isu_order_user = $options["users"];
513 + unset($isu_order_user);
516 + unset($isu_order_user);
518 +$tpl->assign("isu_order_user", $isu_order_user);
520 $list = Issue::getListing($prj_id, $options, $pagerRow, $rows);
521 $tpl->assign("list", $list["list"]);
522 $tpl->assign("list_info", $list["info"]);
523 --- eventum-2.2/lib/eventum/class.display_column.php 2009-09-14 18:07:55.000000000 +0300
524 +++ eventum-2.2-order/lib/eventum/class.display_column.php 2009-10-12 22:10:36.429185594 +0300
527 "iss_expected_resolution_date" => array(
528 "title" => ev_gettext("Expected Resolution Date")
531 + "isu_order" => array(
532 + "title" => ev_gettext("Order")
536 return $columns[$page];
537 --- eventum-2.2/lib/eventum/class.issue.php 2009-09-14 18:07:55.000000000 +0300
538 +++ eventum-2.2-order/lib/eventum/class.issue.php 2009-10-12 22:10:36.445851670 +0300
539 @@ -1333,6 +1333,7 @@
540 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__);
543 + self::moveOrderForAllUsers($issue_id, 1000);
544 $prj_id = self::getProjectID($issue_id);
547 @@ -1636,6 +1637,180 @@
552 + * Method used to update the a single detail field of a specific issue.
554 + * @param integer $issue_id
555 + * @param string $field_name
556 + * @param string $field_value
557 + * @param string $field_type string or integer (for escape)
558 + * @return integer 1 on success, -1 otherwise
560 + function updateField($issue_id, $field_name, $filed_value) {
562 + $issue_id = Misc::escapeInteger($issue_id);
564 + $usr_id = Auth::getUserID();
565 + $prj_id = self::getProjectID($issue_id);
567 + // get all of the 'current' information of this issue
568 + $current = self::getDetails($issue_id);
571 + " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue
573 + iss_updated_date='" . Date_Helper::getCurrentDateGMT() . "',
574 + iss_last_public_action_date='" . Date_Helper::getCurrentDateGMT() . "',
575 + iss_last_public_action_type='updated'";
577 + switch ($field_name) {
579 + $stmt .= ", iss_prc_id = " . Misc::escapeInteger($filed_value);
582 + $stmt .= ", iss_pre_id = " . Misc::escapeInteger($filed_value);
584 + case 'expected_resolution_date':
585 + if (is_null($filed_value)) {
586 + $stmt .= ", iss_expected_resolution_date = null";
588 + $stmt .= ", iss_expected_resolution_date = '" . Misc::escapeString($filed_value) . "'";
592 + $stmt .= ", iss_pre_id = " . Misc::escapeInteger($filed_value);
595 + $stmt .= ", iss_pri_id = " . Misc::escapeInteger($filed_value);
598 + $stmt .= ", iss_sta_id = " . Misc::escapeInteger($filed_value);
601 + $stmt .= ", iss_res_id = " . Misc::escapeInteger($filed_value);
604 + $stmt .= ", iss_summary = '" . Misc::escapeString($filed_value) . "'";
606 + case 'description':
607 + $stmt .= ", iss_description = '" . Misc::escapeString($filed_value) . "'";
609 + case 'estimated_dev_time':
610 + $stmt .= ", iss_dev_time = '" . Misc::escapeString($filed_value) . "'";
612 + case 'percent_complete':
613 + $stmt .= ", iss_percent_complete = '" . Misc::escapeString($filed_value) . "'";
615 + case 'trigger_reminders':
616 + $stmt .= ", iss_trigger_reminders = " . Misc::escapeInteger($filed_value);
619 + $stmt .= ", iss_grp_id = " . Misc::escapeInteger($filed_value);
622 + $stmt .= ", iss_private = " . Misc::escapeInteger($filed_value);
625 + Error_Handler::logError("Unknown field name $field_name", __FILE__, __LINE__);
634 + $res = DB_Helper::getInstance()->query($stmt);
635 + if (PEAR::isError($res)) {
636 + Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__);
640 + 'category' => $current['iss_prc_id'],
641 + 'release' => $current['iss_pre_id'],
642 + 'expected_resolution_date' => $current['iss_expected_resolution_date'],
643 + 'release' => $current['iss_pre_id'],
644 + 'priority' => $current['iss_pri_id'],
645 + 'status' => $current['iss_sta_id'],
646 + 'resolution' => $current['iss_res_id'],
647 + 'summary' => $current['iss_summary'],
648 + 'description' => $current['iss_description'],
649 + 'estimated_dev_time' => $current['iss_dev_time'],
650 + 'percent_complete' => $current['iss_percent_complete'],
651 + 'trigger_reminders' => $current['iss_trigger_reminders'],
652 + 'group' => $current['iss_grp_id'],
653 + 'iss_private' => $current['private']
655 + $new[$field_name] = $filed_value;
657 + // add change to the history (only for changes on specific fields?)
658 + $updated_fields = array();
659 + if ($field_name == 'expected_resolution_date' && $current["iss_expected_resolution_date"] != $filed_value) {
660 + $updated_fields["Expected Resolution Date"] = History::formatChanges($current["iss_expected_resolution_date"], $filed_value);
662 + if ($field_name == 'category' && $current["iss_prc_id"] != $filed_value) {
663 + $updated_fields["Category"] = History::formatChanges(Category::getTitle($current["iss_prc_id"]), Category::getTitle($filed_value));
665 + if ($field_name == 'release' && $current["iss_pre_id"] != $filed_value) {
666 + $updated_fields["Release"] = History::formatChanges(Release::getTitle($current["iss_pre_id"]), Release::getTitle($filed_value));
668 + if ($field_name == 'priority' && $current["iss_pri_id"] != $filed_value) {
669 + $updated_fields["Priority"] = History::formatChanges(Priority::getTitle($current["iss_pri_id"]), Priority::getTitle($filed_value));
670 + Workflow::handlePriorityChange($prj_id, $issue_id, $usr_id, $current, $new);
672 + if ($field_name == 'status' && $current["iss_sta_id"] != $filed_value) {
673 + // clear out the last-triggered-reminder flag when changing the status of an issue
674 + Reminder_Action::clearLastTriggered($issue_id);
676 + // if old status was closed and new status is not, clear closed data from issue.
677 + $old_status_details = Status::getDetails($current['iss_sta_id']);
678 + if ($old_status_details['sta_is_closed'] == 1) {
679 + $new_status_details = Status::getDetails($filed_value);
680 + if ($new_status_details['sta_is_closed'] != 1) {
681 + self::clearClosed($issue_id);
684 + $updated_fields["Status"] = History::formatChanges(Status::getStatusTitle($current["iss_sta_id"]), Status::getStatusTitle($filed_value));
686 + if ($field_name == 'resolution' && $current["iss_res_id"] != $filed_value) {
687 + $updated_fields["Resolution"] = History::formatChanges(Resolution::getTitle($current["iss_res_id"]), Resolution::getTitle($filed_value));
689 + if ($field_name == 'estimated_dev_time' && $current["iss_dev_time"] != $filed_value) {
690 + $updated_fields["Estimated Dev. Time"] = History::formatChanges(Misc::getFormattedTime(($current["iss_dev_time"]*60)), Misc::getFormattedTime(($filed_value*60)));
692 + if ($field_name == 'summary' && $current["iss_summary"] != $filed_value) {
693 + $updated_fields["Summary"] = '';
695 + if ($field_name == 'description' && $current["iss_description"] != $filed_value) {
696 + $updated_fields["Description"] = '';
698 + if ($field_name == 'private' && ($filed_value != $current['iss_private'])) {
699 + $updated_fields["Private"] = History::formatChanges(Misc::getBooleanDisplayValue($current['iss_private']), Misc::getBooleanDisplayValue($filed_value));
701 + if (count($updated_fields) > 0) {
705 + foreach ($updated_fields as $key => $value) {
709 + if (($key != "Summary") && ($key != "Description")) {
710 + $changes .= "$key: $value";
712 + $changes .= "$key";
717 + History::add($issue_id, $usr_id, History::getTypeID('issue_updated'), "Issue updated ($changes) by " . User::getFullName($usr_id));
718 + // send notifications for the issue being updated
719 + Notification::notifyIssueUpdated($issue_id, $current, $new);
727 * Move the issue to a new project
728 @@ -1800,16 +1975,33 @@
730 $issue_id = Misc::escapeInteger($issue_id);
731 $assignee_usr_id = Misc::escapeInteger($assignee_usr_id);
733 + // move all orders down to free "order space" for this new association
735 + " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_user
737 + isu_order = isu_order + 1
739 + isu_usr_id = $assignee_usr_id AND
740 + isu_order >= $order";
741 + $res = DB_Helper::getInstance()->query($stmt);
742 + if (PEAR::isError($res)) {
743 + Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__);
746 + // insert the new association
748 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_user
758 - '" . Date_Helper::getCurrentDateGMT() . "'
759 + '" . Date_Helper::getCurrentDateGMT() . "',
762 $res = DB_Helper::getInstance()->query($stmt);
763 if (PEAR::isError($res)) {
764 @@ -1824,6 +2016,78 @@
769 + * Method used to get the order list to be rearranged
772 + * @param string $issue_id The issue ID or a comma seperated list of IDs already prepared for giving to mysql
773 + * @param string $usr_id The user to remove. When not specified, all users are taken as to be removed for that issue
774 + * @return mixed delete order list to be rearranged. Used as a parameter to the method of rearranging the order.
776 + function getDeleteUserAssociationOrderList($issue_id, $usr_id = "")
778 + // find all affected associantion orders
779 + $stmt = "SELECT isu_usr_id, isu_order FROM
780 + " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_user
782 + isu_iss_id IN ($issue_id)";
783 + if (!empty($usr_id)) {
784 + $stmt.= " AND isu_usr_id IN ($usr_id)";
786 + $stmt.= "ORDER BY isu_order";
787 + $res = DB_Helper::getInstance()->getAll($stmt, DB_FETCHMODE_ASSOC);
788 + if (PEAR::isError($res)) {
789 + Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__);
792 + $deleted_orders = array();
793 + foreach ($res as $row) {
794 + if (empty($deleted_orders[$row['isu_usr_id']])) {
795 + $deleted_orders[$row['isu_usr_id']] = array();
797 + $deleted_orders[$row['isu_usr_id']] [] = $row['isu_order'];
799 + return $deleted_orders;
805 + * Method used to rearrange order list in the db according to known deleted records
808 + * @param mixed deleteorder list
811 + function rearrangeDeleteUserAssociationOrderList($delete_order_list)
813 + if (empty($delete_order_list) || (!is_array($delete_order_list))) {
816 + foreach ($delete_order_list as $isu_usr_id => $orders) {
817 + for ($i = 0; $i < count($orders); $i++) { // traverse all deleted orders
818 + // move the orders after them up to take the "order space" of the deleted records
820 + " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_user
822 + isu_order = isu_order - " . ($i+1) . "
824 + isu_usr_id = $isu_usr_id AND
825 + isu_order > " . $orders[$i];
826 + if ($i < count($orders) - 1) {
828 + isu_order < " . $orders[$i+1];
830 + $res = DB_Helper::getInstance()->query($stmt);
831 + if (PEAR::isError($res)) {
832 + Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__);
842 * Method used to delete all user assignments for a specific issue.
843 @@ -1839,6 +2103,7 @@
844 if (is_array($issue_id)) {
845 $issue_id = implode(", ", $issue_id);
847 + $deleted_order_list = self::getDeleteUserAssociationOrderList($issue_id);
849 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_user
851 @@ -1869,6 +2134,7 @@
853 $issue_id = Misc::escapeInteger($issue_id);
854 $usr_id = Misc::escapeInteger($usr_id);
855 + $delete_order_list = self::getDeleteUserAssociationOrderList($issue_id, $usr_id);
857 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_user
859 @@ -1883,6 +2149,7 @@
860 History::add($issue_id, Auth::getUserID(), History::getTypeID('user_unassociated'),
861 User::getFullName($usr_id) . ' removed from issue by ' . User::getFullName(Auth::getUserID()));
863 + self::rearrangeDeleteUserAssociationOrderList($delete_order_list);
867 @@ -2342,6 +2609,11 @@
869 $sort_by = self::getParam('sort_by');
870 $sort_order = self::getParam('sort_order');
871 + $users = self::getParam('users');
872 + if (empty($users) && ($sort_by == 'isu_order')) { // Sorting by isu_order is impossible when no user specified
874 + unset($sort_order);
876 $rows = self::getParam('rows');
877 $hide_closed = self::getParam('hide_closed');
878 if ($hide_closed === '') {
879 @@ -2448,6 +2720,7 @@
880 "iss_expected_resolution_date" => "desc",
881 "pre_title" => "asc",
883 + "isu_order" => "desc",
886 foreach ($custom_fields as $fld_id => $fld_name) {
887 @@ -3253,6 +3526,8 @@
888 $ids = implode(", ", $ids);
895 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_user,
896 @@ -3264,6 +3539,7 @@
897 if (PEAR::isError($res)) {
898 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__);
900 + // gather names of the users assigned to each issue
902 for ($i = 0; $i < count($res); $i++) {
903 if (!empty($t[$res[$i]['isu_iss_id']])) {
904 @@ -3272,9 +3548,18 @@
905 $t[$res[$i]['isu_iss_id']] = $res[$i]['usr_full_name'];
910 + for ($i = 0; $i < count($res); $i++) {
911 + if (empty($o[$res[$i]['isu_iss_id']])) {
912 + $o[$res[$i]['isu_iss_id']] = array();
914 + $o[$res[$i]['isu_iss_id']][$res[$i]['isu_usr_id']] = $res[$i]['isu_order'];
916 // now populate the $result variable again
917 for ($i = 0; $i < count($result); $i++) {
918 @$result[$i]['assigned_users'] = $t[$result[$i]['iss_id']];
919 + @$result[$i]['assigned_users_order'] = $o[$result[$i]['iss_id']];
923 @@ -4247,6 +4532,7 @@
924 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__);
927 + self::moveOrderForAllUsers($issue_id, 1);
931 @@ -4346,4 +4632,120 @@
932 History::add($issue_id, Auth::getUserID(), History::getTypeID('user_associated'),
933 "Issue assignment to changed (" . History::formatChanges(join(', ', $old_assignee_names), join(', ', $assignee_names)) . ") by " . User::getFullName(Auth::getUserID()));
937 + * Reorders user's issues as requested by user
939 + * @param $usr_id User to be reordered
940 + * @param $issue_id Issue or array of issues to be moved
941 + * @param $neworder The new order of the issues
944 + function reorderUserIssues($usr_id, $issue_id, $neworder)
946 + if (!isset($usr_id) || !isset($issue_id) || !isset($neworder)) {
949 + if (!is_numeric($usr_id) || !is_numeric($neworder)) {
952 + $usr_id = Misc::escapeInteger($usr_id);
953 + $issue_id = Misc::escapeInteger($issue_id);
954 + $neworder = Misc::escapeInteger($neworder);
955 + if (is_array($issue_id)) {
956 + $issue_count = count($issue_id);
957 + $issue_id_str = implode(", ", $issue_id);
960 + $issue_id_str = $issue_id;
961 + $issue_id = array($issue_id);
963 + // do a nasty pretending to be deleting stuff so that reordering happens as if these elements were deleted
964 + $orderlist = self::getDeleteUserAssociationOrderList($issue_id_str, $usr_id);
965 + self::rearrangeDeleteUserAssociationOrderList($orderlist);
966 + // move down the orders to free the "order space" needed
968 + " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_user
970 + isu_order = isu_order + $issue_count
972 + isu_usr_id = $usr_id AND
973 + isu_order >= $neworder";
974 + $res = DB_Helper::getInstance()->query($stmt);
975 + if (PEAR::isError($res)) {
976 + Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__);
979 + //update the order for the issues being moved
981 + foreach ($issue_id as $iss_id) {
983 + " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_user
985 + isu_order = " . ($neworder + $i) . "
987 + isu_usr_id = $usr_id AND
988 + isu_iss_id = $iss_id";
989 + $res = DB_Helper::getInstance()->query($stmt);
990 + if (PEAR::isError($res)) {
991 + Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__);
1000 + * Get users issue order list
1002 + * @param $user_id User
1003 + * @param $order_list Order of the issues
1006 + function getIssueOrderByUser($usr_id) {
1008 + if (!is_numeric($usr_id)) {
1013 + isu_iss_id, isu_order
1015 + " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_user
1017 + isu_usr_id = " . $usr_id ;
1019 + $order_list = array();
1021 + $res = DB_Helper::getInstance()->getAll($stmt, DB_FETCHMODE_ASSOC);
1023 + if (PEAR::isError($res)) {
1024 + Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__);
1027 + foreach ($res as $row) {
1028 + $order_list[$row["isu_iss_id"]] = $row["isu_order"];
1031 + return $order_list;
1034 + function moveOrderForAllUsers($issue_id, $neworder)
1036 + // Move the issue to the top priority for the ppl it's assigned to
1037 + $stmt = "SELECT isu_usr_id FROM
1038 + " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_user
1040 + isu_iss_id = " . Misc::escapeInteger($issue_id);
1041 + $res = DB_Helper::getInstance()->getAll($stmt, DB_FETCHMODE_ASSOC);
1042 + if (PEAR::isError($res)) {
1043 + Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__);
1046 + foreach ($res as $row) {
1047 + self::reorderUserIssues($row["isu_usr_id"], $issue_id, $neworder);
1052 --- eventum-2.2/templates/header.tpl.html 2009-09-14 18:07:55.000000000 +0300
1053 +++ eventum-2.2-order/templates/header.tpl.html 2009-10-12 22:10:36.435851675 +0300
1055 <script type="text/javascript" src="{$rel_url}js/jquery/form.js?c=9984"></script>
1056 <script type="text/javascript" src="{$rel_url}js/jquery/blockui.js?c=eb13"></script>
1057 <script type="text/javascript" src="{$rel_url}js/jquery/ui.datepicker.js?c=a911"></script>
1058 +<script type="text/javascript" src="{$rel_url}js/jquery/jquery.tablednd.js"></script>
1059 <link rel="stylesheet" href="{$rel_url}js/jquery/ui.datepicker.css?c=5096">
1060 <script type="text/javascript">
1062 --- eventum-2.2/templates/list.tpl.html 2009-09-14 18:07:55.000000000 +0300
1063 +++ eventum-2.2-order/templates/list.tpl.html 2009-10-12 22:10:36.439185157 +0300
1065 f.target = '_popup';
1068 +function reorderBulk(order_user, neworder)
1070 + url = page_url + "?";
1071 + url += "reorder_user=" + order_user;
1073 + items = document.getElementsByName("item[]");
1075 + for (var i = 0; i < items.length; i++) {
1076 + if (items[i].checked) {
1077 + url += "&reorder_source[" + checkedcount + "]=" + items[i].value;
1081 + if (checkedcount == 0) {
1082 + alert('{/literal}{t escape=js}Please choose which issues to move to the new place.{/t}{literal}');
1086 + url += "&reorder_neworder=" + neworder;
1088 + window.location.href = url;
1090 function hideClosed(f)
1092 if (f.hide_closed.checked) {
1093 @@ -153,6 +175,13 @@
1094 f.go.disabled = true;
1097 +function updateCustomFields(issue_id)
1099 + var features = 'width=560,height=460,top=30,left=30,resizable=yes,scrollbars=yes,toolbar=no,location=no,menubar=no,status=no';
1100 + var customWin = window.open('custom_fields.php?issue_id=' + issue_id, '_custom_fields', features);
1101 + customWin.focus();
1107 @@ -169,11 +198,11 @@
1108 <input type="hidden" name="cat" value="bulk_update">
1111 - <table bgcolor="#FFFFFF" width="100%" cellspacing="1" cellpadding="2" border="0">
1113 + <table bgcolor="#FFFFFF" width="100%" cellspacing="1" cellpadding="2" border="0" id="issue_list_table">
1114 + <tr class="nodrag">
1115 <td colspan="{$col_count}" class="default">
1116 <table width="100%" cellspacing="0" cellpadding="0" border="0">
1118 + <tr class="nodrag">
1119 <td class="default">
1120 <b>{t}Search Results{/t} ({$list_info.total_rows} {t}issues found{/t}{if $list_info.end_offset > 0}, {math equation="x + 1" x=$list_info.start_offset} - {$list_info.end_offset} {t}shown{/t}{/if})</b>
1121 {include file="help_link.tpl.html" topic="list"}
1126 - <tr bgcolor="{$cell_color}">
1127 + <tr bgcolor="{$cell_color}" class="nodrag">
1128 {if $current_role > $roles.developer}
1130 <input type="button" value="{t}All{/t}" class="shortcut" onClick="javascript:toggleSelectAll(this.form, 'item[]');toggleBulkUpdate();">
1132 {if $sorting.images[$fld_name_id] != ""}<a title="{t}sort by{/t} {$fld_title|escape:"html"}" href="{$sorting.links[$fld_name_id]}" class="white_link"><img border="0" src="{$sorting.images[$fld_name_id]}"></a>{/if}
1136 + {elseif $field_name != 'isu_order' || $isu_order_user}
1137 <td align="{$column.align|default:'center'}" class="default_white" nowrap {if $column.width != ''}width="{$column.width}"{/if}>
1138 {if $field_name == 'iss_summary'}
1139 <table cellspacing="0" cellpadding="1" width="100%">
1145 {section name="i" loop=$list}
1146 - <tr {if $current_role >= $roles.developer AND $list[i].iqu_status > 0}style="text-decoration: line-through;"{/if}>
1147 + <tr {if $current_role >= $roles.developer AND $list[i].iqu_status > 0}style="text-decoration: line-through;"{/if} id="{$list[i].iss_id}" {if !$list[i].assigned_users_order[$current_user_id]}class="nodrag"{/if}>
1148 {if $current_role > $roles.developer}
1149 <td bgcolor="{$list[i].status_color}" width="1%" class="default" align="center"><input type="checkbox" name="item[]" value="{$list[i].iss_id}" onchange="toggleBulkUpdate();"></td>
1152 {$fld_value|formatCustomValue:$fld_id:$list[i].iss_id}
1156 - <td bgcolor="{$list[i].status_color}" align="{$column.align|default:'center'}" class="default">
1157 + {elseif $field_name != 'isu_order' || $isu_order_user}
1158 + <td bgcolor="{$list[i].status_color}" align="{$column.align|default:'center'}" class="default{if $field_name == 'isu_order'} dragHandle{/if}">
1159 {if $field_name == 'iss_id'}
1160 <a href="view.php?id={$list[i].iss_id}" class="link" title="{t}view issue details{/t}">{$list[i].iss_id}</a>
1161 {elseif $field_name == 'pri_rank'}
1163 {if $list[i].iss_private == 1}
1166 + {elseif $field_name == 'isu_order'}
1167 + {if $options.sort_by == "isu_order" and $current_user_id == $isu_order_user} {/if}
1171 @@ -300,10 +332,11 @@
1175 - <tr bgcolor="{$cell_color}">
1177 + <tr bgcolor="{$cell_color}" class="nodrag">
1178 <td colspan="{$col_count}">
1179 <table width="100%" cellspacing="0" cellpadding="0">
1181 + <tr class="nodrag">
1182 <td width="30%" nowrap>
1183 {if $current_role > $roles.developer}
1184 <input type="button" value="{t}All{/t}" class="shortcut" onClick="javascript:toggleSelectAll(this.form, 'item[]');">
1185 @@ -355,6 +388,35 @@
1190 +<script type="text/javascript">
1192 + * Order issues by drag and drop:
1193 + * only if sorted by order and viewing your own issues
1195 +{if $options.sort_by == "isu_order" and $current_user_id == $isu_order_user}
1197 +var before = ''; // make it global variable
1198 +$('#issue_list_table').tableDnD({
1199 + onDragClass: "tDnD_whileDrag",
1200 + onDragStart: function(table, row) {
1201 + before = $.tableDnD.serialize('id');
1203 + onDrop: function(table, row) {
1204 + $.post("/ajax/order.php", {before: before, after: $.tableDnD.serialize('id')}, function(data) {
1205 + if (data.length > 0) {
1210 + dragHandle: "dragHandle"
1212 +$("#issue_list_table tr").hover(function() {
1213 + $('#' + this.id + ' .dragHandle').addClass('showDragHandle');
1215 + $('#' + this.id + ' .dragHandle').removeClass('showDragHandle');
1220 {include file="app_info.tpl.html"}
1221 {include file="footer.tpl.html"}