diff --git a/include/ajax.admin.php b/include/ajax.admin.php index 0e2f18f67bb430792874aa22a798dab5e7fad7fe..c9f82a5c40e1dd8ed6dd19df1f052144952e3cf2 100644 --- a/include/ajax.admin.php +++ b/include/ajax.admin.php @@ -1,6 +1,7 @@ <?php require_once(INCLUDE_DIR . 'class.dept.php'); +require_once(INCLUDE_DIR . 'class.role.php'); require_once(INCLUDE_DIR . 'class.team.php'); class AdminAjaxAPI extends AjaxController { @@ -98,4 +99,61 @@ class AdminAjaxAPI extends AjaxController { include STAFFINC_DIR . 'templates/quick-add.tmpl.php'; } + + /** + * Ajax: GET /admin/add/role + * + * Uses a dialog to add a new role + * + * Returns: + * 200 - HTML form for addition + * 201 - {id: <id>, name: <name>} + * + * Throws: + * 403 - Not logged in + * 403 - Not an adminitrator + */ + function addRole() { + global $ost, $thisstaff; + + if (!$thisstaff) + Http::response(403, 'Agent login required'); + if (!$thisstaff->isAdmin()) + Http::response(403, 'Access denied'); + + $form = new RoleQuickAddForm($_POST); + + if ($_POST && $form->isValid()) { + $role = Role::create(); + $errors = array(); + $vars = $form->getClean(); + if ($role->update($vars, $errors)) { + Http::response(201, $this->encode(array( + 'id' => $role->getId(), + 'name' => $role->name, + ), 'application/json')); + } + foreach ($errors as $name=>$desc) + if ($F = $form->getField($name)) + $F->addError($desc); + } + + $title = __("Add New Role"); + $path = ltrim($ost->get_path_info(), '/'); + + include STAFFINC_DIR . 'templates/quick-add-role.tmpl.php'; + } + + function getRolePerms($id) { + global $ost, $thisstaff; + + if (!$thisstaff) + Http::response(403, 'Agent login required'); + if (!$thisstaff->isAdmin()) + Http::response(403, 'Access denied'); + if (!($role = Role::lookup($id))) + Http::response(404, 'No such role'); + + return $this->encode($role->getPermissionInfo()); + } } diff --git a/include/class.forms.php b/include/class.forms.php index c0fbd9040f3a296af2d3c0e3341e2e810f887543..20fed8cd6dcb56fe5e0027dd5e546d5d9eb21192 100644 --- a/include/class.forms.php +++ b/include/class.forms.php @@ -2932,9 +2932,10 @@ class ChoicesWidget extends Widget { <select name="<?php echo $this->name; ?>[]" <?php echo implode(' ', array_filter(array($classes))); ?> id="<?php echo $this->id; ?>" - <?php foreach ($config['data'] as $D=>$V) { - echo ' data-'.$D.'="'.Format::htmlchars($V).'"'; - } ?> + <?php if (isset($config['data'])) + foreach ($config['data'] as $D=>$V) + echo ' data-'.$D.'="'.Format::htmlchars($V).'"'; + ?> data-placeholder="<?php echo $prompt; ?>" <?php if ($config['multiselect']) echo ' multiple="multiple"'; ?>> @@ -3026,6 +3027,115 @@ class ChoicesWidget extends Widget { } } +/** + * A widget for the ChoiceField which will render a list of radio boxes or + * checkboxes depending on the value of $config['multiple']. Complex choices + * are also supported and will be rendered as divs. + */ +class BoxChoicesWidget extends Widget { + function render($options=array()) { + $this->emitChoices($this->field->getChoices()); + } + + function emitChoices($choices) { + if (!isset($this->value)) + $this->value = $this->field->get('default'); + $config = $this->field->getConfiguration(); + $type = $config['multiple'] ? 'checkbox' : 'radio'; + if (isset($config['classes'])) + $classes = 'class="'.$config['classes'].'"'; + + $i=0; + foreach ($choices as $k => $v) { + if (is_array($v)) { + $this->renderSectionBreak($k); + $this->emitChoices($v); + continue; + } + $id = sprintf("%s-%s", $this->id, $i++); +?> + <label <?php echo $classes; ?> + for="<?php echo $id; ?>" style="display:block;"> + <input id="<?php echo $id; ?>" style="vertical-align:top;" + type="<?php echo $type; ?>" name="<?php echo $this->name; ?>[]" <?php + if ($this->value[$k]) echo 'checked="checked"'; ?> value="<?php + echo Format::htmlchars($k); ?>"/> + <?php + if ($v) { ?> + <em style="display:inline-block;vertical-align:middle;width:90%;width:calc(100% - 25px);"><?php + echo Format::viewableImages($v); ?></em> +<?php } ?> + </label> +<?php } + } + + function renderSectionBreak($label) { ?> + <div><?php echo Format::htmlchars($label); ?></div> +<?php + } + + function getValue() { + $data = $this->field->getSource(); + if (count($data)) { + if (!isset($data[$this->name])) + return array(); + return $this->collectValues($data[$this->name], $this->field->getChoices()); + } + return parent::getValue(); + } + + function collectValues($data, $choices) { + $value = array(); + foreach ($choices as $k => $v) { + if (is_array($v)) + $value = array_merge($value, $this->collectValues($data, $v)); + elseif (@in_array($k, $data)) + $value[$k] = $v; + } + return $value; + } +} + +/** + * An extension to the BoxChoicesWidget which will render complex choices in + * tabs. + */ +class TabbedBoxChoicesWidget extends BoxChoicesWidget { + function render($options=array()) { + $tabs = array(); + foreach ($this->field->getChoices() as $label=>$group) { + if (is_array($group)) { + $tabs[$label] = $group; + } + else { + $this->emitChoices(array($label=>$group)); + } + } + if ($tabs) { + ?> + <div> + <ul class="alt tabs"> +<?php $i = 0; + foreach ($tabs as $label => $group) { + $active = $i++ == 0; ?> + <li <?php if ($active) echo 'class="active"'; + ?>><a href="#<?php echo sprintf('%s-%s', $this->name, Format::slugify($label)); + ?>"><?php echo Format::htmlchars($label); ?></a></li> +<?php } ?> + </ul> +<?php $i = 0; + foreach ($tabs as $label => $group) { + $first = $i++ == 0; ?> + <div class="tab_content <?php if (!$first) echo 'hidden'; ?>" id="<?php + echo sprintf('%s-%s', $this->name, Format::slugify($label));?>"> +<?php $this->emitChoices($group); ?> + </div> +<?php } ?> + </div> +<?php } + } +} + class CheckboxWidget extends Widget { function __construct($field) { parent::__construct($field); diff --git a/include/class.orm.php b/include/class.orm.php index 6fbdf0ae45b12fdc5b7bd4dd0d787f71b3f58893..5fc2362077d0dc6163ccf9275f6beaf1ba2baf47 100644 --- a/include/class.orm.php +++ b/include/class.orm.php @@ -2167,14 +2167,14 @@ class MySqlCompiler extends SqlCompiler { * (string) token to be placed into the compiled SQL statement. This * is a colon followed by a number */ - function input($what, $slot=false) { + function input($what, $slot=false, $model=false) { if ($what instanceof QuerySet) { $q = $what->getQuery(array('nosort'=>true)); $this->params = array_merge($this->params, $q->params); return '('.$q->sql.')'; } elseif ($what instanceof SqlFunction) { - return $what->toSql($this); + return $what->toSql($this, $model); } elseif (!isset($what)) { return 'NULL'; @@ -2462,7 +2462,7 @@ class MySqlCompiler extends SqlCompiler { $table = $model::getMeta('table'); $set = array(); foreach ($what as $field=>$value) - $set[] = sprintf('%s = %s', $this->quote($field), $this->input($value)); + $set[] = sprintf('%s = %s', $this->quote($field), $this->input($value, false, $model)); $set = implode(', ', $set); list($where, $having) = $this->getWhereHavingClause($queryset); $joins = $this->getJoins($queryset); diff --git a/include/class.role.php b/include/class.role.php index 5f73d8d5684b23584d7d2a949424a3cc049b1191..6e9c42a01870f832f4390c9b67b0dbd5e036a3fd 100644 --- a/include/class.role.php +++ b/include/class.role.php @@ -185,7 +185,7 @@ class Role extends RoleModel { return false; // Remove dept access entries - GroupDeptAccess::objects() + StaffDeptAccess::objects() ->filter(array('role_id'=>$this->getId())) ->update(array('role_id' => 0)); @@ -321,4 +321,57 @@ class RolePermission { } } } -?> + +class RoleQuickAddForm +extends AbstractForm { + function buildFields() { + $permissions = array(); + foreach (RolePermission::allPermissions() as $g => $perms) { + foreach ($perms as $k => $v) { + if ($v['primary']) + continue; + $permissions[$g][$k] = "{$v['title']} — {$v['desc']}"; + } + } + return array( + 'name' => new TextboxField(array( + 'required' => true, + 'configuration' => array( + 'placeholder' => __('Name'), + 'classes' => 'span12', + 'autofocus' => true, + 'length' => 128, + ), + )), + 'clone' => new ChoiceField(array( + 'default' => 0, + 'choices' => array_merge( + array(0 => '— '.__('Clone an existing role').' —'), + Role::getRoles() + ), + 'configuration' => array( + 'classes' => 'span12', + ), + )), + 'perms' => new ChoiceField(array( + 'choices' => $permissions, + 'widget' => 'TabbedBoxChoicesWidget', + 'configuration' => array( + 'multiple' => true, + 'classes' => 'vertical-pad', + ), + )), + ); + } + + function getClean() { + $clean = parent::getClean(); + // Index permissions as ['ticket.edit' => 1] + $clean['perms'] = array_keys($clean['perms']); + return $clean; + } + + function render($staff=true) { + return parent::render($staff, false, array('template' => 'dynamic-form-simple.tmpl.php')); + } +} diff --git a/include/staff/staff.inc.php b/include/staff/staff.inc.php index 533eb4f935102ee2072b1d999a4f7b1f94b75fac..7ff2ac4e05bbae231670872c51a022ee1d4a6516 100644 --- a/include/staff/staff.inc.php +++ b/include/staff/staff.inc.php @@ -217,7 +217,7 @@ if (count($bks) > 1) { <div class="error"><?php echo $errors['role_id']; ?></div> </td> <td> - <select name="role_id"> + <select name="role_id" data-quick-add="role"> <option value="0">— <?php echo __('Select Role');?> —</option> <?php foreach (Role::getRoles() as $id=>$name) { @@ -225,6 +225,7 @@ if (count($bks) > 1) { echo sprintf('<option value="%d" %s>%s</option>',$id,$sel,$name); } ?> + <option value="0" data-quick-add>— <?php echo __('Add New');?> —</option> </select> <i class="offset help-tip icon-question-sign" href="#primary_role"></i> </td> @@ -236,13 +237,14 @@ if (count($bks) > 1) { <input type="hidden" data-name="dept_access[]" value="" /> </td> <td> - <select data-name="dept_access_role"> + <select data-name="dept_access_role" data-quick-add="role"> <option value="0">— <?php echo __('Select Role');?> —</option> <?php foreach (Role::getRoles() as $id=>$name) { echo sprintf('<option value="%d" %s>%s</option>',$id,$sel,$name); } ?> + <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" /> diff --git a/include/staff/templates/quick-add-role.tmpl.php b/include/staff/templates/quick-add-role.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..929107b716468dcc302faa6e7705c8215f149d2f --- /dev/null +++ b/include/staff/templates/quick-add-role.tmpl.php @@ -0,0 +1,26 @@ +<?php +include 'quick-add.tmpl.php'; +$clone = $form->getField('clone')->getWidget()->name; +$permissions = $form->getField('perms')->getWidget()->name; +$name = $form->getField('name')->getWidget()->name; +?> +<script type="text/javascript"> + $('#_<?php echo $clone; ?>').change(function() { + var $this = $(this), + id = $this.val(), + form = $this.closest('form'), + name = $('[name="<?php echo $name; ?>"]:first', form); + $.ajax({ + url: 'ajax.php/admin/role/'+id+'/perms', + dataType: 'json', + success: function(json) { + $('[name="<?php echo $permissions; ?>[]"]', form).prop('checked', false); + $.each(json, function(k, v) { + form.find('[value="'+k+'"]', form).prop('checked', !!v); + }); + if (!name.val()) + name.val(__('Copy of {0}').replace('{0}', $this.find(':selected').text())); + } + }); + }); +</script> diff --git a/include/staff/templates/quick-add.tmpl.php b/include/staff/templates/quick-add.tmpl.php index fc5c01c66a6081c5c5cbd333194f305715f48de0..b8efa8dd8283dadefc58ede8be02c1f0cc79f134 100644 --- a/include/staff/templates/quick-add.tmpl.php +++ b/include/staff/templates/quick-add.tmpl.php @@ -2,6 +2,9 @@ <b><a class="close" href="#"><i class="icon-remove-circle"></i></a></b> <div class="clear"></div> <hr/> +<?php if (isset($errors['err'])) { ?> + <div id="msg_error" class="error-banner"><?php echo Format::htmlchars($errors['err']); ?></div> +<?php } ?> <form method="post" action="#<?php echo $path; ?>"> <div class="inset quick-add"> <?php $form->render(); ?> @@ -15,7 +18,7 @@ </span> <span class="buttons pull-right"> <input type="submit" value="<?php - echo $verb ?: __('Submit'); ?>" /> + echo $verb ?: __('Create'); ?>" /> </span> </p> <div class="clear"></div> diff --git a/scp/ajax.php b/scp/ajax.php index 58e153a6a921e8d74fcbc818b6dc25895e192007..72e68d429ae8cd633761be9e484fd4f1f84fc9e0 100644 --- a/scp/ajax.php +++ b/scp/ajax.php @@ -225,11 +225,13 @@ $dispatcher = patterns('', url_post('^translate/(?P<tag>\w+)$', 'updateTranslations'), url_get('^(?P<lang>[\w_]+)/(?P<tag>\w+)$', 'getLanguageFile') )), - url('^/admin', patterns('', + url('^/admin', patterns('ajax.admin.php:AdminAjaxAPI', url('^/quick-add', patterns('ajax.admin.php:AdminAjaxAPI', url('^/department$', 'addDepartment'), - url('^/team', 'addTeam') - )) + url('^/team$', 'addTeam'), + url('^/role$', 'addRole') + )), + url_get('^/role/(?P<id>\d+)/perms', 'getRolePerms') )), url('^/staff/(?P<id>\d+)', patterns('ajax.staff.php:StaffAjaxAPI', url('^/set-password$', 'setPassword') diff --git a/scp/css/scp.css b/scp/css/scp.css index 65c4b41f4fc467f5c3e782185f8c4bd4794c39ba..76f5468e4751863add285ee0267f3ce65a899930 100644 --- a/scp/css/scp.css +++ b/scp/css/scp.css @@ -2524,6 +2524,7 @@ form .inset { .form.footer { margin-top: 50px; } +<<<<<<< HEAD .clearfix:after { visibility: hidden; display: block; @@ -2532,3 +2533,6 @@ form .inset { clear: both; height: 0; } +.vertical-pad { + margin-top: 3px; +} diff --git a/scp/js/scp.js b/scp/js/scp.js index cbba7c8e21ff7777f96f5ac5877eb7a4bfce14b1..257e8f9330fe2c18409449bd768ad473a5a8859a 100644 --- a/scp/js/scp.js +++ b/scp/js/scp.js @@ -980,7 +980,7 @@ $(document).on('change', 'select[data-quick-add]', function() { $('<option>') .attr('value', id) .text(data.name) - .insertBefore(selected); + .insertBefore($('select[data-quick-add="'+type+'"] option[data-quick-add]')); $select.val(id); } });