This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "FusionForge".

The branch, master has been updated
       via  7baa35da028697b1ab2c29858ab06eccffc5833c (commit)
      from  4017da328b347f82f4dcbe0962cb7cdb2eff1f21 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
https://scm.fusionforge.org/anonscm/gitweb/?p=fusionforge/fusionforge.git;a=commitdiff;h=7baa35da028697b1ab2c29858ab06eccffc5833c

commit 7baa35da028697b1ab2c29858ab06eccffc5833c
Author: Franck Villaume <[email protected]>
Date:   Sun Dec 4 18:53:42 2016 +0100

    docman: document review: start implement

diff --git a/src/common/docman/DocumentReview.class.php 
b/src/common/docman/DocumentReview.class.php
new file mode 100644
index 0000000..58d64d9
--- /dev/null
+++ b/src/common/docman/DocumentReview.class.php
@@ -0,0 +1,524 @@
+<?php
+/**
+ * FusionForge Documentation Manager
+ *
+ * Copyright 2016, Franck Villaume - TrivialDev
+ * http://fusionforge.org
+ *
+ * This file is part of FusionForge. FusionForge is free software;
+ * you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the Licence, or (at your option)
+ * any later version.
+ *
+ * FusionForge 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with FusionForge; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+require_once $gfcommon.'docman/DocumentVersion.class.php';
+require_once $gfcommon.'docman/DocumentVersionFactory.class.php';
+require_once $gfcommon.'docman/include/constants.php';
+
+class DocumentReview extends FFError {
+       /**
+        * The Document object.
+        *
+        * @var object  $Document.
+        */
+       var $Document;
+
+       /**
+        * The data values
+        *
+        * @var array   $data_array
+        */
+       var $data_array = array();
+
+       var $version    = null;
+       var $serialid   = null;
+
+       function __construct(&$Document, $revid = false, $arr = false) {
+               parent::__construct();
+               if (!$Document || !is_object($Document)) {
+                       $this->setError(_('No Valid Document Object'));
+                       return false;
+               }
+               if ($Document->isError()) {
+                       $this->setError(_('Document')._(': 
').$Document->getErrorMessage());
+                       return false;
+               }
+               $this->Document =& $Document;
+               if ($revid) {
+                       $this->fetchData($revid);
+               }
+               return true;
+       }
+
+       function fetchData($revid) {
+               $res = db_query_params('SELECT revid, created_by, 
doc_review.statusid as statusid, startdate, enddate, title, 
doc_review.description, doc_review_status.name as statusname
+                                       FROM doc_review, doc_review_status 
WHERE doc_review.statusid = doc_review_status.statusid AND doc_review.revid = 
$1', array($revid));
+               $this->data_array = db_fetch_array($res);
+               $this->getVersion();
+       }
+
+       function getID() {
+               return $this->data_array['revid'];
+       }
+
+       function getTitle() {
+               return $this->data_array['title'];
+       }
+
+       function getDescription() {
+               return $this->data_array['description'];
+       }
+
+       function getStatusID() {
+               return $this->data_array['statusid'];
+       }
+
+       function getStatusName() {
+               return $this->data_array['statusname'];
+       }
+
+       function getCreatedBy() {
+               return $this->data_array['created_by'];
+       }
+
+       function getEnddate() {
+               return $this->data_array['enddate'];
+       }
+
+       function getUsers($statusid = array(), $type = false) {
+               $qpa = db_construct_qpa(false, 'SELECT * FROM doc_review_users 
WHERE revid = $1', array($this->getID()));
+               if ($statusid && is_array($statusid)) {
+                       $qpa = db_construct_qpa($qpa, ' AND statusid = ANY 
($1)', array(db_int_array_to_any_clause($statusid)));
+               }
+               if ($type) {
+                       $qpa = db_construct_qpa($qpa, ' AND typeid = $1', 
array($type));
+               }
+               $res = db_query_qpa($qpa);
+               $users = array();
+               if ($res && (db_numrows($res) > 0)) {
+                       while ($arr = db_fetch_array($res)) {
+                               $users[] = $arr;
+                       }
+               }
+               return $users;
+       }
+
+       function getMandatoryUsers() {
+               return $this->getUsers(false, 1);
+       }
+
+       function getOptionalUsers() {
+               return $this->getUsers(false, 2);
+       }
+
+       function getStatusIcon() {
+               global $HTML;
+               switch ($this->getStatusID()) {
+                       case 1:
+                               $img = $HTML->getOpenTicketPic(_('Open'), 
_('Open'));
+                               break;
+                       case 2:
+                               $img = $HTML->getClosedTicketPic(_('Closed'), 
_('Closed'));
+                               break;
+                       default:
+                               $img = $HTML->getErrorPic(_('On error'), _('On 
error'));
+                               break;
+               }
+               return $img;
+       }
+
+       function getCreateByRealNameLink() {
+               $user = user_get_object($this->getCreatedBy());
+               if (is_object($user) && !$user->isError()) {
+                       return util_display_user($user->getUnixName(), 
$user->getID(), $user->getRealName());
+               }
+               return _('Unknown user');
+       }
+
+       function getVersion() {
+               if (isset($this->version) && $this->version) {
+                       return $this-version;
+               }
+               $res = db_query_params('SELECT version FROM doc_data_version, 
doc_review_version WHERE doc_data_version.serial_id = 
doc_review_version.serialid AND doc_review_version.revid = $1',
+                                       array($this->getID()));
+               if ($res) {
+                       $this->version = db_result($res, 0, 0);
+                       return $this->version;
+               } else {
+                       return $this->setError(db_error());
+               }
+       }
+
+       function getSerialID() {
+               if (isset($this->serialid) && $this->serialid) {
+                       return $this->serialid;
+               }
+               $res = db_query_params('SELECT serialid FROM doc_review_version 
WHERE revid = $1', array($this->getID()));
+               if ($res) {
+                       $this->serialid = db_result($res, 0, 0);
+                       return $this->serialid;
+               } else {
+                       return $this->setError(db_error());
+               }
+       }
+
+
+       function getDeleteAction() {
+               global $HTML;
+               return util_make_link('#', $HTML->getRemovePic(_('Permanently 
delete this review'), 'delreview'), array('id' => 'review_action_delete', 
'onclick' => 'javascript:controllerListFile.deleteReview({review: 
'.$this->getID().'})'), true);
+       }
+
+       function getEditAction() {
+               global $HTML;
+               $enddate = strftime(_('%Y-%m-%d'), $this->getEnddate());
+               $users = $this->getUsers(array(1, 2));
+               $mandatoryUsers = array();
+               $optionalUsers = array();
+               foreach ($users as $user) {
+                       if ($user['typeid'] == 1) {
+                               $mandatoryUsers[] = $user['userid'];
+                       } else {
+                               $optionalUsers[] = $user['userid'];
+                       }
+               }
+               return util_make_link('#', $HTML->getConfigurePic(_('Edit this 
review'), 'editreview'),
+                                       array('id' => 'review_action_edit', 
'onclick' => 'javascript:controllerListFile.toggleEditReviewView({review: 
'.$this->getID().', title: \''.addslashes($this->getTitle()).'\',
+                                                                               
                                                                description: 
\''.addslashes($this->getDescription()).'\',
+                                                                               
                                                                endreviewdate: 
\''.util_html_encode($enddate).'\',
+                                                                               
                                                                serialid: 
'.$this->getSerialID().',
+                                                                               
                                                                mandatoryusers: 
'.json_encode($mandatoryUsers).',
+                                                                               
                                                                optionalusers: 
'.json_encode($optionalUsers).',})'), true);
+       }
+
+       function getReminderAction() {
+               global $HTML;
+               return util_make_link('#', $HTML->getMailNotifyPic(_('Send 
reminder to pending reviewers'), 'reminderreview'), array('id' => 
'review_action_reminder', 'onclick' => 
'javascript:controllerListFile.reminderReview({review: '.$this->getID().'})'), 
true);
+       }
+
+       function getCompleteAction() {
+               global $HTML;
+               return util_make_link('#', 
$HTML->getClosedTicketPic(_('Complete this review'), 'completereview'), 
array('id' => 'review_action_complete', 'onclick' => 
'javascript:controllerListFile.toggleCompleteReviewView({review: 
'.$this->getID().'})'), true);
+       }
+
+       function getCommentAction() {
+               global $HTML;
+               return util_make_link('#', $HTML->getEditFilePic(_('Comment the 
review'), 'remindercomment'), array('id' => 'review_action_comment', 'onclick' 
=> 'javascript:controllerListFile.toggleCommentReviewView({review: 
'.$this->getID().'})'), true);
+       }
+
+       function showCreateFormHTML() {
+               global $HTML;
+               $dvf = new DocumentVersionFactory($this->Document);
+               $return = '';
+               if (is_object($dvf)) {
+                       $userObjects = $this->Document->Group->getUsers();
+                       $userNameArray = array();
+                       $userIDArray = array();
+                       foreach ($userObjects as $userObject) {
+                               if (forge_check_perm_for_user($userObject, 
'docman', $this->Document->Group->getID(), 'approve')) {
+                                       $userNameArray[] = 
$userObject->getRealName();
+                                       $userIDArray[] = $userObject->getID();
+                               }
+                       }
+                       $date_format_js = _('yy-mm-dd');
+                       $return = html_ao('div', array('style' => 'display: 
none;', 'id' => 'editfile-createreview'));
+                       $return .= $HTML->listTableTop();
+                       $cells = array();
+                       $cells[] = 
array(_('Title').utils_requiredField()._(':'), 'style' => 'width: 30%;');
+                       $cells[][] = html_e('input', array('type' => 'text', 
'id' => 'review-title', 'name' => 'review-title', 'style' => 'width: 100%; 
box-sizing: border-box;', 'required' => 'required', 'pattern' => '.{5,}', 
'placeholder' => _('Title').' '.sprintf(_('(at least %s characters)'), 
DOCMAN__REVIEW_TITLE_MIN_SIZE), 'maxlength' => DOCMAN__REVIEW_TITLE_MAX_SIZE));
+                       $return .= $HTML->multiTableRow(array(), $cells);
+                       $cells = array();
+                       $cells[][] = 
_('Description').utils_requiredField()._(':');
+                       $cells[][] = html_e('textarea', array('id' => 
'review-description', 'name' => 'review-description', 'style' => 'width: 100%; 
box-sizing: border-box;', 'rows' => 3, 'required' => 'required', 'pattern' => 
'.{10,}', 'placeholder' => _('Description').' '.sprintf(_('(at least %s 
characters)'), DOCMAN__REVIEW_DESCRIPTION_MIN_SIZE), 'maxlength' => 
DOCMAN__REVIEW_DESCRIPTION_MAX_SIZE), '', false);
+                       $return .= $HTML->multiTableRow(array(), $cells);
+                       $cells = array();
+                       $cells[][] = _('Select Version to 
review').utils_requiredField()._(':');
+                       $cells[][] = 
html_build_select_box($dvf->getDBResVersionSerialIDs(), 'review-serialid', 
false, false, '', false, '', false, array('id' => 'review-serialid'));
+                       $return .= $HTML->multiTableRow(array(), $cells);
+                       $cells = array();
+                       $cells[][] = _('Select End date of this 
review').utils_requiredField()._(':');
+                       $cells[][] = html_e('input', array('id' => 
'datepicker_end_review_date', 'name' => 'review-enddate', 'size' => 10, 
'maxlength' => 10, 'required' => 'required'));
+                       $return .= $HTML->multiTableRow(array(), $cells);
+                       $cells = array();
+                       $cells[][] =_('Add mandatory 
reviewers').utils_requiredField()._(':');
+                       $cells[][] = html_e('p', array(), 
html_build_multiple_select_box_from_arrays($userIDArray, $userNameArray, 
'review-select-mandatory-users[]', array(), 8, false, 'none', false, array('id' 
=> 'review-select-mandatory-users')));
+                       $return .= $HTML->multiTableRow(array(), $cells);
+                       $cells = array();
+                       $cells[][] = _('Add optional reviewers')._(':');
+                       $cells[][] = html_e('p', array(), 
html_build_multiple_select_box_from_arrays($userIDArray, $userNameArray, 
'review-select-optional-users[]', array(), 8, false, 'none', false, array('id' 
=> 'review-select-optional-users')));
+                       $return .= $HTML->multiTableRow(array(), $cells);
+                       $return .= $HTML->listTableBottom();
+                       $return .= $HTML->addRequiredFieldsInfoBox();
+                       $return .= html_e('input', array('type' => 'hidden', 
'id' => 'new_review', 'name' => 'new_review', 'value' => 0));
+                       $return .= html_e('input', array('type' => 'hidden', 
'id' => 'review_id', 'name' => 'review_id', 'value' => 0));
+                       $return .= html_ac(html_ap() -1);
+                       $javascript = 
'jQuery("#datepicker_end_review_date").datepicker({dateFormat: 
"'.$date_format_js.'"});';
+                       $return .= html_e('script', array( 
'type'=>'text/javascript'), '//<![CDATA['."\n".$javascript."\n".'//]]>');
+               } else {
+                       $return = $HTML->error_msg(_('Cannot get Document 
Versions'));
+               }
+               return $return;
+       }
+
+       function create($reviewversionserialid, $reviewtitle, 
$reviewdescription, $reviewenddate, $reviewmandatoryusers, $reviewoptionalusers 
= array()) {
+               if (!is_int($reviewversionserialid) && $reviewversionserialid < 
1) {
+                       $this->setError(_('Missing Version ID to create 
review'));
+                       return false;
+               }
+               if (strlen($reviewtitle) < DOCMAN__REVIEW_TITLE_MIN_SIZE || 
strlen($reviewtitle) > DOCMAN__REVIEW_TITLE_MAX_SIZE) {
+                       $this->setError(sprintf(_('Review Title must be %d 
characters minimum and %d characters maximum'), DOCMAN__REVIEW_TITLE_MIN_SIZE, 
DOCMAN__REVIEW_TITLE_MAX_SIZE));
+                       return false;
+               }
+               if (strlen($reviewdescription) < 
DOCMAN__REVIEW_DESCRIPTION_MIN_SIZE || strlen($reviewdescription) > 
DOCMAN__REVIEW_DESCRIPTION_MAX_SIZE) {
+                       $this->setError(sprintf(_('Review Description must be 
%d characters minimum and %d characters maximum'), 
DOCMAN__REVIEW_DESCRIPTION_MIN_SIZE, DOCMAN__REVIEW_DESCRIPTION_MAX_SIZE));
+                       return false;
+               }
+               if ($reviewenddate < time()) {
+                       $this->setError(_('Review End date is in the past. 
Please set it the future'));
+                       return false;
+               }
+               if ((is_array($reviewmandatoryusers) && 
count($reviewmandatoryusers) == 0) || (!is_array($reviewmandatoryusers))) {
+                       $this->setError(_('Missing mandatory 
reviewers').$reviewmandatoryusers);
+                       return false;
+               }
+               if (!is_array($reviewoptionalusers)) {
+                       $this->setError(_('Wrong parameter type for optional 
reviewers'));
+                       return false;
+               }
+
+               $user = session_get_user();
+               db_begin();
+               $res = db_query_params('INSERT INTO doc_review (created_by, 
statusid, docid, startdate, enddate, title, description) VALUES ($1, $2, $3, 
$4, $5, $6, $7)',
+                                       array($user->getID(), 1, 
$this->Document->getID(), time(), $reviewenddate, $reviewtitle, 
$reviewdescription));
+               if ($res) {
+                       $notifyUsers = array();
+                       $revid = db_insertid($res, 'doc_review', 'revid');
+                       $this->fetchData($revid);
+                       db_query_params('INSERT INTO doc_review_version (revid, 
serialid) VALUES ($1, $2)', array($revid, $reviewversionserialid));
+                       $args = array();
+                       foreach ($reviewmandatoryusers as $reviewmandatoryuser) 
{
+                               $args[] = array($revid, $reviewmandatoryuser, 
1, 1);
+                       }
+                       if (count($reviewoptionalusers) > 0) {
+                               foreach ($reviewoptionalusers as 
$reviewoptionaluser) {
+                                       $args[] = array($revid, 
$reviewoptionaluser, 2, 1);
+                               }
+                       }
+                       foreach ($args as $arg) {
+                               db_query_params('INSERT INTO doc_review_users 
(revid, userid, typeid, statusid) VALUES ($1, $2, $3, $4)', $arg);
+                               $notifyUsers[] = $arg;
+
+                       }
+                       $this->sendNotice($notifyUsers, true);
+                       db_commit();
+                       return true;
+               } else {
+                       db_rollback();
+               }
+               $this->setError(_('Unable to create review'));
+               return false;
+       }
+
+       /**
+        * sendNotice - Notifies users of review.
+        *
+        * @param       array   array of users where IDs to be notify is arr[1].
+        * @param       boolean true = new review, false = reminder
+        * @return      bool
+        */
+       function sendNotice($users, $new = false) {
+               if (count($users) > 0) {
+                       $createdbyuser = user_get_object($this->getCreatedBy());
+                       if ($new) {
+                               $title = _('New review started');
+                       } else {
+                               $title = _('Review still open');
+                       }
+                       $subject = 
'['.$this->Document->Group->getPublicName().'] '.$title.' '._('for the 
document').' - '.$this->Document->getName().' - '._('version ID')._(': 
').$this->getVersion();
+                       $body = _('Project')._(': 
').$this->Document->Group->getPublicName()."\n";
+                       $body .= _('Folder')._(': 
').$this->Document->getDocGroupName()."\n";
+                       $body .= _('Document version')._(': 
').$this->getVersion()."\n";
+                       $dv = documentversion_get_object($this->getVersion(), 
$this->Document->getID(), $this->Document->Group->getID());
+                       $body .= _('Document version title')._(': 
').$dv->getTitle()."\n";
+                       $body .= _('Document version description')._(': 
').util_unconvert_htmlspecialchars($dv->getDescription())."\n";
+                       $body .= _('Review submitter')._(': 
').$createdbyuser->getRealName().' ('.$createdbyuser->getUnixName().") \n";
+                       $body .= 
"\n\n-------------------------------------------------------\n".
+                               _('Please review, visit')._(':').
+                               "\n\n" . 
util_make_url('/docman/?group_id='.$this->Document->Group->getID().'&view=listfile&dirid='.$this->Document->getDocGroupID().'&filedetailid='.$this->Document->getID());
+
+                       foreach ($users as $user) {
+                               $userObject = user_get_object($user[1]);
+                               util_send_message($userObject->getEmail(), 
$subject, $body, 'noreply@'.forge_get_config('web_host'), '', _('Docman'));
+                       }
+                       return true;
+               }
+               return false;
+       }
+
+       function sendDeleteNotice($users) {
+               if (count($users) > 0) {
+                       $deletedbyuser = session_get_user();
+                       $createdbyuser = user_get_object($this->getCreatedBy());
+                       $subject = 
'['.$this->Document->Group->getPublicName().'] '._('Review deleted for the 
document').' - '.$this->Document->getName().' - '._('version ID')._(': 
').$this->getVersion();
+                       $body = _('Project')._(': 
').$this->Document->Group->getPublicName()."\n";
+                       $body .= _('Folder')._(': 
').$this->Document->getDocGroupName()."\n";
+                       $body .= _('Document version')._(': 
').$this->getVersion()."\n";
+                       $dv = documentversion_get_object($this->getVersion(), 
$this->Document->getID(), $this->Document->Group->getID());
+                       $body .= _('Document version title')._(': 
').$dv->getTitle()."\n";
+                       $body .= _('Document version description')._(': 
').util_unconvert_htmlspecialchars($dv->getDescription())."\n";
+                       $body .= _('Review submitter')._(': 
').$createdbyuser->getRealName().' ('.$createdbyuser->getUnixName().") \n";
+                       $body .= 
"\n\n-------------------------------------------------------\n".
+                               sprintf(_('This review ID %d has been deleted 
by %s (%s)'), $this->getID(), $deletedbyuser->getRealName(), 
$deletedbyuser->getUnixName());
+
+                       foreach ($users as $user) {
+                               $userObject = user_get_object($user[1]);
+                               util_send_message($userObject->getEmail(), 
$subject, $body, 'noreply@'.forge_get_config('web_host'), '', _('Docman'));
+                       }
+                       return true;
+               }
+               return false;
+       }
+
+       function getProgressbar() {
+               $doneMandatoryUsers = array();
+               $mandatoryUsers = $this->getMandatoryUsers();
+               foreach ($mandatoryUsers as $mandarotyUser) {
+                       if ($mandarotyUser['statusid'] == 2) {
+                               $doneMandatoryUsers[] = $mandarotyUser;
+                       }
+               }
+               $percentDoneMandatoryUsers = ((count($doneMandatoryUsers) / 
count($mandatoryUsers)) * 100).'%';
+               $doneOptionalUsers = array();
+               $optionalUsers = $this->getOptionalUsers();
+               foreach ($optionalUsers as $optionalUser) {
+                       if ($optionalUser['statusid'] == 2) {
+                               $doneOptionalUsers[] = $optionalUser;
+                       }
+               }
+               if (count($optionalUsers) > 0) {
+                       $percentDoneOptionalUsers = ((count($doneOptionalUsers) 
/ count($optionalUsers)) * 100).'%';
+               } else {
+                       $percentDoneOptionalUsers = _('n/a');
+               }
+               return html_e('span', array('title' => _('% of mandotary users 
with status done - % of optional users with status done')), 
$percentDoneMandatoryUsers.' - '.$percentDoneOptionalUsers);
+       }
+
+       function delete() {
+               $users = $this->getUsers(array(1));
+               $res = db_query_params('DELETE FROM doc_review WHERE revid = 
$1', array($this->getID()));
+               if ($res) {
+                       if (count($users) > 0) {
+                               $this->sendDeleteNotice($users);
+                       }
+                       return true;
+               }
+               $this->setError(db_error());
+               return false;
+       }
+
+       function update($reviewversionserialid, $reviewtitle, 
$reviewdescription, $reviewenddate, $reviewmandatoryusers, $reviewoptionalusers 
= array()) {
+               if (!is_int($reviewversionserialid) && $reviewversionserialid < 
1) {
+                       $this->setError(_('Missing Version ID to create 
review'));
+                       return false;
+               }
+               if (strlen($reviewtitle) < DOCMAN__REVIEW_TITLE_MIN_SIZE || 
strlen($reviewtitle) > DOCMAN__REVIEW_TITLE_MAX_SIZE) {
+                       $this->setError(sprintf(_('Review Title must be %d 
characters minimum and %d characters maximum'), DOCMAN__REVIEW_TITLE_MIN_SIZE, 
DOCMAN__REVIEW_TITLE_MAX_SIZE));
+                       return false;
+               }
+               if (strlen($reviewdescription) < 
DOCMAN__REVIEW_DESCRIPTION_MIN_SIZE || strlen($reviewdescription) > 
DOCMAN__REVIEW_DESCRIPTION_MAX_SIZE) {
+                       $this->setError(sprintf(_('Review Description must be 
%d characters minimum and %d characters maximum'), 
DOCMAN__REVIEW_DESCRIPTION_MIN_SIZE, DOCMAN__REVIEW_DESCRIPTION_MAX_SIZE));
+                       return false;
+               }
+               if ($reviewenddate < time()) {
+                       $this->setError(_('Review End date is in the past. 
Please set it the future'));
+                       return false;
+               }
+               if ((is_array($reviewmandatoryusers) && 
count($reviewmandatoryusers) == 0) || (!is_array($reviewmandatoryusers))) {
+                       $this->setError(_('Missing mandatory 
reviewers').$reviewmandatoryusers);
+                       return false;
+               }
+               if (!is_array($reviewoptionalusers)) {
+                       $this->setError(_('Wrong parameter type for optional 
reviewers'));
+                       return false;
+               }
+
+               db_begin();
+               $res = db_query_params('UPDATE doc_review SET (enddate, title, 
description) = ($1, $2, $3) WHERE revid = $4', array($reviewenddate, 
$reviewtitle, $reviewdescription, $this->getID()));
+               if ($res) {
+                       if ($reviewversionserialid != $this->getSerialID()) {
+                               db_query_params('UPDATE doc_review_version SET 
serialid = $1 WHERE revid = $2', array($reviewversionserialid, $this->getID()));
+                       }
+                       $mandatoryUsers = $this->getMandatoryUsers();
+                       $mandarotyUserIDs = array();
+                       foreach ($mandatoryUsers as $mandatoryUser) {
+                               $mandarotyUserIDs[] = $mandatoryUser['userid'];
+                       }
+                       $optionalUsers = $this->getOptionalUsers();
+                       $optionalUserIDs = array();
+                       foreach ($optionalUsers as $optionalUser) {
+                               $optionalUserIDs[] = $optionalUser['userid'];
+                       }
+                       foreach ($reviewmandatoryusers as $reviewmandatoryuser) 
{
+                               if (in_array($reviewmandatoryuser, 
$mandarotyUserIDs)) {
+                                       
unset($mandarotyUserIDs[array_search($reviewmandatoryuser, $mandarotyUserIDs)]);
+                                       var_dump($mandarotyUserIDs);
+                               } elseif (in_array($reviewmandatoryuser, 
$optionalUserIDs)) {
+                                       db_query_params('UPDATE 
doc_review_users SET typeid = $1 WHERE userid = $2 AND revid = $3', array(1, 
$reviewmandatoryuser, $this->getID()));
+                                       
unset($optionalUserIDs[array_search($reviewmandatoryuser, $optionalUserIDs)]);
+                               } else {
+                                       db_query_params('INSERT INTO 
doc_review_users (revid, userid, typeid, statusid) VALUES ($1, $2, $3, $4)', 
array($this->getID(), $reviewmandatoryuser, 1, 1));
+                               }
+                       }
+                       foreach ($reviewoptionalusers as $reviewoptionaluser) {
+                               if (in_array($reviewoptionaluser, 
$mandarotyUserIDs)) {
+                                       db_query_params('UPDATE 
doc_review_users SET typeid = $1 WHERE userid = $2 AND revid = $3', array(2, 
$reviewoptionaluser, $this->getID()));
+                                       
unset($mandarotyUserIDs[array_search($reviewoptionaluser, $mandarotyUserIDs)]);
+                               } elseif (in_array($reviewoptionaluser, 
$optionalUserIDs)) {
+                                       
unset($optionalUserIDs[array_search($reviewoptionaluser, $optionalUserIDs)]);
+                               } else {
+                                       db_query_params('INSERT INTO 
doc_review_users (revid, userid, typeid, statusid) VALUES ($1, $2, $3, $4)', 
array($this->getID(), $reviewoptionaluser, 2, 1));
+                               }
+                       }
+                       foreach ($mandarotyUserIDs as $mandarotyUserID) {
+                               db_query_params('DELETE from doc_review_users 
WHERE userid = $1 AND revid = $2', array($mandarotyUserID, $this->getID()));
+                       }
+                       foreach ($optionalUserIDs as $optionalUserID) {
+                               db_query_params('DELETE from doc_review_users 
WHERE userid = $1 AND revid = $2', array($optionalUserID, $this->getID()));
+                       }
+                       db_commit();
+                       return true;
+               }
+               db_rollback();
+               $this->setError(db_error());
+               return false;
+       }
+
+       function getNbComments() {
+               $res = db_query_params('SELECT COUNT(commentid) FROM 
doc_review_comments WHERE revid = $1', array($this->getID()));
+               if ($res) {
+                       return db_result($res, 0, 0);
+               }
+               return null;
+       }
+
+       function getComments() {
+               return array();
+       }
+
+       function isCompleted() {
+               if ($this->getStatusID() == 2) {
+                       return true;
+               } else {
+                       return false;
+               }
+       }
+}
diff --git a/src/common/docman/DocumentReviewFactory.class.php 
b/src/common/docman/DocumentReviewFactory.class.php
new file mode 100644
index 0000000..b825b43
--- /dev/null
+++ b/src/common/docman/DocumentReviewFactory.class.php
@@ -0,0 +1,123 @@
+<?php
+/**
+ * FusionForge Documentation Manager
+ *
+ * Copyright 2016, Franck Villaume - TrivialDev
+ * http://fusionforge.org
+ *
+ * This file is part of FusionForge. FusionForge is free software;
+ * you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the Licence, or (at your option)
+ * any later version.
+ *
+ * FusionForge 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with FusionForge; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+class DocumentReviewFactory extends FFError {
+       /**
+        * The Document object.
+        *
+        * @var object  $Document
+        */
+       var $Document;
+
+       /**
+        * @var array   $reviews Reviews of this document
+        */
+       var $reviews;
+
+       /**
+        * @param       $Document
+        * @internal    param           \The $object Document object to which 
this review factory is associated.
+        */
+       function __construct(&$Document) {
+               parent::__construct();
+               if (!$Document || !is_object($Document)) {
+                       $this->setError(_('No Valid Document Object'));
+                       return false;
+               }
+               if ($Document->isError()) {
+                       $this->setError(_('Document')._(': 
').$Document->getErrorMessage());
+                       return false;
+               }
+               $this->Document =& $Document;
+               return true;
+       }
+
+       function getReviews($serialids = array()) {
+               $res = db_query_params('SELECT doc_review.revid as revid, 
created_by, statusid, docid, startdate, enddate, title, description, 
doc_review_version.serialid as serialid FROM doc_review, doc_review_version
+                                       WHERE doc_review.revid = 
doc_review_version.revid AND doc_review_version.serialid = ANY ($1) AND 
doc_review.docid = $2 ORDER BY enddate DESC',
+                                       
array(db_int_array_to_any_clause($serialids), $this->Document->getID()));
+               if ($res) {
+                       while ($arr = db_fetch_array($res)) {
+                               $serialid = $arr['serialid'];
+                               $this->reviews[] = $arr;
+                       }
+               }
+               db_free_result($res);
+               return $this->reviews;
+       }
+
+       function getReviewsHTML($serialids = array()) {
+               global $HTML;
+               $return = '';
+               $this->getReviews($serialids);
+               if ($this->getReviewsCounter() > 0) {
+                       $titleArr = array('ID', _('Version'), _('Title'), 
_('Created By'), _('Status'), _('End date'), _('Progress'), _('Comments'), 
_('Actions'));
+                       $classth = array('', '', '', '', '', 'unsortable');
+                       $return .= $HTML->listTableTop($titleArr, array(), 
'full sortable', 'sortable_docman_listreview', $classth);
+                       foreach ($this->reviews as $thereview) {
+                               $dr = new DocumentReview($this->Document, 
$thereview[0]);
+                               $cells = array();
+                               $cells[][] = $thereview[0];
+                               $cells[] = array($dr->getVersion(), 'id' => 
'docversionreview'.$dr->getVersion());
+                               $cells[] = array($dr->getTitle(), 'title' => 
$dr->getDescription());
+                               $cells[][] = $dr->getCreateByRealNameLink();
+                               $cells[][] = $dr->getStatusIcon();
+                               $overdue = '';
+                               if (time() > $dr->getEnddate()) {
+                                       $overdue = $HTML->getErrorPic(_('Review 
overdue'), 'overdue');
+                               }
+                               $cells[][] = strftime(_('%Y-%m-%d'), 
$dr->getEnddate()).$overdue;
+                               $cells[][] = $dr->getProgressbar();
+                               $cells[][] = $dr->getNbComments();
+                               $actions = '';
+                               $user = session_get_user();
+                               $users = $dr->getUsers(array(1, 2));
+                               if ($user->getID() == $dr->getCreatedBy()) {
+                                       $actions .= 
$dr->getReminderAction().$dr->getEditAction();
+                                       if ($dr->isCompleted()) {
+                                               $actions .= 
$dr->getCompleteAction();
+                                       }
+                               }
+                               if (($dr->getStatusID() == 1) && 
(($user->getID() == $dr->getCreatedBy()) || in_array($user->getID(), $users))) {
+                                       $actions .= $dr->getCommentAction();
+                               }
+                               if ($user->getID() == $dr->getCreatedBy()) {
+                                       $actions .= $dr->getDeleteAction();
+                               }
+
+                               $cells[][] = $actions;
+                               $return .= $HTML->multiTableRow(array('id' => 
'docreview'.$thereview[0]), $cells);
+                       }
+                       $return .= $HTML->listTableBottom();
+               } else {
+                       $return = $HTML->information(_('No Reviews.'));
+               }
+               $return .= html_e('button', array('id' => 
'doc_review_addbutton', 'type' => 'button', 'onclick' => 
'javascript:controllerListFile.toggleAddReviewView()'), _('Add new review'));
+               return $return;
+       }
+
+
+       function getReviewsCounter() {
+               return count($this->reviews);
+       }
+}
diff --git a/src/common/docman/actions/deletereview.php 
b/src/common/docman/actions/deletereview.php
new file mode 100644
index 0000000..dfe54fc
--- /dev/null
+++ b/src/common/docman/actions/deletereview.php
@@ -0,0 +1,65 @@
+<?php
+/**
+ * FusionForge Documentation Manager
+ *
+ * Copyright 2016, Franck Villaume - TrivialDev
+ * http://fusionforge.org
+ *
+ * This file is part of FusionForge. FusionForge is free software;
+ * you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the Licence, or (at your option)
+ * any later version.
+ *
+ * FusionForge 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with FusionForge; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/* please do not add require here : use www/docman/index.php to add require */
+/* global variables used */
+global $g; // Group object
+global $group_id; // id of group
+global $HTML;
+
+$sysdebug_enable = false;
+$result = array();
+$result['status'] = 0;
+
+if (!forge_check_perm('docman', $group_id, 'approve')) {
+       $result['html'] = $HTML->error_msg(_('Document Manager Action 
Denied.'));
+       echo json_encode($result);
+       exit;
+}
+
+$docid = getIntFromRequest('docid');
+$review = getIntFromRequest('review');
+
+if ($docid && $review) {
+       $documentObject = document_get_object($docid, $group_id);
+       if ($documentObject && !$documentObject->isError()) {
+               $dr = new DocumentReview($documentObject, $review);
+               if ($dr && !$dr->isError()) {
+                       if ($dr->delete()) {
+                               $result['html'] = $HTML->feedback(_('Review 
deleted successfully.'));
+                               $result['status'] = 1;
+                       } else {
+                               $result['html'] = $HTML->error_msg(_('Cannot 
delete review ID')._(': ').$review.' '.$dr->getErrorMessage());
+                       }
+               } else {
+                       $result['html'] = $HTML->error_msg(_('Cannot create 
object documentreview'));
+               }
+       } else {
+               $result['html'] = $HTML->error_msg(_('Cannot retrieve 
document')._(': ').$docid);
+       }
+} else {
+       $result['html'] = $HTML->warning_msg(_('No document ID. Cannot retrieve 
review.'));
+}
+
+echo json_encode($result);
+exit;
diff --git a/src/common/docman/actions/editfile.php 
b/src/common/docman/actions/editfile.php
index f73827a..3265421 100644
--- a/src/common/docman/actions/editfile.php
+++ b/src/common/docman/actions/editfile.php
@@ -40,145 +40,179 @@ if ($childgroup_id) {
        $urlparam .= '&childgroup_id='.$childgroup_id;
 }
 
-$doc_group = getIntFromRequest('doc_group');
-$fromview = getStringFromRequest('fromview');
-
-switch ($fromview) {
-       case 'listrashfile': {
-               $urlparam .= '&view='.$fromview;
-               break;
-       }
-       default: {
-               $urlparam .= '&dirid='.$doc_group;
-               break;
-       }
-}
-
 if (!forge_check_perm('docman', $g->getID(), 'approve')) {
        $warning_msg = _('Document Manager Action Denied.');
        session_redirect($urlparam);
 }
 
+$subaction = getStringFromRequest('subaction', 'version');
 $docid = getIntFromRequest('docid');
-$title = getStringFromRequest('title');
-$description = getStringFromRequest('description');
-$vcomment = getStringFromRequest('vcomment');
-$details = getStringFromRequest('details');
-$file_url = getStringFromRequest('file_url');
-$uploaded_data = getUploadedFile('uploaded_data');
-$stateid = getIntFromRequest('stateid');
-$filetype = getStringFromRequest('filetype');
-$editor = getStringFromRequest('editor');
-$current_version_radio = getIntFromRequest('doc_version_cv_radio');
-$current_version = getIntFromRequest('current_version', 0);
-$version = getIntFromRequest('edit_version', 0);
-$new_version = getIntFromRequest('new_version', 0);
-$newobjectsassociation = getStringFromRequest('newobjectsassociation');
-
 if (!$docid) {
        $warning_msg = _('No document found to update');
        session_redirect($urlparam);
 }
-
 $d = document_get_object($docid, $g->getID());
 if ($d->isError()) {
        $error_msg = $d->getErrorMessage();
        session_redirect($urlparam);
 }
 
-$sanitizer = new TextSanitizer();
-$details = $sanitizer->SanitizeHtml($details);
-$data = '';
+$doc_group = getIntFromRequest('doc_group');
+$fromview = getStringFromRequest('fromview');
 
-if ($version) {
-       $dv = documentversion_get_object($version, $docid, $group_id);
-       if (($editor) && ($dv->getFileData() != $details) && 
(!$uploaded_data['name'])) {
-               $filename = $dv->getFileName();
-               $datafile = tempnam('/tmp', 'docman');
-               $fh = fopen($datafile, 'w');
-               fwrite($fh, $details);
-               fclose($fh);
-               $data = $datafile;
-               if (!$filetype)
-                       $filetype = $dv->getFileType();
+switch ($fromview) {
+       case 'listrashfile': {
+               $urlparam .= '&view='.$fromview;
+               break;
+       }
+       default: {
+               $urlparam .= '&dirid='.$doc_group;
+               break;
+       }
+}
 
-       } elseif (!empty($uploaded_data) && $uploaded_data['name']) {
-               if (!is_uploaded_file($uploaded_data['tmp_name'])) {
-                       $error_msg = sprintf(_('Invalid file attack attempt 
%s.'), $uploaded_data['name']);
+switch ($subaction) {
+       case 'version':
+               $title = getStringFromRequest('title');
+               $description = getStringFromRequest('description');
+               $vcomment = getStringFromRequest('vcomment');
+               $details = getStringFromRequest('details');
+               $file_url = getStringFromRequest('file_url');
+               $uploaded_data = getUploadedFile('uploaded_data');
+               $stateid = getIntFromRequest('stateid');
+               $filetype = getStringFromRequest('filetype');
+               $editor = getStringFromRequest('editor');
+               $current_version_radio = 
getIntFromRequest('doc_version_cv_radio');
+               $current_version = getIntFromRequest('current_version', 0);
+               $version = getIntFromRequest('edit_version', 0);
+               $new_version = getIntFromRequest('new_version', 0);
+               $sanitizer = new TextSanitizer();
+               $details = $sanitizer->SanitizeHtml($details);
+               $data = '';
+
+               if ($version) {
+                       $dv = documentversion_get_object($version, $docid, 
$group_id);
+                       if (($editor) && ($dv->getFileData() != $details) && 
(!$uploaded_data['name'])) {
+                               $filename = $dv->getFileName();
+                               $datafile = tempnam('/tmp', 'docman');
+                               $fh = fopen($datafile, 'w');
+                               fwrite($fh, $details);
+                               fclose($fh);
+                               $data = $datafile;
+                               if (!$filetype) {
+                                       $filetype = $dv->getFileType();
+                               }
+                       } elseif (!empty($uploaded_data) && 
$uploaded_data['name']) {
+                               if 
(!is_uploaded_file($uploaded_data['tmp_name'])) {
+                                       $error_msg = sprintf(_('Invalid file 
attack attempt %s.'), $uploaded_data['name']);
+                                       session_redirect($urlparam);
+                               }
+                               $data = $uploaded_data['tmp_name'];
+                               $filename = $uploaded_data['name'];
+                               if (function_exists('finfo_open')) {
+                                       $finfo = finfo_open(FILEINFO_MIME_TYPE);
+                                       $filetype = finfo_file($finfo, 
$uploaded_data['tmp_name']);
+                               } else {
+                                       $filetype = $uploaded_data['type'];
+                               }
+                       } elseif ($file_url) {
+                               $filename = $file_url;
+                               $filetype = 'URL';
+                       } else {
+                               $filename = $dv->getFileName();
+                               $filetype = $dv->getFileType();
+                       }
+               } elseif ($new_version) {
+                       if ($editor && $details && $name) {
+                               $filename = $name;
+                               $datafile = tempnam('/tmp', 'docman');
+                               $fh = fopen($datafile, 'w');
+                               fwrite($fh, $details);
+                               fclose($fh);
+                               $data = $datafile;
+                               if (!$filetype) {
+                                       $filetype = 'text/html';
+                               }
+                       } elseif (!empty($uploaded_data) && 
$uploaded_data['name']) {
+                               if 
(!is_uploaded_file($uploaded_data['tmp_name'])) {
+                                       $error_msg = sprintf(_('Invalid file 
attack attempt %s.'), $uploaded_data['name']);
+                                       session_redirect($urlparam);
+                               }
+                               $data = $uploaded_data['tmp_name'];
+                               $filename = $uploaded_data['name'];
+                               if (function_exists('finfo_open')) {
+                                       $finfo = finfo_open(FILEINFO_MIME_TYPE);
+                                       $filetype = finfo_file($finfo, 
$uploaded_data['tmp_name']);
+                               } else {
+                                       $filetype = $uploaded_data['type'];
+                               }
+                       } elseif ($file_url) {
+                               $filename = $file_url;
+                               $filetype = 'URL';
+                       }
+               } elseif (($d->getDocGroupID() != $doc_group) || 
($d->getStateID() != $stateid)) {
+                       // we do the update based on the current version.
+                       if (!$current_version_radio) {
+                               $current_version_radio = $d->getVersion();
+                       }
+                       $dv = 
documentversion_get_object($current_version_radio, $docid, $group_id);
+                       $filename = $dv->getFileName();
+                       $filetype = $dv->getFileType();
+                       $title = $dv->getTitle();
+                       $description = $dv->getDescription();
+                       $vcomment = $dv->getComment();
+                       $version = $current_version_radio;
+                       $current_version = 1;
+               } else {
+                       $warning_msg = _('No action to perform');
                        session_redirect($urlparam);
                }
-               $data = $uploaded_data['tmp_name'];
-               $filename = $uploaded_data['name'];
-               if (function_exists('finfo_open')) {
-                       $finfo = finfo_open(FILEINFO_MIME_TYPE);
-                       $filetype = finfo_file($finfo, 
$uploaded_data['tmp_name']);
+
+               if (!$d->update($filename, $filetype, $data, $doc_group, 
$title, $description, $stateid, $version, $current_version, $new_version, null, 
$vcomment)) {
+                       $error_msg = $d->getErrorMessage();
                } else {
-                       $filetype = $uploaded_data['type'];
+                       $feedback = sprintf(_('Document [D%s] updated 
successfully.'), $d->getID());
                }
-       } elseif ($file_url) {
-               $filename = $file_url;
-               $filetype = 'URL';
-       } else {
-               $filename = $dv->getFileName();
-               $filetype = $dv->getFileType();
-       }
-} elseif ($new_version) {
-       if ($editor && $details && $name) {
-               $filename = $name;
-               $datafile = tempnam('/tmp', 'docman');
-               $fh = fopen($datafile, 'w');
-               fwrite($fh, $details);
-               fclose($fh);
-               $data = $datafile;
-               if (!$filetype)
-                       $filetype = 'text/html';
-
-       } elseif (!empty($uploaded_data) && $uploaded_data['name']) {
-               if (!is_uploaded_file($uploaded_data['tmp_name'])) {
-                       $error_msg = sprintf(_('Invalid file attack attempt 
%s.'), $uploaded_data['name']);
-                       session_redirect($urlparam);
+               break;
+       case 'association':
+               $newobjectsassociation = 
getStringFromRequest('newobjectsassociation');
+               if (!$d->addAssociations($newobjectsassociation)) {
+                       $error_msg = $d->getErrorMessage();
+               } else {
+                       $feedback = sprintf(_('Document [D%s] updated 
successfully.'), $d->getID());
                }
-               $data = $uploaded_data['tmp_name'];
-               $filename = $uploaded_data['name'];
-               if (function_exists('finfo_open')) {
-                       $finfo = finfo_open(FILEINFO_MIME_TYPE);
-                       $filetype = finfo_file($finfo, 
$uploaded_data['tmp_name']);
+               break;
+       case 'review':
+               $reviewtitle = getStringFromRequest('review-title');
+               $reviewdescription = getStringFromRequest('review-description');
+               $reviewversionserialid = getIntFromRequest('review-serialid', 
null);
+               $reviewenddateraw = getStringFromRequest('review-enddate');
+               $date_format = _('%Y-%m-%d');
+               $tmp = strptime($reviewenddateraw, $date_format);
+               $reviewenddate = mktime(0, 0, 0, $tmp['tm_mon']+1, 
$tmp['tm_mday'], $tmp['tm_year'] + 1900);
+               $reviewmandatoryusers = 
getArrayFromRequest('review-select-mandatory-users', array());
+               $reviewoptionalusers = 
getArrayFromRequest('review-select-optional-users', array());
+               $new_review = getIntFromRequest('new_review');
+               $reviewid = getIntFromRequest('review_id');
+               if ($reviewversionserialid) {
+                       if ($new_review) {
+                               $dr = new DocumentReview($d);
+                               if ($dr->create($reviewversionserialid, 
$reviewtitle, $reviewdescription, $reviewenddate, $reviewmandatoryusers, 
$reviewoptionalusers)) {
+                                       $feedback = _('Review created');
+                               } else {
+                                       $error_msg = $dr->getErrorMessage();
+                               }
+                       } else {
+                               $dr = new DocumentReview($d, $reviewid);
+                               if ($dr->update($reviewversionserialid, 
$reviewtitle, $reviewdescription, $reviewenddate, $reviewmandatoryusers, 
$reviewoptionalusers)) {
+                                       $feedback = _('Review updated');
+                               } else {
+                                       $error_msg = $dr->getErrorMessage();
+                               }
+                       }
                } else {
-                       $filetype = $uploaded_data['type'];
+                       $warning_msg = _('Missing version to create review');
                }
-       } elseif ($file_url) {
-               $filename = $file_url;
-               $filetype = 'URL';
-       }
-} elseif (($d->getDocGroupID() != $doc_group) || ($d->getStateID() != 
$stateid)) {
-       // we do the update based on the current version.
-       if (!$current_version_radio) {
-               $current_version_radio = $d->getVersion();
-       }
-       $dv = documentversion_get_object($current_version_radio, $docid, 
$group_id);
-       $filename = $dv->getFileName();
-       $filetype = $dv->getFileType();
-       $title = $dv->getTitle();
-       $description = $dv->getDescription();
-       $vcomment = $dv->getComment();
-       $version = $current_version_radio;
-       $current_version = 1;
-} elseif ($newobjectsassociation) {
-       if (!$d->addAssociations($newobjectsassociation)) {
-               $error_msg = $d->getErrorMessage();
-       } else {
-               $feedback = sprintf(_('Document [D%s] updated successfully.'), 
$d->getID());
-       }
-       session_redirect($urlparam);
-} else {
-       $warning_msg = _('No action to perform');
-       session_redirect($urlparam);
-}
-
-if (!$d->update($filename, $filetype, $data, $doc_group, $title, $description, 
$stateid, $version, $current_version, $new_version, null, $vcomment)) {
-       $error_msg = $d->getErrorMessage();
-       session_redirect($urlparam);
+               break;
 }
-
-$feedback = sprintf(_('Document [D%s] updated successfully.'), $d->getID());
 session_redirect($urlparam);
diff --git a/src/common/docman/actions/getdocreviews.php 
b/src/common/docman/actions/getdocreviews.php
new file mode 100644
index 0000000..8286584
--- /dev/null
+++ b/src/common/docman/actions/getdocreviews.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * FusionForge Documentation Manager
+ *
+ * Copyright 2016, Franck Villaume - TrivialDev
+ * http://fusionforge.org
+ *
+ * This file is part of FusionForge. FusionForge is free software;
+ * you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the Licence, or (at your option)
+ * any later version.
+ *
+ * FusionForge 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with FusionForge; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/* please do not add require here : use www/docman/index.php to add require */
+/* global variables used */
+global $group_id; // id of group
+global $HTML;
+
+$sysdebug_enable = false;
+$result = array();
+
+if (!forge_check_perm('docman', $group_id, 'approve')) {
+       $result['html'] = $HTML->error_msg(_('Document Manager Action 
Denied.'));
+       echo json_encode($result);
+       exit;
+}
+
+$docid = getIntFromRequest('docid');
+if ($docid) {
+       $documentObject = document_get_object($docid, $group_id);
+       if ($documentObject && !$documentObject->isError()) {
+               $drf = new DocumentReviewFactory($documentObject);
+               $dvf = new DocumentVersionFactory($documentObject);
+               $dr = new DocumentReview($documentObject);
+               if ($drf && $dvf && $dr && !$drf->isError() && !$dvf->isError() 
&& !$dr->isError()) {
+                       $serialIDs = $dvf->getSerialIDs();
+                       $result['html'] .= $drf->getReviewsHTML($serialIDs);
+                       $result['html'] .= $dr->showCreateFormHTML();
+                       $result['htmltab'] = _('Reviews').' 
('.$drf->getReviewsCounter().')';
+               } else {
+                       $result['html'] = $HTML->warning_msg(_('Cannot retrieve 
reviews')._(': ').$docid);
+               }
+       } else {
+               $result['html'] = $HTML->warning_msg(_('Cannot retrieve 
document')._(': ').$docid);
+       }
+} else {
+       $result['html'] = $HTML->warning_msg(_('No document ID. Cannot retrieve 
reviews.'));
+}
+
+echo json_encode($result);
+exit;
diff --git a/src/common/docman/actions/reminderreview.php 
b/src/common/docman/actions/reminderreview.php
new file mode 100644
index 0000000..55118cb
--- /dev/null
+++ b/src/common/docman/actions/reminderreview.php
@@ -0,0 +1,65 @@
+<?php
+/**
+ * FusionForge Documentation Manager
+ *
+ * Copyright 2016, Franck Villaume - TrivialDev
+ * http://fusionforge.org
+ *
+ * This file is part of FusionForge. FusionForge is free software;
+ * you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the Licence, or (at your option)
+ * any later version.
+ *
+ * FusionForge 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with FusionForge; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/* please do not add require here : use www/docman/index.php to add require */
+/* global variables used */
+global $g; // Group object
+global $group_id; // id of group
+global $HTML;
+
+$sysdebug_enable = false;
+$result = array();
+$result['status'] = 0;
+
+if (!forge_check_perm('docman', $group_id, 'approve')) {
+       $result['html'] = $HTML->error_msg(_('Document Manager Action 
Denied.'));
+       echo json_encode($result);
+       exit;
+}
+
+$docid = getIntFromRequest('docid');
+$review = getIntFromRequest('review');
+
+if ($docid && $review) {
+       $documentObject = document_get_object($docid, $group_id);
+       if ($documentObject && !$documentObject->isError()) {
+               $dr = new DocumentReview($documentObject, $review);
+               if ($dr && !$dr->isError()) {
+                       $users = $dr->getUsers(array(1));
+                       if ($dr->sendNotice($users)) {
+                               $result['html'] = $HTML->feedback(_('Reminder 
sent successfully.'));
+                       } else {
+                               $result['html'] = $HTML->error_msg(_('No 
reminder sent for review ID')._(': ').$review);
+                       }
+               } else {
+                       $result['html'] = $HTML->error_msg(_('Cannot create 
object documentreview'));
+               }
+       } else {
+               $result['html'] = $HTML->error_msg(_('Cannot retrieve 
document')._(': ').$docid);
+       }
+} else {
+       $result['html'] = $HTML->warning_msg(_('No document ID. Cannot retrieve 
review.'));
+}
+
+echo json_encode($result);
+exit;
diff --git a/src/common/docman/include/constants.php 
b/src/common/docman/include/constants.php
index 7f9f0cb..36e030e 100644
--- a/src/common/docman/include/constants.php
+++ b/src/common/docman/include/constants.php
@@ -26,3 +26,7 @@ define('DOCMAN__DESCRIPTION_MIN_SIZE', 10);
 define('DOCMAN__DESCRIPTION_MAX_SIZE', 255);
 define('DOCMAN__COMMENT_MAX_SIZE', 255);
 define('DOCMAN__INFAMOUS_USER_ID', 100);
+define('DOCMAN__REVIEW_TITLE_MIN_SIZE', 5);
+define('DOCMAN__REVIEW_DESCRIPTION_MIN_SIZE', 10);
+define('DOCMAN__REVIEW_TITLE_MAX_SIZE', 255);
+define('DOCMAN__REVIEW_DESCRIPTION_MAX_SIZE', 255);
diff --git a/src/common/docman/views/editfile.php 
b/src/common/docman/views/editfile.php
index e641504..9b69399 100644
--- a/src/common/docman/views/editfile.php
+++ b/src/common/docman/views/editfile.php
@@ -49,7 +49,8 @@ jQuery(document).ready(function() {
                buttonUrl:              jQuery('#editButtonUrl'),
                buttonManualUpload:     jQuery('#editButtonManualUpload'),
                buttonEditor:           jQuery('#editButtonEditor'),
-               divAssociation:         jQuery('#tabbereditfile-association')
+               divAssociation:         jQuery('#tabbereditfile-association'),
+               divReview:              jQuery('#tabbereditfile-review')
        });
 });
 
@@ -67,14 +68,18 @@ $cells[] = array(_('State')._(':'), 'class' => 
'docman_editfile_title');
 $cells[][] = html_e('select', array('name' => 'stateid', 'id' => 'stateid'), 
'', false);
 echo $HTML->multiTableRow(array(), $cells);
 echo $HTML->listTableBottom();
+echo html_ao('div', array('id' => 'tabbereditfile'));
+$elementsLi = array();
+$elementsLi[] = array('content' => util_make_link('#tabbereditfile-version', 
_('Versions'), array('id' => 'versiontab', 'title' => _('View/Add/Remove 
document version.')), true));
+if (forge_get_config('use_docman_review')) {
+       $elementsLi[] = array('content' => 
util_make_link('#tabbereditfile-review', _('Reviews'), array('id' => 
'reviewtab', 'title' => _('View/Start/Comment document review.')), true));
+}
 if (forge_get_config('use_object_associations')) {
-       echo html_ao('div', array('id' => 'tabbereditfile'));
-       $elementsLi = array();
-       $elementsLi[] = array('content' => 
util_make_link('#tabbereditfile-version', _('Edit Versions'), array('title' => 
_('View/Add/Remove document version.')), true));
        $elementsLi[] = array('content' => 
util_make_link('#tabbereditfile-association', _('Associations'), array('id' => 
'associationtab', 'title' => _('Add/Remove associated objects.')), true));
-       echo $HTML->html_list($elementsLi);
-       echo html_ao('div', array('id' => 'tabbereditfile-version', 'class' => 
'tabbertab'));
 }
+echo $HTML->html_list($elementsLi);
+echo html_ao('div', array('id' => 'tabbereditfile-version', 'class' => 
'tabbertab'));
+
 $thArr = array(_('ID (x)'), _('Filename'), _('Title'), _('Description'), 
_('Comment'), _('Author'), _('Last Time'), _('Size'), _('Actions'));
 $thTitle = array(_('x does mark the current version'), '', '', '', '', '', '', 
'', '', '', '');
 $thSizeCssArr = array(array('style' => 'width: 60px'), array('style' => 
'width: 150px'), array('style' => 'width: 150px'), array('style' => 'width: 
150px'), array('style' => 'width: 110px'),
@@ -86,11 +91,11 @@ echo html_e('button', array('id' => 
'doc_version_addbutton', 'type' => 'button',
 echo $HTML->listTableTop(array(), array(), 'listing full hide', 
'doc_version_edit');
 $cells = array();
 $cells[] = array(_('Document Title').utils_requiredField()._(':'), 'class' => 
'docman_editfile_title');
-$cells[][] = html_e('input', array('pattern' => '.{5,}', 'title' => 
sprintf(_('(at least %s characters)'), 5), 'id' => 'title', 'type' => 'text', 
'name' => 'title', 'size' => '40', 'maxlength' => '255'));
+$cells[][] = html_e('input', array('pattern' => '.{5,}', 'required' => 
'required', 'title' => sprintf(_('(at least %s characters)'), 5), 'id' => 
'title', 'type' => 'text', 'name' => 'title', 'size' => '40', 'maxlength' => 
'255'));
 echo $HTML->multiTableRow(array(), $cells);
 $cells = array();
 $cells[] = array(_('Description').utils_requiredField()._(':'), 'class' => 
'docman_editfile_description');
-$cells[][] = html_e('textarea', array('pattern' => '.{10,}', 'title' => 
_('Editing tips:http,https or ftp: Hyperlinks. [#NNN]: Tracker id NNN. [TNNN]: 
Task id NNN. [wiki:&lt;pagename&gt;]: Wiki page. [forum:&lt;msg_id&gt;]: Forum 
post. [DNNN]: Document id NNN.').
+$cells[][] = html_e('textarea', array('pattern' => '.{10,}', 'required' => 
'required', 'title' => _('Editing tips:http,https or ftp: Hyperlinks. [#NNN]: 
Tracker id NNN. [TNNN]: Task id NNN. [wiki:&lt;pagename&gt;]: Wiki page. 
[forum:&lt;msg_id&gt;]: Forum post. [DNNN]: Document id NNN.').
                                                                                
sprintf(_('at least %s characters)'), 10), 'id' => 'description', 'name' => 
'description', 'maxlength' => '255', 'rows' => '5', 'cols' => '50'), '', false);
 echo $HTML->multiTableRow(array(), $cells);
 $cells = array();
@@ -151,19 +156,26 @@ $cells = array();
 $cells[] = array(_('File')._(':'), 'class' => 'docman_editfile_file');
 $cells[][] = html_e('input', array('type' => 'file', 'name' => 
'uploaded_data')).html_e('br').'('._('max upload size')._(': 
').human_readable_bytes(util_get_maxuploadfilesize()).')';
 echo $HTML->multiTableRow(array('id' => 'uploadnewroweditfile', 'class' => 
'hide'), $cells);
+$cells = array();
+$cells[] = array($HTML->addRequiredFieldsInfoBox(), 'colspan' => 2);
+echo $HTML->multiTableRow(array(), $cells);
 echo $HTML->listTableBottom();
 echo html_e('input', array('type' => 'hidden', 'id' => 'docid', 'name' => 
'docid'));
 echo html_e('input', array('type' => 'hidden', 'id' => 'edit_version', 'name' 
=> 'edit_version'));
 echo html_e('input', array('type' => 'hidden', 'id' => 'new_version', 'name' 
=> 'new_version', 'value' => 0));
+echo html_e('input', array('type' => 'hidden', 'id' => 'subaction', 'name' => 
'subaction', 'value' => 'version'));
+echo html_ac(html_ap() -1);
+if (forge_get_config('use_docman_review')) {
+       echo html_e('div', array('id' => 'tabbereditfile-review', 'class' => 
'tabbertab'), '', false);
+}
 if (forge_get_config('use_object_associations')) {
-       echo html_ac(html_ap() -1);
        echo html_e('div', array('id' => 'tabbereditfile-association', 'class' 
=> 'tabbertab'), '', false);
-       echo '<script type="text/javascript">//<![CDATA[
-                       jQuery(document).ready(function() {
-                               jQuery("#tabbereditfile").tabs();
-                       });
-                       //]]></script>';
-       echo html_ac(html_ap() -1);
 }
+echo '<script type="text/javascript">//<![CDATA[
+               jQuery(document).ready(function() {
+                       jQuery("#tabbereditfile").tabs();
+               });
+               //]]></script>';
+echo html_ac(html_ap() -1);
 echo $HTML->closeForm();
 echo html_ac(html_ap() -1);
diff --git a/src/common/include/FFObject.class.php 
b/src/common/include/FFObject.class.php
index a9a7037..15e70f1 100644
--- a/src/common/include/FFObject.class.php
+++ b/src/common/include/FFObject.class.php
@@ -177,31 +177,46 @@ class FFObject extends FFError {
                                if (preg_match('/^[Dd][0-9]+/', $objectRef)) {
                                        //Document Ref.
                                        $documentId = substr($objectRef, 1);
-                                       $documentObject = 
document_get_object($documentId, $this->getGroupID($this));
-                                       if (is_object($documentObject)) {
-                                               $statusArr[] = 
$this->addAssociationTo($documentObject);
+                                       if ($documentId != $this->getID()) {
+                                               $documentObject = 
document_get_object($documentId, $this->getGroupID($this));
+                                               if (is_object($documentObject)) 
{
+                                                       $statusArr[] = 
$this->addAssociationTo($documentObject);
+                                               } else {
+                                                       
$this->setError(_('Unable to retrieve object ref')._(': ').$objectRef);
+                                                       $statusArr[] = false;
+                                               }
                                        } else {
-                                               $this->setError(_('Unable to 
retrieve object ref')._(': ').$objectRef);
+                                               $this->setError(_('Unable to 
associate to itself'));
                                                $statusArr[] = false;
                                        }
                                } elseif (preg_match('/^#[0-9]+/', $objectRef)) 
{
                                        //Artifact Ref.
                                        $artifactId = substr($objectRef, 1);
-                                       $artifactObject = 
artifact_get_object($artifactId);
-                                       if (is_object($artifactObject)) {
-                                               $statusArr[] = 
$this->addAssociationTo($artifactObject);
+                                       if ($artifactId != $this->getID()) {
+                                               $artifactObject = 
artifact_get_object($artifactId);
+                                               if (is_object($artifactObject)) 
{
+                                                       $statusArr[] = 
$this->addAssociationTo($artifactObject);
+                                               } else {
+                                                       
$this->setError(_('Unable to retrieve object ref')._(': ').$objectRef);
+                                                       $statusArr[] = false;
+                                               }
                                        } else {
-                                               $this->setError(_('Unable to 
retrieve object ref')._(': ').$objectRef);
+                                               $this->setError(_('Unable to 
associate to itself'));
                                                $statusArr[] = false;
                                        }
                                } elseif (preg_match('/^[Rr][0-9]+/', 
$objectRef)) {
                                        //Artifact Ref.
-                                       $frsreleaseid = substr($objectRef, 1);
-                                       $frsreleaseObject = 
frsrelease_get_object($frsreleaseid);
-                                       if (is_object($frsreleaseObject)) {
-                                               $statusArr[] = 
$this->addAssociationTo($frsreleaseObject);
+                                       $frsreleaseId = substr($objectRef, 1);
+                                       if ($frsreleaseId != $this->getID()) {
+                                               $frsreleaseObject = 
frsrelease_get_object($frsreleaseId);
+                                               if 
(is_object($frsreleaseObject)) {
+                                                       $statusArr[] = 
$this->addAssociationTo($frsreleaseObject);
+                                               } else {
+                                                       
$this->setError(_('Unable to retrieve object ref')._(': ').$objectRef);
+                                                       $statusArr[] = false;
+                                               }
                                        } else {
-                                               $this->setError(_('Unable to 
retrieve object ref')._(': ').$objectRef);
+                                               $this->setError(_('Unable to 
associate to itself'));
                                                $statusArr[] = false;
                                        }
                                } else {
diff --git a/src/db/20161126-document-review.sql 
b/src/db/20161126-document-review.sql
new file mode 100644
index 0000000..ef595fe
--- /dev/null
+++ b/src/db/20161126-document-review.sql
@@ -0,0 +1,73 @@
+CREATE SEQUENCE doc_review_pk_seq
+    START WITH 1
+    INCREMENT BY 1
+    MAXVALUE 2147483647
+    NO MINVALUE
+    CACHE 1;
+
+CREATE TABLE doc_review_status (
+    statusid    integer NOT NULL UNIQUE,
+    name        text,
+    description text
+);
+
+INSERT INTO doc_review_status (statusid, name) VALUES (1, 'open');
+INSERT INTO doc_review_status (statusid, name) VALUES (2, 'close');
+INSERT INTO doc_review_status (statusid, name) VALUES (3, 'error');
+
+CREATE TABLE doc_review (
+    revid       integer DEFAULT nextval('doc_review_pk_seq'::text) NOT NULL 
UNIQUE,
+    created_by  integer REFERENCES users (user_id) ON DELETE CASCADE,
+    statusid    integer REFERENCES doc_review_status (statusid),
+    docid       integer REFERENCES doc_data (docid) ON DELETE CASCADE,
+    startdate   integer,
+    enddate     integer,
+    title       text,
+    description text
+);
+
+CREATE TABLE doc_review_version (
+    revid       integer REFERENCES doc_review (revid) ON DELETE CASCADE,
+    serialid    integer REFERENCES doc_data_version (serial_id) ON DELETE 
CASCADE
+);
+
+CREATE TABLE doc_review_users_type (
+    typeid      integer NOT NULL UNIQUE,
+    name        text
+);
+
+INSERT INTO doc_review_users_type (typeid, name) VALUES (1, 'mandatory');
+INSERT INTO doc_review_users_type (typeid, name) VALUES (2, 'optional');
+
+CREATE TABLE doc_review_users_status (
+    statusid    integer NOT NULL UNIQUE,
+    name        text
+);
+
+INSERT INTO doc_review_users_status (statusid, name) VALUES (1, 'pending');
+INSERT INTO doc_review_users_status (statusid, name) VALUES (2, 'done');
+
+CREATE TABLE doc_review_users (
+    revid       integer REFERENCES doc_review (revid) ON DELETE CASCADE,
+    userid      integer REFERENCES users (user_id) ON DELETE CASCADE,
+    typeid      integer REFERENCES doc_review_users_type (typeid),
+    statusid    integer REFERENCES doc_review_users_status (statusid),
+    updatedate  integer
+);
+
+CREATE TABLE doc_review_comments (
+    commentid   integer NOT NULL UNIQUE,
+    revid       integer REFERENCES doc_review (revid) ON DELETE CASCADE,
+    userid      integer REFERENCES users (user_id) ON DELETE CASCADE,
+    rcomment    text,
+    createdate  integer
+);
+
+CREATE TABLE doc_review_attachments (
+    attachid    integer,
+    createdate  integer,
+    commentid   integer REFERENCES doc_review_comments (commentid) ON DELETE 
CASCADE,
+    filename    text,
+    filetype    text,
+    filesize    text
+);
diff --git a/src/etc/config.ini.d/defaults.ini 
b/src/etc/config.ini.d/defaults.ini
index 8710393..9de0344 100644
--- a/src/etc/config.ini.d/defaults.ini
+++ b/src/etc/config.ini.d/defaults.ini
@@ -93,8 +93,9 @@ use_quicknav_default = yes
 use_home = yes
 use_my = yes
 check_password_strength = no
-use_object_associations = yes
+use_object_associations = no
 use_tracker_widget_display = no
+use_docman_review = no
 
 scm_single_host = yes
 system_user=fusionforge
diff --git a/src/www/docman/index.php b/src/www/docman/index.php
index 29811e1..e348e55 100644
--- a/src/www/docman/index.php
+++ b/src/www/docman/index.php
@@ -32,8 +32,8 @@ require_once $gfcommon.'docman/Document.class.php';
 require_once $gfcommon.'docman/DocumentFactory.class.php';
 require_once $gfcommon.'docman/DocumentGroup.class.php';
 require_once $gfcommon.'docman/DocumentGroupFactory.class.php';
-// require_once $gfcommon.'docman/DocumentReview.class.php';
-// require_once $gfcommon.'docman/DocumentReviewFactory.class.php';
+require_once $gfcommon.'docman/DocumentReview.class.php';
+require_once $gfcommon.'docman/DocumentReviewFactory.class.php';
 require_once $gfcommon.'docman/DocumentVersion.class.php';
 require_once $gfcommon.'docman/DocumentVersionFactory.class.php';
 require_once $gfcommon.'docman/include/utils.php';
diff --git a/src/www/docman/scripts/DocManController.js 
b/src/www/docman/scripts/DocManController.js
index 45532d3..ea482da 100644
--- a/src/www/docman/scripts/DocManController.js
+++ b/src/www/docman/scripts/DocManController.js
@@ -64,6 +64,15 @@ DocManListFileController.prototype =
                if (typeof(this.listfileparams.buttonAddItem) != 'undefined') {
                        
this.listfileparams.buttonAddItem.click(jQuery.proxy(this, 
"toggleAddItemView"));
                }
+               if (typeof(jQuery('#versiontab')) != 'undefined') {
+                       jQuery('#versiontab').click(jQuery.proxy(this, 
"setRequiredInputs", jQuery('#versiontab')));
+               }
+               if (typeof(jQuery('#reviewtab')) != 'undefined') {
+                       jQuery('#reviewtab').click(jQuery.proxy(this, 
"setRequiredInputs", jQuery('#reviewtab')));
+               }
+               if (typeof(jQuery('#associationtab')) != 'undefined') {
+                       jQuery('#associationtab').click(jQuery.proxy(this, 
"setRequiredInputs", jQuery('#associationtab')));
+               }
        },
 
        resizableDiv: function() {
@@ -107,7 +116,6 @@ DocManListFileController.prototype =
                        autoOpen: false,
                        width: 1000,
                        modal: true,
-                       title: this.listfileparams.divEditTitle,
                        buttons: {
                                Save: jQuery.proxy(function() {
                                        jQuery('#editdocdata').submit();
@@ -183,14 +191,13 @@ DocManListFileController.prototype =
                var modalId = this.listfileparams.divNotifyUsers;
                jQuery(modalId).dialog({
                        autoOpen: false,
-                       width: 475,
+                       width: 600,
                        modal: true,
-                       title: this.listfileparams.divNotifyTitle,
                        buttons: {
                                Save: { text: 
this.listfileparams.divNotifySaveButtonTxt,
                                        click: jQuery.proxy(function() {
                                        jQuery('#notifyusersdoc').submit();
-                                       var id = 
jQuery('#notifydocid').attr('value');
+                                       var id = jQuery('#docid').attr('value');
                                        
jQuery.get(this.listfileparams.docManURL+'/', {
                                                group_id:       
this.listfileparams.groupId,
                                                action:         'lock',
@@ -212,7 +219,7 @@ DocManListFileController.prototype =
                                        jQuery(modalId).dialog( "close" );
                                }, this)},
                                Cancel: jQuery.proxy(function() {
-                                       var id = 
jQuery('#notifydocid').attr('value');
+                                       var id = jQuery('#docid').attr('value');
                                        
jQuery.get(this.listfileparams.docManURL+'/', {
                                                group_id:       
this.listfileparams.groupId,
                                                action:         'lock',
@@ -236,7 +243,7 @@ DocManListFileController.prototype =
                        }
                });
                jQuery(modalId).bind('dialogclose', jQuery.proxy(function() {
-                       var id = jQuery('#notifydocid').attr('value');
+                       var id = jQuery('#docid').attr('value');
                        jQuery.get(this.listfileparams.docManURL+'/', {
                                group_id:       this.listfileparams.groupId,
                                action:         'lock',
@@ -457,6 +464,27 @@ DocManListFileController.prototype =
                                }
                        }, this));
 
+               jQuery.getJSON(this.listfileparams.docManURL + '/?group_id=' + 
docid_groupid + '&action=getdocreviews&docid='+ this.docparams.id, 
jQuery.proxy(function(data){
+                               if (typeof data.html != 'undefined') {
+                                       jQuery('#tabbereditfile-review > 
.feedback').remove();
+                                       jQuery('#tabbereditfile-review > 
.error').remove();
+                                       jQuery('#tabbereditfile-review > 
.warning_msg').remove();
+                                       jQuery('#tabbereditfile-review > 
.information').remove();
+                                       jQuery('#tabbereditfile-review > 
table').remove();
+                                       jQuery('#tabbereditfile-review > 
span').remove();
+                                       jQuery('#tabbereditfile-review > 
p').remove();
+                                       
jQuery('#editfile-createreview').remove();
+                                       
jQuery('#doc_review_addbutton').remove();
+                                       
jQuery('#tabbereditfile-review').prepend(data.html);
+                                       
jQuery('#doc_review_addbutton').button();
+                               }
+                               if (typeof data.htmltab != 'undefined') {
+                                       jQuery('#reviewtab').text(data.htmltab);
+                               }
+                               
jQuery('#review-select-mandatory-users').gentleSelect({columns: 3, itemWidth: 
150});
+                               
jQuery('#review-select-optional-users').gentleSelect({columns: 3, itemWidth: 
150});
+                       }, this));
+
                jQuery('#editdocdata').attr('action', this.docparams.action);
 
                jQuery.get(this.docparams.docManURL+'/', {
@@ -469,7 +497,9 @@ DocManListFileController.prototype =
                        });
                this.lockInterval[this.docparams.id] = 
setInterval("jQuery.get('" + this.docparams.docManURL + "/', 
{group_id:"+this.docparams.groupId+", action:'lock', lock:1, type:'file', 
itemid:"+this.docparams.id+", childgroup_id:"+this.docparams.childGroupId+"})", 
this.docparams.lockIntervalDelay);
                this.lockInterval[this.docparams.docgroupId] = 
setInterval("jQuery.get('" + this.docparams.docManURL + "/', 
{group_id:"+this.docparams.groupId+", action:'lock', lock:1, type: 'dir', 
itemid:"+this.docparams.docgroupId+", 
childgroup_id:"+this.docparams.childGroupId+"})", 
this.docparams.lockIntervalDelay);
-               jQuery(this.listfileparams.divEditFile).dialog('open');
+               jQuery('#tabbereditfile').tabs("option", "active", 0);
+               this.setRequiredInputs(jQuery('#versiontab'));
+               jQuery(this.listfileparams.divEditFile).dialog('option', 
'title', '[D'+this.docparams.id+'] 
'+this.listfileparams.divEditTitle).dialog('open');
                return false;
        },
 
@@ -490,8 +520,50 @@ DocManListFileController.prototype =
                }
        },
 
+       toggleAddReviewView: function() {
+               jQuery('#review-title').val('');
+               jQuery('#review-description').val('');
+               jQuery('#datepicker_end_review_date').val('');
+               jQuery('#review-serialid').val();
+               jQuery('[class^=gentle]').remove();
+               jQuery('#review-select-mandatory-users').val('');
+               jQuery('#review-select-optional-users').val('');
+               jQuery('#review-select-mandatory-users').gentleSelect({columns: 
3, itemWidth: 150});
+               jQuery('#review-select-optional-users').gentleSelect({columns: 
3, itemWidth: 150});
+               if (jQuery('#editfile-createreview').is(':visible')) {
+                       jQuery('#editfile-createreview').hide();
+                       jQuery('#new_review').val(0);
+               } else {
+                       jQuery('#new_review').val(1);
+                       jQuery('#editfile-createreview').show();
+               }
+       },
+
+       toggleEditReviewView: function(params) {
+               this.review = params;
+               if (jQuery('#editfile-createreview').is(':visible')) {
+                       jQuery('#editfile-createreview').hide();
+                       jQuery('#new_review').val(0);
+                       jQuery('#review_id').val(0);
+               } else {
+                       jQuery('#review_id').val(this.review.review);
+                       jQuery('#review-title').val(this.review.title);
+                       
jQuery('#review-description').val(this.review.description);
+                       
jQuery('#datepicker_end_review_date').val(this.review.endreviewdate);
+                       jQuery('#review-serialid').val(this.review.serialid);
+                       jQuery('[class^=gentle]').remove();
+                       
jQuery('#review-select-mandatory-users').val(this.review.mandatoryusers);
+                       
jQuery('#review-select-optional-users').val(this.review.optionalusers);
+                       
jQuery('#review-select-mandatory-users').gentleSelect({columns: 3, itemWidth: 
150});
+                       
jQuery('#review-select-optional-users').gentleSelect({columns: 3, itemWidth: 
150});
+                       jQuery('#new_review').val(0);
+                       jQuery('#editfile-createreview').show();
+               }
+       },
+
        toggleEditVersionView: function(params) {
                this.version = params;
+               jQuery('#new_version').val(0);
                if (this.version.isHtml) {
                        jQuery('#defaulteditfiletype').val('text/html');
                }
@@ -567,6 +639,10 @@ DocManListFileController.prototype =
                                if (typeof data.status != 'undefined') {
                                        if (data.status == 1) {
                                                
jQuery('#docversion'+this.version).remove();
+                                               //adjust review tab & version 
tab number?
+                                               if 
(jQuery('#docversionreview'+this.version) != 'undefined') {
+                                                       
jQuery('#docversionreview'+this.version).parent.remove();
+                                               }
                                                if 
(jQuery('#sortable_doc_version_table tr').length <= 2) {
                                                        
jQuery('#version_action_delete').remove();
                                                }
@@ -575,6 +651,36 @@ DocManListFileController.prototype =
                        }, this.delversion));
        },
 
+       deleteReview: function(params) {
+               this.delreview = params;
+               jQuery.getJSON(this.docparams.docManURL + '/?group_id=' + 
this.docparams.groupId + 
'&action=deletereview&docid='+this.docparams.id+'&review='+this.delreview.review
 , jQuery.proxy(function(data){
+                               if (typeof data.html != 'undefined') {
+                                       jQuery('#editFile > 
.feedback').remove();
+                                       jQuery('#editFile > .error').remove();
+                                       jQuery('#editFile > 
.warning_msg').remove();
+                                       jQuery('#editFile').prepend(data.html);
+                               }
+                               if (typeof data.status != 'undefined') {
+                                       if (data.status == 1) {
+                                               
jQuery('#docreview'+this.review).remove();
+                                               //adjust review tab number?
+                                       }
+                               }
+                       }, this.delreview));
+       },
+
+       reminderReview: function(params) {
+               this.reminderreview = params;
+               jQuery.getJSON(this.docparams.docManURL + '/?group_id=' + 
this.docparams.groupId + 
'&action=reminderreview&docid='+this.docparams.id+'&review='+this.reminderreview.review
 , jQuery.proxy(function(data){
+                               if (typeof data.html != 'undefined') {
+                                       jQuery('#editFile > 
.feedback').remove();
+                                       jQuery('#editFile > .error').remove();
+                                       jQuery('#editFile > 
.warning_msg').remove();
+                                       jQuery('#editFile').prepend(data.html);
+                               }
+                       }, this.reminderreview));
+       },
+
        toggleMoveFileView: function() {
                if (!this.listfileparams.divMoveFile.is(':visible')) {
                        this.listfileparams.divMoveFile.show();
@@ -595,7 +701,7 @@ DocManListFileController.prototype =
        toggleNotifyUserView: function(params) {
                this.notifyparams = params;
                jQuery('#notifytitle').text(this.notifyparams.title);
-               
jQuery('#notifydescription').text(this.notifyparams.description);
+               
jQuery('#notifydescription').html(this.notifyparams.description);
                jQuery('#notifydocid').val(this.notifyparams.id);
                jQuery('#notifyfilelink').text(this.notifyparams.filename);
                if (this.notifyparams.statusId != 2) {
@@ -617,7 +723,8 @@ DocManListFileController.prototype =
                        });
                this.lockInterval[this.notifyparams.id] = 
setInterval("jQuery.get('" + this.notifyparams.docManURL + "/', 
{group_id:"+this.notifyparams.groupId+",action:'lock',lock:1,type:'file',itemid:"+this.notifyparams.id+",childgroup_id:"+this.notifyparams.childGroupId+"})",
 this.notifyparams.lockIntervalDelay);
                this.lockInterval[this.notifyparams.docgroupId] = 
setInterval("jQuery.get('" + this.notifyparams.docManURL + "/', 
{group_id:"+this.notifyparams.groupId+",action:'lock',lock:1,type:'dir',itemid:"+this.notifyparams.docgroupId+",childgroup_id:"+this.notifyparams.childGroupId+"})",
 this.notifyparams.lockIntervalDelay);
-               jQuery(this.listfileparams.divNotifyUsers).dialog('open');
+               jQuery('#notify-userids').gentleSelect({columns: 2, itemWidth: 
120});
+               jQuery(this.listfileparams.divNotifyUsers).dialog('option', 
'title', '[D'+this.notifyparams.id+'] 
'+this.listfileparams.divNotifyTitle).dialog('open');
 
                return false;
 
@@ -661,6 +768,25 @@ DocManListFileController.prototype =
                                break;
                        }
                }
+       },
+
+       setRequiredInputs: function(id) {
+               if (id.attr('id') == 'reviewtab') {
+                       jQuery('#tabbereditfile-version 
:input').not(':input[type=hidden], :input[type=button]').prop('disabled', true);
+                       jQuery('#tabbereditfile-association').prop('disabled', 
true);
+                       jQuery('#tabbereditfile-review 
:input').removeAttr('disabled');
+                       jQuery('#subaction').val('review');
+               } else if (id.attr('id') == 'associationtab') {
+                       jQuery('#tabbereditfile-version 
:input').not(':input[type=hidden], :input[type=button]').prop('disabled', true);
+                       
jQuery('#tabbereditfile-association').removeAttr('disabled');
+                       jQuery('#tabbereditfile-review 
:input').prop('disabled', true);
+                       jQuery('#subaction').val('association');
+               } else if (id.attr('id') == 'versiontab') {
+                       jQuery('#tabbereditfile-version 
:input').not(':input[type=hidden], :input[type=button]').removeAttr('disabled');
+                       jQuery('#tabbereditfile-association').prop('disabled', 
true);
+                       jQuery('#tabbereditfile-review 
:input').prop('disabled', true);
+                       jQuery('#subaction').val('version');
+               }
        }
 };
 

-----------------------------------------------------------------------

Summary of changes:
 src/common/docman/DocumentReview.class.php         | 524 +++++++++++++++++++++
 src/common/docman/DocumentReviewFactory.class.php  | 123 +++++
 .../{deleteversion.php => deletereview.php}        |  18 +-
 src/common/docman/actions/editfile.php             | 264 ++++++-----
 .../{getdocversions.php => getdocreviews.php}      |  16 +-
 .../{deleteversion.php => reminderreview.php}      |  20 +-
 src/common/docman/include/constants.php            |   4 +
 src/common/docman/views/editfile.php               |  42 +-
 src/common/include/FFObject.class.php              |  41 +-
 src/db/20161126-document-review.sql                |  73 +++
 src/etc/config.ini.d/defaults.ini                  |   3 +-
 src/www/docman/index.php                           |   4 +-
 src/www/docman/scripts/DocManController.js         | 144 +++++-
 13 files changed, 1095 insertions(+), 181 deletions(-)
 create mode 100644 src/common/docman/DocumentReview.class.php
 create mode 100644 src/common/docman/DocumentReviewFactory.class.php
 copy src/common/docman/actions/{deleteversion.php => deletereview.php} (81%)
 copy src/common/docman/actions/{getdocversions.php => getdocreviews.php} (76%)
 copy src/common/docman/actions/{deleteversion.php => reminderreview.php} (80%)
 create mode 100644 src/db/20161126-document-review.sql


hooks/post-receive
-- 
FusionForge

_______________________________________________
Fusionforge-commits mailing list
[email protected]
http://lists.fusionforge.org/cgi-bin/mailman/listinfo/fusionforge-commits

Reply via email to