diff --git a/include/class.dept.php b/include/class.dept.php index d06bef503bf0447b92ec74c61d251a7f4f4182a5..6bb8625a27ff7dd2fd7ef8f8ae3611f480b23fd9 100644 --- a/include/class.dept.php +++ b/include/class.dept.php @@ -548,7 +548,7 @@ implements TemplateVariable { if ($vars['pid'] && !($p = static::lookup($vars['pid']))) $errors['pid'] = __('Department selection is required'); - // Format access update as [array(dept_id, alerts?)] + // Format access update as [array(dept_id, role_id, alerts?)] $access = array(); if (isset($vars['members'])) { foreach (@$vars['members'] as $staff_id) { @@ -576,8 +576,8 @@ implements TemplateVariable { $this->flags = isset($vars['assign_members_only']) ? self::FLAG_ASSIGN_MEMBERS_ONLY : 0; $this->path = $this->getFullPath(); - if ($rv = $this->save()) - return $rv; + if ($this->save()) + return $this->extended->saveAll(); if (isset($this->id)) $errors['err']=sprintf(__('Unable to update %s.'), __('this department')) @@ -611,8 +611,6 @@ implements TemplateVariable { $da->role_id = $role_id; } $da->setAlerts($alerts); - if (!$errors) - $da->save(); } if (!$errors && $dropped) { $this->extended diff --git a/include/class.orm.php b/include/class.orm.php index 8e1a35537ca6ab7803dcce3496bef1bf05113a20..56ee74bdba907098b8f7ca07a87e44c5a5d5ef08 100644 --- a/include/class.orm.php +++ b/include/class.orm.php @@ -1602,6 +1602,58 @@ class InstrumentedList extends ModelInstanceManager { } } + /** + * Sort the instrumented list in place. This would be useful to change the + * sorting order of the items in the list without fetching the list from + * the database again. + * + * Parameters: + * $key - (callable|int) A callable function to produce the sort keys + * or one of the SORT_ constants used by the array_multisort + * function + * $reverse - (bool) true if the list should be sorted descending + * + * Returns: + * This instrumented list for chaining and inlining. + */ + function sort($key=false, $reverse=false) { + // Fetch all records into the cache + $this->asArray(); + if (is_callable($key)) { + array_multisort( + array_map($key, $this->cache), + $reverse ? SORT_DESC : SORT_ASC, + $this->cache); + } + elseif ($key) { + array_multisort($this->cache, + $reverse ? SORT_DESC : SORT_ASC, $key); + } + elseif ($reverse) { + rsort($this->cache); + } + else + sort($this->cache); + return $this; + } + + /** + * Reverse the list item in place. Returns this object for chaining + */ + function reverse() { + $this->asArray(); + array_reverse($this->cache); + return $this; + } + + // Save all changes made to any list items + function saveAll() { + foreach ($this as $I) + if (!$I->save()) + return false; + return true; + } + // QuerySet delegates function count() { return $this->objects()->count(); diff --git a/include/class.staff.php b/include/class.staff.php index d830515b84eb937fb87e24783e0b6924c1e964ee..bf3e59a3f711ec743d197ce489f52bea0e8d18f7 100644 --- a/include/class.staff.php +++ b/include/class.staff.php @@ -609,7 +609,8 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { } } - if($errors) return false; + if ($errors) + return false; $this->firstname = $vars['firstname']; $this->lastname = $vars['lastname']; diff --git a/include/class.team.php b/include/class.team.php index 1165ecc8a104c06e09ba2badd92006c452d8a6ce..fe80bd4f997f12222b1b76b81b717186f9b0e54a 100644 --- a/include/class.team.php +++ b/include/class.team.php @@ -110,7 +110,7 @@ implements TemplateVariable { $base = $this->ht; $base['isenabled'] = $this->isEnabled(); $base['noalerts'] = !$this->alertsEnabled(); - unset($base['staffmembers']); + unset($base['members']); return $base; } @@ -166,15 +166,20 @@ implements TemplateVariable { $this->name = Format::striptags($vars['name']); $this->notes = Format::sanitize($vars['notes']); - if ($this->save()) { - // Remove checked members - if ($vars['remove'] && is_array($vars['remove'])) { - TeamMember::objects() - ->filter(array('staff_id__in' => $vars['remove'])) - ->delete(); + // Format access update as [array(staff_id, alerts?)] + $access = array(); + if (isset($vars['members'])) { + foreach (@$vars['members'] as $staff_id) { + $access[] = array($staff_id, @$vars['member_alerts'][$staff_id]); } - return true; } + $this->updateMembers($access, $errors); + + if ($errors) + return false; + + if ($this->save()) + return $this->members->saveAll(); if (isset($this->team_id)) { $errors['err']=sprintf(__('Unable to update %s.'), __('this team')) @@ -187,6 +192,31 @@ implements TemplateVariable { return false; } + function updateMembers($access, &$errors) { + reset($access); + $dropped = array(); + foreach ($this->members as $member) + $dropped[$member->staff_id] = 1; + while (list(, list($staff_id, $alerts)) = each($access)) { + unset($dropped[$staff_id]); + if (!$staff_id || !Staff::lookup($staff_id)) + $errors['members'][$staff_id] = __('No such agent'); + $member = $this->members->findFirst(array('staff_id' => $staff_id)); + if (!isset($member)) { + $member = TeamMember::create(array('staff_id' => $staff_id)); + $this->members->add($member); + } + $member->setAlerts($alerts); + } + if (!$errors && $dropped) { + $this->members + ->filter(array('staff_id__in' => array_keys($dropped))) + ->delete(); + $this->members->reset(); + } + return !$errors; + } + function save($refetch=false) { if ($this->dirty) $this->updated = SqlFunction::NOW(); @@ -205,11 +235,12 @@ implements TemplateVariable { return false; # Remove members of this team - $this->staffmembers->delete(); + $this->members->delete(); # Reset ticket ownership for tickets owned by this team - db_query('UPDATE '.TICKET_TABLE.' SET team_id=0 WHERE team_id=' - .db_input($id)); + Ticket::objects() + ->filter(array('team_id' => $id)) + ->update(array('team_id' => 0)); return true; } @@ -289,6 +320,7 @@ class TeamMember extends VerySimpleModel { static $meta = array( 'table' => TEAM_MEMBER_TABLE, 'pk' => array('team_id', 'staff_id'), + 'select_related' => array('staff'), 'joins' => array( 'team' => array( 'constraint' => array('team_id' => 'Team.team_id'), diff --git a/include/staff/department.inc.php b/include/staff/department.inc.php index e3be963f66c13d92a2cae69031887744c5a1cbde..c5f474255a47f260155a158278ddc0e2c5e4f2c5 100644 --- a/include/staff/department.inc.php +++ b/include/staff/department.inc.php @@ -305,7 +305,7 @@ foreach ($dept->getMembers() as $member) { <option value="0">— <?php echo __('Select Agent');?> —</option> <?php foreach ($agents as $id=>$name) { - echo sprintf('<option value="%d">%s</option>',$id,$name); + echo sprintf('<option value="%d">%s</option>',$id,Format::htmlchars($name)); } ?> <option value="0" data-quick-add>— <?php echo __('Add New');?> —</option> @@ -332,8 +332,10 @@ foreach ($dept->getMembers() as $member) { <option value="0" data-quick-add>— <?php echo __('Add New');?> —</option> </select> <span style="display:inline-block;width:60px"> </span> - <input type="checkbox" data-name="member_alerts" value="1" /> - <?php echo __('Alerts'); ?> + <label> + <input type="checkbox" data-name="member_alerts" value="1" /> + <?php echo __('Alerts'); ?> + </label> <a href="#" class="pull-right drop-membership" title="<?php echo __('Delete'); ?>"><i class="icon-trash"></i></a> </td> diff --git a/include/staff/staff.inc.php b/include/staff/staff.inc.php index 9bc201df682559cf191bc51b6ec11ddf63600b81..96cb584877af45512c66174258821ec98bee806c 100644 --- a/include/staff/staff.inc.php +++ b/include/staff/staff.inc.php @@ -253,8 +253,10 @@ if (count($bks) > 1) { <option value="0" data-quick-add>— <?php echo __('Add New');?> —</option> </select> <span style="display:inline-block;width:20px"> </span> - <input type="checkbox" data-name="dept_access_alerts" value="1" /> - <?php echo __('Alerts'); ?> + <label> + <input type="checkbox" data-name="dept_access_alerts" value="1" /> + <?php echo __('Alerts'); ?> + </label> <a href="#" class="pull-right drop-access" title="<?php echo __('Delete'); ?>"><i class="icon-trash"></i></a> </td> @@ -279,7 +281,7 @@ foreach ($staff->dept_access as $dept_access) { <option value="0">— <?php echo __('Select Department');?> —</option> <?php foreach ($depts as $id=>$name) { - echo sprintf('<option value="%d">%s</option>',$id,$name); + echo sprintf('<option value="%d">%s</option>',$id,Format::htmlchars($name)); } ?> <option value="0" data-quick-add>— <?php echo __('Add New');?> —</option> @@ -371,7 +373,7 @@ foreach ($staff->teams as $TM) { <option value="0">— <?php echo __('Select Team');?> —</option> <?php foreach ($teams as $id=>$name) { - echo sprintf('<option value="%d">%s</option>', $id, $name); + echo sprintf('<option value="%d">%s</option>',$id,Format::htmlchars($name)); } ?> <option value="0" data-quick-add>— <?php echo __('Add New');?> —</option> @@ -388,8 +390,10 @@ foreach ($staff->teams as $TM) { <input type="hidden" data-name="teams[]" value="" /> </td> <td> - <input type="checkbox" data-name="team_alerts" value="1" /> - <?php echo __('Alerts'); ?> + <label> + <input type="checkbox" data-name="team_alerts" value="1" /> + <?php echo __('Alerts'); ?> + </label> <a href="#" class="pull-right drop-membership" title="<?php echo __('Delete'); ?>"><i class="icon-trash"></i></a> </td> diff --git a/include/staff/team.inc.php b/include/staff/team.inc.php index 93719667251ffef9ff2ac99ea041907eb62a1add..c055f8743711d05bee1bbf6e842d199ee2ee9659 100644 --- a/include/staff/team.inc.php +++ b/include/staff/team.inc.php @@ -6,8 +6,6 @@ if ($team && $_REQUEST['a']!='add') { $title=__('Update Team'); $action='update'; $submit_text=__('Save Changes'); - $info=$team->getInfo(); - $info['id']=$team->getId(); $trans['name'] = $team->getTranslateTag('name'); $members = $team->getMembers(); $qs += array('id' => $team->getId()); @@ -15,18 +13,21 @@ if ($team && $_REQUEST['a']!='add') { $title=__('Add New Team'); $action='create'; $submit_text=__('Create Team'); - $info['isenabled']=1; - $info['noalerts']=0; + if (!$team) { + $team = Team::create(array( + 'flags' => Team::FLAG_ENABLED, + )); + } $qs += array('a' => $_REQUEST['a']); } -$info = Format::htmlchars(($errors && $_POST) ? $_POST : $info); +$info = $team->getInfo(); ?> <form action="teams.php?<?php echo Http::build_query($qs); ?>" method="post" id="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> - <input type="hidden" name="id" value="<?php echo $info['id']; ?>"> + <input type="hidden" name="id" value="<?php echo $team->getId(); ?>"> <h2><?php echo __('Team');?> <i class="help-tip icon-question-sign" href="#teams"></i> </h2> @@ -34,13 +35,10 @@ $info = Format::htmlchars(($errors && $_POST) ? $_POST : $info); <ul class="clean tabs"> <li class="active"><a href="#team"> <i class="icon-file"></i> <?php echo __('Team'); ?></a></li> - <?php - if ($members) { ?> <li><a href="#members"> <i class="icon-group"></i> <?php echo __('Members'); ?></a></li> - <?php - } ?> </ul> + <div id="team" class="tab_content"> <table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2"> <thead> @@ -57,7 +55,7 @@ $info = Format::htmlchars(($errors && $_POST) ? $_POST : $info); <?php echo __('Name');?>: </td> <td> - <input type="text" size="30" name="name" value="<?php echo $info['name']; ?>" + <input type="text" size="30" name="name" value="<?php echo Format::htmlchars($team->name); ?>" autofocus data-translate-tag="<?php echo $trans['name']; ?>"/> <span class="error">* <?php echo $errors['name']; ?></span> </td> @@ -68,9 +66,9 @@ $info = Format::htmlchars(($errors && $_POST) ? $_POST : $info); </td> <td> <span> - <input type="radio" name="isenabled" value="1" <?php echo $info['isenabled']?'checked="checked"':''; ?>><strong><?php echo __('Active');?></strong> + <input type="radio" name="isenabled" value="1" <?php echo $team->isEnabled()?'checked="checked"':''; ?>><strong><?php echo __('Active');?></strong> - <input type="radio" name="isenabled" value="0" <?php echo !$info['isenabled']?'checked="checked"':''; ?>><?php echo __('Disabled');?> + <input type="radio" name="isenabled" value="0" <?php echo !$team->isEnabled()?'checked="checked"':''; ?>><?php echo __('Disabled');?> <span class="error">* </span> <i class="help-tip icon-question-sign" href="#status"></i> </span> @@ -87,7 +85,7 @@ $info = Format::htmlchars(($errors && $_POST) ? $_POST : $info); <?php if ($members) { foreach($members as $k=>$staff){ - $selected=($info['lead_id'] && $staff->getId()==$info['lead_id'])?'selected="selected"':''; + $selected=($team->lead_id && $staff->getId()==$team->lead_id)?'selected="selected"':''; echo sprintf('<option value="%d" %s>%s</option>',$staff->getId(),$selected,$staff->getName()); } } @@ -103,7 +101,7 @@ $info = Format::htmlchars(($errors && $_POST) ? $_POST : $info); <?php echo __('Assignment Alert');?>: </td> <td> - <input type="checkbox" name="noalerts" value="1" <?php echo $info['noalerts']?'checked="checked"':''; ?> > + <input type="checkbox" name="noalerts" value="1" <?php echo !$team->alertsEnabled()?'checked="checked"':''; ?> > <?php echo __('<strong>Disable</strong> for this Team'); ?> <i class="help-tip icon-question-sign" href="#assignment_alert"></i> </td> @@ -116,44 +114,119 @@ $info = Format::htmlchars(($errors && $_POST) ? $_POST : $info); <tr> <td colspan=2> <textarea class="richtext no-bar" name="notes" cols="21" - rows="8" style="width: 80%;"><?php echo $info['notes']; ?></textarea> + rows="8" style="width: 80%;"><?php echo Format::htmlchars($team->notes); ?></textarea> </td> </tr> </tbody> </table> </div> + <?php -if ($members) { ?> +$agents = Staff::getStaffMembers(); +foreach ($members as $m) + unset($agents[$m->staff_id]); +?> + <div id="members" class="tab_content" style="display:none"> - <table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2"> - <thead> - <tr> - <th> - <em><?php echo __('Agents who are members of this team'); ?><i - class="help-tip icon-question-sign" href="#members"></i></em> - </th> + <table class="two-column table" width="100%"> + <tbody> + <tr class="header"> + <td colspan="2"> + <?php echo __('Team Members'); ?> + <div><small> + <?php echo __('Agents who are members of this team'); ?> + <i class="help-tip icon-question-sign" href="#members"></i> + </small></div> + </td> </tr> - </thead> + <tr id="add_member"> + <td colspan="2"> + <i class="icon-plus-sign"></i> + <select id="add_access" data-quick-add="staff"> + <option value="0">— <?php echo __('Select Agent');?> —</option> + <?php + foreach ($agents as $id=>$name) { + echo sprintf('<option value="%d">%s</option>',$id,Format::htmlchars($name)); + } + ?> + <option value="0" data-quick-add>— <?php echo __('Add New');?> —</option> + </select> + <button type="button" class="action-button"> + <?php echo __('Add'); ?> + </button> + </td> + </tr> + </tbody> <tbody> - <?php - foreach($members as $k=>$staff) { - echo sprintf('<tr><td colspan=2><span style="width:350px;padding-left:5px; display:block;" class="pull-left"> - <b><a href="staff.php?id=%d">%s</a></span></b> - <input type="checkbox" name="remove[]" value="%d"><i>'.__('Remove').'</i></td></tr>', - $staff->getId() , - $staff->getName(), - $staff->getId()); - - } - ?> + <tr id="member_template" class="hidden"> + <td> + <input type="hidden" data-name="members[]" value="" /> + </td> + <td> + <label> + <input type="checkbox" data-name="member_alerts" value="1" /> + <?php echo __('Alerts'); ?> + </label> + <a href="#" class="pull-right drop-membership" title="<?php echo __('Delete'); + ?>"><i class="icon-trash"></i></a> + </td> + </tr> </tbody> </table> </div> -<?php -} ?> + <p style="text-align:center"> <input type="submit" name="submit" value="<?php echo $submit_text; ?>"> <input type="reset" name="reset" value="<?php echo __('Reset');?>"> <input type="button" name="cancel" value="<?php echo __('Cancel');?>" onclick='window.location.href="?"'> </p> </form> + +<script type="text/javascript"> +var addMember = function(staffid, name, alerts, error) { + var copy = $('#member_template').clone(); + + copy.find('[data-name=members\\[\\]]') + .attr('name', 'members[]') + .val(staffid); + copy.find('[data-name^=member_alerts]') + .attr('name', 'member_alerts['+staffid+']') + .prop('checked', alerts); + copy.find('td:first').append(document.createTextNode(name)); + copy.attr('id', '').show().insertBefore($('#add_member')); + copy.removeClass('hidden') + if (error) + $('<div class="error">').text(error).appendTo(copy.find('td:last')); +}; + +$('#add_member').find('button').on('click', function() { + var selected = $('#add_access').find(':selected'); + addMember(selected.val(), selected.text(), true); + selected.remove(); + return false; +}); + +$(document).on('click', 'a.drop-membership', function() { + var tr = $(this).closest('tr'); + $('#add_access').append( + $('<option>') + .attr('value', tr.find('input[name^=members][type=hidden]').val()) + .text(tr.find('td:first').text()) + ); + tr.fadeOut(function() { $(this).remove(); }); + return false; +}); + +<?php +if ($team) { + foreach ($team->members->sort(function($a) { return $a->staff->getName(); }) as $member) { + echo sprintf('addMember(%d, %s, %d, %s);', + $member->staff_id, + JsonDataEncoder::encode((string) $member->staff->getName()), + $member->isAlertsEnabled(), + JsonDataEncoder::encode($errors['members'][$member->staff_id]) + ); + } +} +?> +</script> diff --git a/scp/teams.php b/scp/teams.php index 28e72adbf8e850a6c59a3f85b846514b2222f2d2..791181487cd471b8687b39b4a704a4bdd2e60630 100644 --- a/scp/teams.php +++ b/scp/teams.php @@ -33,8 +33,8 @@ if($_POST){ } break; case 'create': - $_team = Team::create(); - if (($_team->update($_POST, $errors))){ + $team = Team::create(); + if (($team->update($_POST, $errors))){ $msg=sprintf(__('Successfully added %s'),Format::htmlchars($_POST['team'])); $_REQUEST['a']=null; }elseif(!$errors['err']){