parent
78ec9585b4
commit
ee59bb488c
|
@ -0,0 +1,6 @@ |
||||
<?php |
||||
require '../inc/global.inc.php'; |
||||
$skills = new Skill(); |
||||
//$all = $skills->get_all(false,false,null,0); |
||||
$all = $skills->get_skills_tree_json(); |
||||
echo $all; |
@ -0,0 +1,312 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
/** |
||||
* This tool allows platform admins to add skills by uploading a CSV or XML file |
||||
* @package chamilo.admin |
||||
* @documentation Some interesting basic skills can be found in the "Skills" section here: http://en.wikipedia.org/wiki/Personal_knowledge_management |
||||
*/ |
||||
/** |
||||
* Validate the imported data. |
||||
*/ |
||||
$language_file = array ('admin', 'registration'); |
||||
|
||||
$cidReset = true; |
||||
require '../inc/global.inc.php'; |
||||
require_once api_get_path(LIBRARY_PATH).'mail.lib.inc.php'; |
||||
require_once api_get_path(LIBRARY_PATH).'fileManage.lib.php'; |
||||
require_once api_get_path(LIBRARY_PATH).'classmanager.lib.php'; |
||||
require_once api_get_path(LIBRARY_PATH).'import.lib.php'; |
||||
|
||||
function validate_data($skills) { |
||||
$errors = array(); |
||||
$skills = array(); |
||||
// 1. Check if mandatory fields are set. |
||||
$mandatory_fields = array('id', 'parent_id', 'name'); |
||||
foreach ($skills as $index => $skill) { |
||||
foreach ($mandatory_fields as $field) { |
||||
if (empty($skill[$field])) { |
||||
$skill['error'] = get_lang(ucfirst($field).'Mandatory'); |
||||
$errors[] = $skill; |
||||
} |
||||
} |
||||
// 2. Check skill ID is not empty |
||||
if (!isset($skill['id']) || empty($skill['id'])) { |
||||
$skill['error'] = get_lang('SkillImportNoID'); |
||||
$errors[] = $skill; |
||||
} |
||||
// 3. Check skill Parent |
||||
if (!isset($skill['parent_id'])) { |
||||
$skill['error'] = get_lang('SkillImportNoParent'); |
||||
$errors[] = $skill; |
||||
} |
||||
// 4. Check skill Name |
||||
if (!isset($skill['name'])) { |
||||
$skill['error'] = get_lang('SkillImportNoName'); |
||||
$errors[] = $skill; |
||||
} |
||||
} |
||||
return $errors; |
||||
} |
||||
|
||||
/** |
||||
* Save the imported data |
||||
* @param array List of users |
||||
* @return void |
||||
* @uses global variable $inserted_in_course, which returns the list of courses the user was inserted in |
||||
*/ |
||||
function save_data($skills) { |
||||
if (is_array($skills)) { |
||||
$parents = array(); |
||||
foreach ($skills as $index => $skill) { |
||||
if (isset($parents[$skill['parent_id']])) { |
||||
$skill['parent_id'] = $parents[$skill['parent_id']]; |
||||
} else { |
||||
$skill['parent_id'] = 1; |
||||
} |
||||
$skill['a'] = 'add'; |
||||
$saved_id = $skill['id']; |
||||
$skill['id'] = null; |
||||
$oskill = new Skill(); |
||||
$skill_id = $oskill->add($skill); |
||||
$parents[$saved_id] = $skill_id; |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Read the CSV-file |
||||
* @param string $file Path to the CSV-file |
||||
* @return array All userinformation read from the file |
||||
*/ |
||||
function parse_csv_data($file) { |
||||
$skills = Import :: csv_to_array($file); |
||||
foreach ($skills as $index => $skill) { |
||||
$skills[$index] = $skill; |
||||
} |
||||
return $skills; |
||||
} |
||||
/** |
||||
* XML-parser: handle start of element |
||||
*/ |
||||
function element_start($parser, $data) { |
||||
$data = api_utf8_decode($data); |
||||
global $skill; |
||||
global $current_tag; |
||||
switch ($data) { |
||||
case 'Skill' : |
||||
$skill = array (); |
||||
break; |
||||
default : |
||||
$current_tag = $data; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* XML-parser: handle end of element |
||||
*/ |
||||
function element_end($parser, $data) { |
||||
$data = api_utf8_decode($data); |
||||
global $skill; |
||||
global $skills; |
||||
global $current_value; |
||||
switch ($data) { |
||||
case 'Skill' : |
||||
$skills[] = $skill; |
||||
break; |
||||
default : |
||||
$skill[$data] = $current_value; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* XML-parser: handle character data |
||||
*/ |
||||
function character_data($parser, $data) { |
||||
$data = trim(api_utf8_decode($data)); |
||||
global $current_value; |
||||
$current_value = $data; |
||||
} |
||||
|
||||
/** |
||||
* Read the XML-file |
||||
* @param string $file Path to the XML-file |
||||
* @return array All userinformation read from the file |
||||
*/ |
||||
function parse_xml_data($file) { |
||||
global $current_tag; |
||||
global $current_value; |
||||
global $skill; |
||||
global $skills; |
||||
$skills = array(); |
||||
$parser = xml_parser_create('UTF-8'); |
||||
xml_set_element_handler($parser, 'element_start', 'element_end'); |
||||
xml_set_character_data_handler($parser, 'character_data'); |
||||
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false); |
||||
xml_parse($parser, api_utf8_encode_xml(file_get_contents($file))); |
||||
xml_parser_free($parser); |
||||
return $skills; |
||||
} |
||||
|
||||
$this_section = SECTION_PLATFORM_ADMIN; |
||||
api_protect_admin_script(true); |
||||
|
||||
|
||||
//$tool_name = get_lang('ImportSkillsListXMLCSV'); |
||||
$tool_name = get_lang('ImportSkillsListCSV'); |
||||
$interbreadcrumb[] = array ("url" => 'index.php', "name" => get_lang('PlatformAdmin')); |
||||
|
||||
set_time_limit(0); |
||||
$extra_fields = UserManager::get_extra_fields(0, 0, 5, 'ASC', true); |
||||
$user_id_error = array(); |
||||
$error_message = ''; |
||||
|
||||
if ($_POST['formSent'] AND $_FILES['import_file']['size'] !== 0) { |
||||
$file_type = $_POST['file_type']; |
||||
Security::clear_token(); |
||||
$tok = Security::get_token(); |
||||
$allowed_file_mimetype = array('csv','xml'); |
||||
$error_kind_file = false; |
||||
|
||||
$ext_import_file = substr($_FILES['import_file']['name'],(strrpos($_FILES['import_file']['name'],'.')+1)); |
||||
|
||||
if (in_array($ext_import_file,$allowed_file_mimetype)) { |
||||
if (strcmp($file_type, 'csv') === 0 && $ext_import_file == $allowed_file_mimetype[0]) { |
||||
$skills = parse_csv_data($_FILES['import_file']['tmp_name']); |
||||
$errors = validate_data($skills); |
||||
$error_kind_file = false; |
||||
} elseif (strcmp($file_type, 'xml') === 0 && $ext_import_file == $allowed_file_mimetype[1]) { |
||||
$skills = parse_xml_data($_FILES['import_file']['tmp_name']); |
||||
$errors = validate_data($skills); |
||||
$error_kind_file = false; |
||||
} else { |
||||
$error_kind_file = true; |
||||
} |
||||
} else { |
||||
$error_kind_file = true; |
||||
} |
||||
|
||||
// List skill id whith error. |
||||
$skills_to_insert = $skill_id_error = array(); |
||||
if (is_array($errors)) { |
||||
foreach ($errors as $my_errors) { |
||||
$skill_id_error[] = $my_errors['SkillName']; |
||||
} |
||||
} |
||||
|
||||
if (is_array($skills)) { |
||||
foreach ($skills as $my_skill) { |
||||
if (!in_array($my_skill['SkillName'], $skill_id_error)) { |
||||
$skills_to_insert[] = $my_skill; |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (strcmp($file_type, 'csv') === 0) { |
||||
save_data($skills_to_insert); |
||||
} elseif (strcmp($file_type, 'xml') === 0) { |
||||
save_data($skills_to_insert); |
||||
} else { |
||||
$error_message = get_lang('YouMustImportAFileAccordingToSelectedOption'); |
||||
} |
||||
|
||||
if (count($errors) > 0) { |
||||
$see_message_import = get_lang('FileImportedJustSkillsThatAreNotRegistered'); |
||||
} else { |
||||
$see_message_import = get_lang('FileImported'); |
||||
} |
||||
|
||||
if (count($errors) != 0) { |
||||
$warning_message = '<ul>'; |
||||
foreach ($errors as $index => $error_skill) { |
||||
$warning_message .= '<li><b>'.$error_skill['error'].'</b>: '; |
||||
$warning_message .= '<strong>'.$error_skill['SkillName'].'</strong> ('.$error_skill['SkillName'].')'; |
||||
$warning_message .= '</li>'; |
||||
} |
||||
$warning_message .= '</ul>'; |
||||
} |
||||
|
||||
// if the warning message is too long then we display the warning message trough a session |
||||
if (api_strlen($warning_message) > 150) { |
||||
$_SESSION['session_message_import_skills'] = $warning_message; |
||||
$warning_message = 'session_message'; |
||||
} |
||||
|
||||
if ($error_kind_file) { |
||||
$error_message = get_lang('YouMustImportAFileAccordingToSelectedOption'); |
||||
} else { |
||||
//header('Location: '.api_get_path(WEB_CODE_PATH).'admin/skills_import.php?action=show_message&warn='.urlencode($warning_message).'&message='.urlencode($see_message_import).'&sec_token='.$tok); |
||||
//exit; |
||||
} |
||||
|
||||
} |
||||
Display :: display_header($tool_name); |
||||
|
||||
if (!empty($error_message)) { |
||||
Display::display_error_message($error_message); |
||||
} |
||||
if (!empty($see_message_import)) { |
||||
Display::display_normal_message($see_message_import); |
||||
} |
||||
|
||||
$form = new FormValidator('user_import','post','skills_import.php'); |
||||
$form->addElement('header', '', $tool_name); |
||||
$form->addElement('hidden', 'formSent'); |
||||
$form->addElement('file', 'import_file', get_lang('ImportFileLocation')); |
||||
$group = array(); |
||||
$group[] = $form->createElement('radio', 'file_type', '', 'CSV (<a href="skill_example.csv" target="_blank">'.get_lang('ExampleCSVFile').'</a>)', 'csv'); |
||||
//$group[] = $form->createElement('radio', 'file_type', null, 'XML (<a href="skill_example.xml" target="_blank">'.get_lang('ExampleXMLFile').'</a>)', 'xml'); |
||||
$form->addGroup($group, '', get_lang('FileType'), '<br/>'); |
||||
|
||||
$form->addElement('style_submit_button', 'submit', get_lang('Import'), 'class="save"'); |
||||
$defaults['formSent'] = 1; |
||||
$defaults['sendMail'] = 0; |
||||
$defaults['file_type'] = 'csv'; |
||||
$form->setDefaults($defaults); |
||||
$form->display(); |
||||
|
||||
$list = array(); |
||||
$list_reponse = array(); |
||||
$result_xml = ''; |
||||
$i = 0; |
||||
$count_fields = count($extra_fields); |
||||
if ($count_fields > 0) { |
||||
foreach ($extra_fields as $extra) { |
||||
$list[] = $extra[1]; |
||||
$list_reponse[] = 'xxx'; |
||||
$spaces = ' '; |
||||
$result_xml .= $spaces.'<'.$extra[1].'>xxx</'.$extra[1].'>'; |
||||
if ($i != $count_fields - 1) { |
||||
$result_xml .= '<br/>'; |
||||
} |
||||
$i++; |
||||
} |
||||
} |
||||
|
||||
?> |
||||
<p><?php echo get_lang('CSVMustLookLike').' ('.get_lang('MandatoryFields').')'; ?> :</p>
|
||||
|
||||
<blockquote> |
||||
<pre> |
||||
<b>id</b>;<b>parent_id</b>;<b>name</b>;<b>description</b><br /> |
||||
<b>n</b>;<b>m</b>;<b>xxx</b>;xxx<br /> |
||||
</pre> |
||||
</blockquote> |
||||
|
||||
<!--p><?php echo get_lang('XMLMustLookLike').' ('.get_lang('MandatoryFields').')'; ?> :</p>
|
||||
|
||||
<blockquote> |
||||
<pre> |
||||
<?xml version="1.0" encoding="<?php echo api_refine_encoding_id(api_get_system_encoding()); ?>"?>
|
||||
<Skills> |
||||
<Skill> |
||||
<b><id>n</id></b> |
||||
<b><parent_id>n</parent_id></b> |
||||
<b><name>xxx</name></b> |
||||
<description>xxx</description> |
||||
</Skill> |
||||
</Skills> |
||||
</pre> |
||||
</blockquote--> |
||||
<?php |
||||
Display :: display_footer(); |
@ -0,0 +1,46 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
/** |
||||
* @package chamilo.admin |
||||
*/ |
||||
|
||||
// Language files that need to be included. |
||||
$language_file = array('admin'); |
||||
$cidReset = true; |
||||
|
||||
require_once '../inc/global.inc.php'; |
||||
require_once api_get_path(LIBRARY_PATH).'skill.lib.php'; |
||||
|
||||
$this_section = SECTION_PLATFORM_ADMIN; |
||||
|
||||
api_protect_admin_script(); |
||||
|
||||
if (api_get_setting('allow_skills_tool') != 'true') { |
||||
api_not_allowed(); |
||||
} |
||||
|
||||
//Adds the JS needed to use the jqgrid |
||||
$htmlHeadXtra[] = api_get_js('d3.v2.min.js'); |
||||
|
||||
$skill = new Skill(); |
||||
$type = 'edit'; //edit |
||||
//$tree = $skill->get_skills_tree_json(null, true); |
||||
|
||||
//$html = $skill_visualizer->return_html(); |
||||
//$html = $skill_visualizer->return_html(); |
||||
//$url = api_get_path(WEB_AJAX_PATH).'skill.ajax.php?1=1'; |
||||
|
||||
$tpl = new Template(null, false, false); |
||||
|
||||
//$tpl->assign('url', $url); |
||||
//$tpl->assign('html', $html); |
||||
$tpl->assign('html', api_get_js('coffeewheel/wheel.js')); |
||||
//$tpl->assign('skill_visualizer', $skill_visualizer); |
||||
//$tpl->assign('js', $skill_visualizer->return_js()); |
||||
//$tpl->assign('js', api_get_js('coffeewheel/wheel.js')); |
||||
|
||||
// |
||||
$content = $tpl->fetch('default/skill/skill_wheel.tpl'); |
||||
$tpl->assign('content', $content); |
||||
$tpl->display_no_layout_template(); |
@ -0,0 +1,159 @@ |
||||
/* Define constants and size of the wheel */ |
||||
/** Total width of the wheel (also counts for the height) */ |
||||
var w = 800, |
||||
h = w, |
||||
r = w / 2, |
||||
/** x/y positionning of the center of the wheel */ |
||||
x = d3.scale.linear().range([0, 2 * Math.PI]), |
||||
y = d3.scale.pow().exponent(1.1).domain([0, 1]).range([0, r]), |
||||
/** Padding in pixels before th string starts */ |
||||
p = 3, |
||||
/** Duration of click animations */ |
||||
duration = 1000, |
||||
/** Levels to show */ |
||||
l = 3; |
||||
/* Locate the #div id element */ |
||||
var div = d3.select("#vis"); |
||||
/* Remove the image (make way for the dynamic stuff */ |
||||
div.select("img").remove(); |
||||
/* Append an element "svg" to the #vis section */ |
||||
var vis = div.append("svg") |
||||
.attr("width", w + p * 2) |
||||
.attr("height", h + p * 2) |
||||
.append("g") |
||||
.attr("transform", "translate(" + (r + p) + "," + (r/1.5 + p) + ")");
|
||||
/* ...update translate variables to change coordinates of wheel's center */ |
||||
/* Add a small label to help the user */ |
||||
div.append("p") |
||||
.attr("id", "intro") |
||||
.text("Click to zoom!"); |
||||
/* Generate the partition layout */ |
||||
var partition = d3.layout.partition() |
||||
.sort(null) |
||||
/** Value here seems to be a calculation of the size of the elements |
||||
depending on the level of depth they're at. Changing it makes |
||||
elements pass over the limits of others... */ |
||||
.value(function(d) { return 5.8 - d.depth; }); |
||||
/* Generate an arc which will define the whole wheel context */ |
||||
var arc = d3.svg.arc() |
||||
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); }) |
||||
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); }) |
||||
.innerRadius(function(d) { return Math.max(0, d.y ? y(d.y) : d.y); }) |
||||
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); }); |
||||
/* ...adding "+1" to "y" function's params is really funny */ |
||||
/* Exexute the calculation based on a JSON file provided */ |
||||
/*d3.json("wheel.json", function(json) {*/ |
||||
/** Get the JSON list of skills and work on it */ |
||||
d3.json("skills.json.php", function(json) { |
||||
/** Define the list of nodes based on the JSON */ |
||||
var nodes = partition.nodes({children: json}); |
||||
|
||||
var path = vis.selectAll("path").data(nodes); |
||||
path.enter().append("path") |
||||
.attr("id", function(d, i) { return "path-" + i; }) |
||||
.attr("d", arc) |
||||
.attr("fill-rule", "evenodd") |
||||
.style("fill", colour) |
||||
.on("click", click); |
||||
|
||||
var text = vis.selectAll("text").data(nodes); |
||||
var textEnter = text.enter().append("text") |
||||
.style("fill-opacity", 1) |
||||
.style("fill", function(d) { |
||||
return brightness(d3.rgb(colour(d))) < 125 ? "#eee" : "#000"; |
||||
}) |
||||
.attr("text-anchor", function(d) { |
||||
return x(d.x + d.dx / 2) > Math.PI ? "end" : "start"; |
||||
}) |
||||
.attr("dy", ".2em") |
||||
.attr("transform", function(d) { |
||||
/** Get the text details and define the rotation and general position */ |
||||
var multiline = (d.name || "").split(" ").length > 1, |
||||
angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90, |
||||
rotate = angle + (multiline ? -.5 : 0); |
||||
return "rotate(" + rotate + ")translate(" + (y(d.y) + p) + ")rotate(" + (angle > 90 ? -180 : 0) + ")"; |
||||
}) |
||||
.on("click", click); |
||||
/** Managing text - alway maximum two words */ |
||||
textEnter.append("tspan") |
||||
.attr("x", 0) |
||||
.text(function(d) { return d.depth ? d.name.split(" ")[0] : ""; }); |
||||
textEnter.append("tspan") |
||||
.attr("x", 0) |
||||
.attr("dy", "1em") |
||||
.text(function(d) { return d.depth ? d.name.split(" ")[1] || "" : ""; }); |
||||
|
||||
function click(d) { |
||||
path.transition() |
||||
.duration(duration) |
||||
.attrTween("d", arcTween(d)); |
||||
|
||||
// Somewhat of a hack as we rely on arcTween updating the scales.
|
||||
text |
||||
.style("visibility", function(e) { |
||||
return isParentOf(d, e) ? null : d3.select(this).style("visibility"); |
||||
}) |
||||
.transition().duration(duration) |
||||
.attrTween("text-anchor", function(d) { |
||||
return function() { |
||||
return x(d.x + d.dx / 2) > Math.PI ? "end" : "start"; |
||||
}; |
||||
}) |
||||
.attrTween("transform", function(d) { |
||||
var multiline = (d.name || "").split(" ").length > 1; |
||||
return function() { |
||||
var angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90, |
||||
rotate = angle + (multiline ? -.5 : 0); |
||||
return "rotate(" + rotate + ")translate(" + (y(d.y) + p) + ")rotate(" + (angle > 90 ? -180 : 0) + ")"; |
||||
}; |
||||
}) |
||||
.style("fill-opacity", function(e) { return isParentOf(d, e) ? 1 : 1e-6; }) |
||||
.each("end", function(e) { |
||||
d3.select(this).style("visibility", isParentOf(d, e) ? null : "hidden"); |
||||
}); |
||||
} |
||||
}); |
||||
/* Returns whether p is parent of c */ |
||||
function isParentOf(p, c) { |
||||
if (p === c) return true; |
||||
if (p.children) { |
||||
return p.children.some(function(d) { |
||||
return isParentOf(d, c); |
||||
}); |
||||
} |
||||
return false; |
||||
} |
||||
/* Generated random colour */ |
||||
function colour(d) { |
||||
if (d.children) { |
||||
// There is a maximum of two children!
|
||||
var colours = d.children.map(colour), |
||||
a = d3.hsl(colours[0]), |
||||
b = d3.hsl(colours[1]); |
||||
// L*a*b* might be better here...
|
||||
return d3.hsl((a.h + b.h) / 2, a.s * 1.2, a.l / 1.2); |
||||
} |
||||
return d.colour || "#fff"; |
||||
} |
||||
|
||||
/* Interpolate the scales! */ |
||||
function arcTween(d) { |
||||
var my = maxY(d), |
||||
xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]), |
||||
yd = d3.interpolate(y.domain(), [d.y, my]), |
||||
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, r]); |
||||
return function(d) { |
||||
return function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); return arc(d); }; |
||||
}; |
||||
} |
||||
/* */ |
||||
function maxY(d) { |
||||
return d.children ? Math.max.apply(Math, d.children.map(maxY)) : d.y + d.dy; |
||||
} |
||||
|
||||
/* Use a formula for contrasting colour |
||||
http://www.w3.org/WAI/ER/WD-AERT/#color-contrast
|
||||
*/ |
||||
function brightness(rgb) { |
||||
return rgb.r * .299 + rgb.g * .587 + rgb.b * .114; |
||||
} |
File diff suppressed because one or more lines are too long
@ -0,0 +1,50 @@ |
||||
<div id="menu" class="well" style="top:20px; left:20px; width:380px; z-index: 9000; opacity: 0.9;"> |
||||
<h3>{{'Skills'|get_lang}}</h3> |
||||
<div class="btn-group"> |
||||
<a style="z-index: 1000" class="btn" id="add_item_link" href="#">{{'AddSkill'|get_lang}}</a> |
||||
<a style="z-index: 1000" class="btn" id="return_to_root" href="#">{{'Root'|get_lang}}</a> |
||||
<a style="z-index: 1000" class="btn" id="return_to_admin" href="{{_p.web_main}}admin">{{'BackToAdmin'|get_lang}}</a> |
||||
|
||||
</div> |
||||
</div> |
||||
|
||||
<div id="vis"><img src=""></div> |
||||
{{ html }} |
||||
|
||||
<!--div id="dialog-form" style="display:none; z-index:9001;"> |
||||
<p class="validateTips"></p> |
||||
<form class="form-horizontal" id="add_item" name="form"> |
||||
<fieldset> |
||||
<input type="hidden" name="id" id="id"/> |
||||
<div class="control-group"> |
||||
<label class="control-label" for="name">{{'Name'|get_lang}}</label> |
||||
<div class="controls"> |
||||
<input type="text" name="name" id="name" size="40" /> |
||||
</div> |
||||
</div> |
||||
<div class="control-group"> |
||||
<label class="control-label" for="name">{{'Parent'|get_lang}}</label> |
||||
<div class="controls"> |
||||
<select id="parent_id" name="parent_id" /> |
||||
</select> |
||||
</div> |
||||
</div> |
||||
<div class="control-group"> |
||||
<label class="control-label" for="name">{{'Gradebook'|get_lang}}</label> |
||||
<div class="controls"> |
||||
<select id="gradebook_id" name="gradebook_id[]" multiple="multiple"/> |
||||
</select> |
||||
<span class="help-block"> |
||||
{{'WithCertificate'|get_lang}} |
||||
</span> |
||||
</div> |
||||
</div> |
||||
<div class="control-group"> |
||||
<label class="control-label" for="name">{{'Description'|get_lang}}</label> |
||||
<div class="controls"> |
||||
<textarea name="description" id="description" class="span3" rows="7"></textarea> |
||||
</div> |
||||
</div> |
||||
</fieldset> |
||||
</form> |
||||
</div--> |
Loading…
Reference in new issue