--- /dev/null
+<?php
+require_once 'Seth/Model/Filter.php';
+
+/**
+ * Controller for selecting/creating/editing a filter
+ *
+ * @author Kristian Kræmmer Nielsen <jkkn@jkkn.dk>
+ */
+class Seth_Controller_Filter {
+
+ private $_groupId = null;
+
+ public function setGroupId($id) {
+ $this->_groupId = intval($id);
+ }
+
+ private function _printSelect($id, $values, $selected=null) {
+ printf('<select id="%s" name="%s">', htmlentities($id), htmlentities($id));
+ foreach ($values as $key => $value) {
+ printf('<option value="%d"%s>%s</option>',
+ $key,
+ (!is_null($selected) && $selected==$key) ? ' selected="selected"' : '',
+ htmlentities($value));
+ }
+ echo '</select>';
+ }
+
+ /** Return all groups
+ */
+ public static function getGroups() {
+ $db = DB_PDO::get('seth');
+ $groups = $db->getHash('SELECT id, title FROM `group` ORDER BY title',
+ array(), 'id', 'title');
+ return $groups;
+ }
+
+ /** Return all filters for a given group
+ */
+ public static function getFilters($groupId, Seth_Model_Filter $exclude) {
+ $db = DB_PDO::get('seth');
+ $filters = $db->getHash('SELECT id, title FROM filter WHERE group_id=? ORDER BY title',
+ array($groupId), 'id', 'title');
+ $filters = array(''=>'') + $filters;
+ if ($exclude && !$exclude->isNew()) {
+ unset($filters[$exclude->getId()]);
+ }
+ return $filters;
+ }
+
+ public function printGroupSelect() {
+ $groups = self::getGroups();
+ echo '<form method="GET"/>';
+ $this->_printSelect('group_id', $groups, $this->_groupId);
+ echo '</form>' . "\n";
+ }
+
+ /**
+ * Method inserts "edit" links into I2_GUI_List for filters
+ */
+ static public function insertEditLinks(array &$row, Seth_Model_Filter $filter) {
+ $row['title'] = sprintf('<a href="javascript:parent.SethManager.addListAndCloseDialog(%d,\'%s\')">%s</a>',
+ $filter->getId(), rawurlencode($filter->getTitle()),
+ htmlentities($filter->getTitle()));
+ $row['action'] =
+ '<div style="width:100%; text-align:right">'.
+ sprintf('<a href="editfilter.php?id=%d">Redigér</a> | ',
+ $filter->getId()).
+ sprintf('<a href="#" onclick="SethManager.removeFilter(%d, \'%s\'); event.stopPropagation();">Slet</a>',
+ $filter->getId(), htmlentities(rawurlencode($filter->getTitle())))
+ .'</div>';
+ }
+
+ static private function _param($key) {
+ return isset($_POST[$key]) ? $_POST[$key] : null;
+ }
+
+ /**
+ * Delete filter
+ */
+ private static function _remove($id) {
+ $delete = new Seth_Model_Filter(intval($id));
+ if (!$delete->isNew()) {
+ $delete->delete();
+ }
+ return array('ok'=>true);
+ }
+
+ /**
+ * Process callbacks
+ */
+ static public function process() {
+ $action = self::_param('action');
+
+ $rtn = null;
+ switch ($action) {
+ case 'remove':
+ $rtn = self::_remove(self::_param('id'));
+ default:
+ break;
+ }
+
+ if (!is_null($rtn)) {
+ header('Content-Type: application/json');
+ echo json_encode($rtn);
+ die();
+ }
+ }
+
+}
+
+
+?>
--- /dev/null
+<?php
+/**
+ * Controller for the group list
+ *
+ * @author Kristian Kræmmer Nielsen <jkkn@tv2.dk>
+ */
+class Seth_Controller_Group {
+
+ // Insert actions into I2_GUI_List
+ static public function insertActions(array &$row, Seth_Model_Group $group) {
+ $row['actions'] =
+ '<div style="width:100%; text-align:right">'.
+ sprintf('<a href="#" onclick="SethGroup.rename(%d, \'%s\'); event.stopPropagation();">Omdøb</a> | ',
+ $group->getId(), htmlentities(rawurlencode($group->getTitle()))) .
+ sprintf('<a href="#" onclick="SethGroup.remove(%d, \'%s\'); event.stopPropagation();">Slet</a>',
+ $group->getId(), htmlentities(rawurlencode($group->getTitle()))) .
+ '</div>';
+ }
+
+ /* Create group */
+ private static function _new($title) {
+ $new = new Seth_Model_Group();
+ $new->setTitle($title);
+ $new->save();
+ return array('ok'=>true);
+ }
+
+ /* Rename group */
+ private static function _rename($id, $title) {
+ $rename = new Seth_Model_Group(intval($id));
+ if ($rename->isNew()) {
+ return array('err'=>'Group no longer exists');
+ }
+ $rename->setTitle($title);
+ $rename->save();
+ return array('ok'=>true);
+ }
+
+ /* Delete group */
+ private static function _remove($id) {
+ $delete = new Seth_Model_Group(intval($id));
+ if (!$delete->isNew()) {
+ $delete->delete();
+ }
+ return array('ok'=>true);
+ }
+
+
+ static private function _param($key) {
+ return isset($_POST[$key]) ? $_POST[$key] : null;
+ }
+
+ static public function process() {
+ $action = self::_param('action');
+
+ $rtn = null;
+ switch ($action) {
+ case 'new':
+ $rtn = self::_new(self::_param('title'));
+ break;
+ case 'rename':
+ $rtn = self::_rename(self::_param('id'), self::_param('title'));
+ break;
+ case 'remove':
+ $rtn = self::_remove(self::_param('id'));
+ default:
+ break;
+ }
+
+ if (!is_null($rtn)) {
+ header('Content-Type: application/json');
+ echo json_encode($rtn);
+ die();
+ }
+ }
+
+}
--- /dev/null
+<?php
+require_Once 'Seth/Model/Element.php';
+require_Once 'Seth/Model/Element/Descriptions.php';
+require_Once 'Seth/Model/Filter.php';
+require_Once 'Seth/Model/Element/FilterOrder.php';
+require_Once 'Seth/Model/View.php';
+
+/**
+ * Contains all list callback actions for Seth
+ *
+ * @author Kristian Kræmmer Nielsen <jkkn@tv2.dk>
+ */
+class Seth_Controller_List {
+
+ private $_viewId;
+
+ /**
+ * Initializes new List controller
+ *
+ * @param integer $view View we are displaying
+ */
+ public function __construct($viewId) {
+ $this->_viewId = $viewId;
+ }
+
+ /**
+ * Create new element
+ * @param $obj array('title'=>, 'filter_id'=>, 'group_defined_as'=>)
+ */
+ private function _newItem(array $obj) {
+ // create the new object
+ $elm = new Seth_Model_Element();
+ // fill in the entered fields from the interface
+ $elm->setTitle(utf8_decode($obj['title']));
+
+ /* We must also match the group parameters */
+ $gd = $obj['group_defined_as'];
+ if ($gd) {
+ foreach (unserialize(utf8_decode($gd)) as $key => $value) {
+ $elm->set($key, $value);
+ }
+ }
+
+ $filter = new Seth_Model_Filter(intval($obj['filter_id']));
+ if ($filter->isNew()) {
+ return array('err'=>'Filter no longer exists');
+ }
+
+ // Always set group id
+ $elm->setGroupId($filter->getGroupId());
+ $elm->save();
+
+ // Set order index to be at bottom at the beginning
+ $order = new Seth_Model_Element_FilterOrder($filter->getId(), $elm->getId());
+ $order->setOrderIndex(9223372036854775807); // max bigint
+ $order->save();
+
+ return array('new'=>true, 'list' => $this->_getList($filter));
+ }
+
+ /** Format a groupset title a bit nice */
+ static private function _formatGroupTitle($group) {
+ $title = '';
+ foreach ($group as $key => $value) {
+ if ($title != '') {
+ $title .= '; ';
+ }
+ // For now change field name by splitting _ and uppercasing first letters
+ $name = array();
+ foreach (split('_', $key) as $part) {
+ $name[] = ucfirst($part);
+ }
+ $name = join(' ', $name);
+ if (is_null($value)) {
+ $value = '(ikke sat)';
+ }
+ $title .= htmlentities(ucfirst($name)) . ': ' . htmlentities($value);
+ }
+ return utf8_encode($title);
+ }
+
+ /**
+ * Place here warning (NOT IMPLEMENTED)
+ */
+ static private function _formatPlaceHere($definition) {
+ return null;
+ }
+
+ /**
+ * Adds some information for mouseover
+ */
+ static private function _formatDescription(Seth_Model_Element $item,
+ $desc) {
+ // TODO: Implement a overlay for mouseover instead of <title/>:
+
+ $text = '';
+ if ($desc && $us=$desc->getUserStory()) {
+ $text .= $desc->getUserStory() . " \n\n";
+ }
+ $text .= 'Prioritet: ' . $item->getPriority() . " \n";
+ if ($cp=$item->getContactPerson()) {
+ $text .= 'Kontakt: ' . $cp . " \n";
+ }
+ if ($ar=$item->getArea()) {
+ $text .= 'Område: ' . $ar . " \n";
+ }
+ if ($as=$item->getAssignee()) {
+ $text .= 'Tildelt: ' . $as . " \n";
+ }
+
+ return $text;
+ }
+
+ /**
+ * Returns a group definition (including filter requirements)
+ *
+ * All = requirements from filter are copied, if other type
+ * exists we will only allow creating/drag if group overrides them
+ *
+ * This is used for new elements created in the group/dragged there
+ * @param array $group Group elements optional
+ * @return string Serializd array that defines group or null if not able to do so
+ */
+ static private function _getGroupDefinition(Seth_Model_filter $filter, array $group = array()) {
+ $define = array();
+ $conds = $filter->getFilterByIncludeInherited();
+ if ($conds) {
+ $conds = Seth_Model_Filter::cleanFilter($conds);
+ foreach ($conds as $cond) {
+ // all
+ if ($cond['cond'] == '=') {
+ $define[$cond['field']] = $cond['value'];
+ } elseif ($cond['cond'] == 'IS NULL') {
+ $define[$cond['field']] = null;
+ } else {
+ // must be in group
+ if (!isset($group[$cond['field']])) {
+ return null; // we cannot guess this value - no drag/create here
+ }
+ }
+ }
+ }
+ $define += $group; // add group by definition
+
+ $define['group_id'] = $filter->getGroupId();
+
+ return utf8_encode(serialize($define));
+ }
+
+ /** Returns a filtered list by id
+ * @param mixed $listid Id or Seth_Model_Filter object
+ */
+ static private function _getList($listid) {
+ // Start by finding the filter object
+ if (!$listid instanceof Seth_Model_Filter) {
+ $filter = new Seth_Model_Filter(intval($listid));
+ if ($filter->isNew()) {
+ // failed
+ return array('err'=>'Filter/view no longer exists');
+ }
+ } else {
+ $filter = $listid;
+ }
+
+ $items = Seth_Model_Element::find($filter->getQueryConditions());
+ $desc = Seth_Model_Element_Descriptions::multiple(
+ Object_Util::extract($items, 'id'), ORM::INDEX_BY_KEY);
+
+ $groupby = $filter->getGroupByIncludeInherited();
+
+ $groups = array();
+ $curGroup = null;
+
+ // If we have no elements or are not grouping, items will appear in a
+ // standard group:
+ $define = self::_getGroupDefinition($filter);
+ $current = array(
+ 'title' => utf8_encode(empty($conds) ? 'Alle elementer' : 'Filtrede elementer'),
+ 'when_placing_here' => self::_formatPlaceHere($define),
+ 'defined_as' => $define,
+ 'elements' => array());
+
+ // Construct our response by grouping elements of the same group
+ foreach ($items as $no => $item) {
+ if ($groupby) {
+ $thisGroup = array();
+ foreach ($groupby as $gby) {
+ $thisGroup[$gby] = $item->get($gby);
+ }
+
+ if ($thisGroup !== $curGroup) {
+ if (!is_null($curGroup)) $groups[] = $current;
+ $define = self::_getGroupDefinition($filter, $thisGroup);
+ $current = array(
+ 'title' => self::_formatGroupTitle($thisGroup),
+ 'when_placing_here' => self::_formatPlaceHere($define),
+ 'defined_as' => $define,
+ 'elements' => array());
+ $curGroup = $thisGroup;
+ }
+ }
+
+ $current['elements'][] = array(
+ 'title' => utf8_encode($item->getTitle()),
+ 'id' => $item->getId(),
+ 'description' => utf8_encode(self::_formatDescription($item,
+ isset($desc[$no]) ? $desc[$no] : null)));
+ }
+
+ $groups[] = $current; // always at least one empty group
+
+ return array('groups'=>$groups);
+ }
+
+ /**
+ * Stores new tab order (order of filters)
+ *
+ * @param integer $ViewId view_id
+ * @param array $order Ordered array of filter_ids
+ */
+ private function _setFilterOrder($viewId, $order) {
+ if (!is_array($order)) {
+ $order = array(); // empty view
+ }
+ $view = new Seth_Model_View($viewId);
+ if ($view->isNew()) {
+ // failed
+ return array('err'=>'Filter/view no longer exists');
+ }
+ $view->setFilterOrder($order);
+ $view->save();
+ return array('save'=>true);
+ }
+
+ /**
+ * Load filter order
+ *
+ * @param $viewId integer view id
+ * @return array of oredered filter ids as JSON
+ */
+ public function getFilterOrder(Seth_Model_View $view) {
+ // Get titles
+ $filters = ORM::multiple(Seth_Model_Filter::TYPE, $view->getFilterOrder());
+ $titles = array();
+ foreach ($filters as $filter) {
+ $titles[] = array('id' => $filter->getId(), 'title' => utf8_encode($filter->getTitle())); // JSON must be UTF-8
+ }
+ return json_encode($titles);
+ }
+
+ /**
+ * Move an element to another filtered list
+ *
+ * @param integer $elementId Element to move
+ * @param integer $filterId Id of filter to obey
+ */
+ static private function _moveToList($elementId, $filterId) {
+ $element = new Seth_Model_Element($elementId);
+ if ($element->isNew()) {
+ return array('err'=>'Element no longer exists');
+ }
+ $filter = new Seth_Model_Filter($filterId);
+ if ($filter->isNew()) {
+ return array('err'=>'Filter no longer exists');
+ }
+
+ // just copy all = rows from the filter - fail if there is non = conditions
+ $conds = $filter->getFilterByIncludeInherited();
+ if ($conds) {
+ $conds = Seth_Model_Filter::cleanFilter($conds);
+ foreach ($conds as $cond) {
+ if ($cond['cond'] != '=' && $cond['cond'] != 'IS NULL') {
+ return array('err'=>'Kan ikke flyttes her, filtret benytter andet end "=" krav!');
+ }
+ if ($cond['cond'] == 'IS NULL') {
+ $element->set($cond['field'], null);
+ } else {
+ $element->set($cond['field'], $cond['value']);
+ }
+ }
+ }
+
+ // always copy group id
+ $element->setGroupId($filter->getGroupId());
+
+ // save
+ $element->save();
+
+ return array('save'=>true);
+ }
+
+ /**
+ * Moves an element to another location and/or group
+ *
+ * @param array $obj Array of ('element_id'=>, 'above_id'=>, 'below_id'=>, 'group_defined_as'=>, 'filter_id'=>)
+ */
+ private function _moveElement(array $obj) {
+
+ // 1) First check if all elements are alive
+ $element = new Seth_Model_Element(intval($obj['element_id']));
+ if ($element->isNew()) {
+ return array('err'=>'Element no longer exists');
+ }
+
+ $filter = new Seth_Model_Filter(intval($obj['filter_id']));
+ if ($filter->isNew()) {
+ return array('err'=>'Filter no longer exists');
+ }
+
+ // 2) Check and find above/below objects
+ $above = $below = null;
+ if (!empty($obj['above_id'])) {
+ $above = new Seth_Model_Element(intval($obj['above_id']));
+ if ($above->isNew()) $above = null;
+ }
+
+ if (!empty($obj['below_id'])) {
+ $below = new Seth_Model_Element(intval($obj['below_id']));
+ if ($below->isNew()) $below = null;
+ }
+
+ /* 3) Move element into the right group */
+ $gd = $obj['group_defined_as'];
+ if ($gd) {
+ foreach (unserialize(utf8_decode($gd)) as $key => $value) {
+ $element->set($key, $value);
+ }
+ }
+ $element->save();
+
+ /** 3) The differcult part - try to place it between the two elements
+ * We only change the order_index in the filter many-to-many relation
+ */
+ Seth_Model_Element_FilterOrder::placeBetween(
+ $filter, $element, $above, $below);
+
+ return array('moved'=>true, 'list' => $this->_getList($filter));
+ }
+
+ /**
+ * Delete an element
+ *
+ * @param integer $id Element id
+ */
+ static private function _deleteElement($id) {
+ $element = new Seth_Model_Element($id);
+ if ($element->isNew()) {
+ return array('err'=>'Element var allerede slettet.');
+ }
+ $element->delete();
+ return array('delete'=>true);
+ }
+
+ private function _param($key) {
+ return isset($_POST[$key]) ? $_POST[$key] : null;
+ }
+
+ public function process() {
+ $action = $this->_param('action');
+
+ $rtn = null;
+ switch ($action) {
+ case 'new':
+ // create new
+ $rtn = $this->_newItem($this->_param('obj'));
+ break;
+ case 'get':
+ // get a filtered list
+ $rtn = array('list' => $this->_getList($this->_param('listid')));
+ break;
+ case 'setfilterorder':
+ // store new filter order in view
+ $rtn = $this->_setFilterOrder($this->_param('view_id'), $this->_param('order'));
+ break;
+ case 'move_to_list';
+ // moves item to another list
+ $rtn = $this->_moveToList($this->_param('element_id'), $this->_param('filter_id'));
+ break;
+ case 'move_element';
+ // moves item to another location and/or group
+ $rtn = $this->_moveElement($this->_param('obj'));
+ break;
+ case 'delete_element';
+ // moves item to another list
+ $rtn = $this->_deleteElement($this->_param('element_id'));
+ default:
+ break;
+ }
+
+ if (!is_null($rtn)) {
+ header('Content-Type: application/json');
+ echo json_encode($rtn);
+ die();
+ }
+ }
+
+}
--- /dev/null
+<?php
+
+/**
+ * Makes up the general header for a Seth page
+ * @author Kristian Kræmmer Nielsen <jkkn@tv2.dk>
+ */
+class Seth_Controller_Page {
+
+ static public function printTop($head='') {
+ $vendor = getSite('vendor');
+ echo <<<EOH
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Seth - projekt organizer</title>
+ <link type="text/css" href="http://$vendor/css/jquery-ui/sunny/jquery-ui.css" rel="stylesheet"/>
+ <script type="text/javascript" src="http://$vendor/js/jquery.js"></script>
+ <script type="text/javascript" src="http://$vendor/js/jquery-ui.js"></script>
+ <script type="text/javascript" src="js/seth.js"></script>
+ <link type="text/css" href="css/seth.css" rel="stylesheet"/>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
+ $head
+</head>
+<body>
+EOH;
+ }
+
+ static public function printBottom() {
+ echo <<<EOF
+ </body>
+ </html>
+EOF;
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Controller for the view list
+ *
+ * @author Kristian Kræmmer Nielsen <jkkn@tv2.dk>
+ */
+class Seth_Controller_View {
+
+ // Insert actions into I2_GUI_List
+ static public function insertActions(array &$row, Seth_Model_View $view) {
+ $row['actions'] =
+ '<div style="width:100%; text-align:right">'.
+ sprintf('<a href="#" onclick="SethView.rename(%d, \'%s\'); event.stopPropagation();">Omdøb</a> | ',
+ $view->getId(), htmlentities(rawurlencode($view->getTitle()))) .
+ sprintf('<a href="#" onclick="SethView.remove(%d, \'%s\'); event.stopPropagation();">Slet</a>',
+ $view->getId(), htmlentities(rawurlencode($view->getTitle()))) .
+ '</div>';
+ }
+
+ /* Create view */
+ private static function _new($title, $groupId) {
+ $new = new Seth_Model_View();
+ $new->setTitle($title);
+ $new->setGroupId(intval($groupId));
+ $new->save();
+ return array('ok'=>true);
+ }
+
+ /* Rename view */
+ private static function _rename($id, $title) {
+ $rename = new Seth_Model_View(intval($id));
+ if ($rename->isNew()) {
+ return array('err'=>'View no longer exists');
+ }
+ $rename->setTitle($title);
+ $rename->save();
+ return array('ok'=>true);
+ }
+
+ /* Delete view */
+ private static function _remove($id) {
+ $delete = new Seth_Model_View(intval($id));
+ if (!$delete->isNew()) {
+ $delete->delete();
+ }
+ return array('ok'=>true);
+ }
+
+
+ static private function _param($key) {
+ return isset($_POST[$key]) ? $_POST[$key] : null;
+ }
+
+ static public function process() {
+ $action = self::_param('action');
+
+ $rtn = null;
+ switch ($action) {
+ case 'new':
+ $rtn = self::_new(self::_param('title'), self::_param('group_id'));
+ break;
+ case 'rename':
+ $rtn = self::_rename(self::_param('id'), self::_param('title'));
+ break;
+ case 'remove':
+ $rtn = self::_remove(self::_param('id'));
+ default:
+ break;
+ }
+
+ if (!is_null($rtn)) {
+ header('Content-Type: application/json');
+ echo json_encode($rtn);
+ die();
+ }
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * This implements a select using three controls to add new part of
+ * a filter:
+ *
+ * [ field ] [ condition ] [ value ]
+ *
+ * @author Kristian Kræmmer Nielsen <jkkn@tv2.dk>
+ */
+class Seth_Element_Filter extends StraightForm_Element_Container {
+
+ private $_title = '';
+ private $_elmField;
+ private $_elmCondition;
+ private $_elmValue;
+
+ /**
+ * New filter element
+ * @param string $title Title displayed above
+ * @param array $fields Array of fields to select
+ * @param array $conds Array of possible conditions
+ */
+ public function __construct($title, array $fields, array $conds) {
+ $this->_title = $title;
+ $this->_elmField = StraightForm_BasicElements::select(null, $fields, key($fields));
+ $this->_elmCondition = StraightForm_BasicElements::select(null, $conds, key($conds));
+ $this->_elmValue = StraightForm_BasicElements::text(null, 30);
+ $this->_idField = $this->addElement($this->_elmField);
+ $this->_idCond = $this->addElement($this->_elmCondition);
+ $this->_idValue = $this->addElement($this->_elmValue);
+ }
+
+ public function getHTML($id, $label = true) {
+
+ if ($label && !empty($this->_title)) {
+ $html = sprintf('<label for="%s">%s%s</label>',
+ htmlentities($id),
+ htmlentities($this->_title),
+ !$this->isValid() ? ' <span>*</span>':'') . "\n<br/>\n";
+ } else {
+ $html = '';
+ }
+ $html .= self::getSubHTML($this->_idField, $id, false);
+ $html .= self::getSubHTML($this->_idCond, $id, false);
+ $html .= self::getSubHTML($this->_idValue, $id, false);
+ $html .= '<br/>';
+
+ return $html;
+ }
+
+ public function isValid() {
+ if (!parent::isValid()) {
+ return false;
+ }
+ /* Value must not be set if field has not been selected */
+ if (!$this->_elmField->getValue() && $this->_elmValue->getValue() != '') {
+ return false;
+ }
+ return true;
+ }
+
+ public function getValue() {
+ if ($this->_elmField->getValue()) {
+ return
+ array('field' => $this->_elmField->getValue(),
+ 'cond' => $this->_elmCondition->getValue(),
+ 'value' => $this->_elmValue->getValue());
+ } else {
+ return null;
+ }
+ }
+
+ public function setValue($value) {
+ if (!is_null($value)) {
+ $this->_elmField->setValue($value['field']);
+ $this->_elmCondition->setValue($value['cond']);
+ $this->_elmValue->setValue($value['value']);
+ } else {
+ $this->_elmField->setValue(0);
+ $this->_elmConditon->setValue(0);
+ $this->_elmValue->setValue('');
+ }
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * This represents an element in a list
+ *
+ * @author Kristian Kræmmer Nielsen <jkkn@jkkn.dk>
+ * @version $Revision$
+ * @access public
+ * @package Seth
+ */
+class Seth_Model_Element extends ORM_AbstractBase {
+
+ const TYPE = __CLASS__;
+
+ /**
+ * Create new and set defaults
+ */
+ public function __construct($element_id = null) {
+ parent::__construct($element_id);
+ if (is_null($element_id)) {
+ $this->_fields['priority'] = 100; // default priority
+ $this->_fields['created_at'] = date('c');
+ }
+ }
+
+ /**
+ * Configuration of the ORM object
+ *
+ * @param ORM_Configuration $config
+ */
+ public static function _ORMConfiguration(ORM_Configuration $config) {
+ $config->db = 'seth';
+ $config->table = 'element';
+ $config->autoIncrement = 'id';
+/* $config->type['deadline'] = ORM::TYPE_DATE;
+ $config->type['created_at'] = ORM::TYPE_DATE;
+ $config->type['completed_date'] = ORM::TYPE_DATE;*/
+ $config->createGet(array('id'));
+ $config->createGetSet(array('title', 'group_id', 'priority', 'area',
+ 'contact_person', 'estimate_design', 'estimate_development',
+ 'actual_design', 'actual_development', 'approved', 'design',
+ 'contact_person_email', 'completed', 'deleted', 'bug', 'deadline',
+ 'created_at', 'sprint', 'completed_date', 'po_time', 'site',
+ 'ressource', 'product_owner', 'assignee'));
+ }
+
+
+ /**
+ * Return array of fields that may be filtered
+ */
+ public static function getFilterFields() {
+ return
+ array('id', 'title', 'priority', 'area',
+ 'contact_person', 'estimate_design', 'estimate_development',
+ 'actual_design', 'actual_development', 'approved', 'design',
+ 'contact_person_email', 'completed', 'deleted', 'bug', 'deadline',
+ 'created_at', 'sprint', 'completed_date', 'po_time', 'site',
+ 'ressource', 'product_owner', 'assignee');
+ }
+
+ /**
+ * Allows to return a specific field
+ * @param string $field Name of field
+ * @return mixed value of field
+ */
+ public function get($field) {
+ return $this->_fields[$field];
+ }
+
+ /**
+ * Allows to set a specific field
+ * @param string $field Name of field
+ * @param mixed value of field
+ */
+ public function set($field, $value) {
+ $this->_fields[$field] = $value;
+ }
+
+ /**
+ * Shortcut to find Seth_Element objects from database
+ * @param mixed $cond Optional SQL condition as string or ORM_Condition object
+ * @param array $params If condition is provided as string, optional array of parameters
+ * @return array Array of Seth_Element objects
+ */
+ static function find($cond = null, $params = array()) {
+ return ORM::find(self::TYPE, $cond, $params);
+ }
+
+ /**
+ * Shortcut to count number of Seth_Element objects in database
+ * @param mixed $cond Optional SQL condition as string or ORM_Condition object
+ * @param array $params If condition is provided as string, optional array of parameters
+ * @return integer Number of objects in database
+ */
+ static function count($cond = null, $params = array()) {
+ return ORM::count(self::TYPE, $cond, $params);
+ }
+
+ /**
+ * Shortcut to see if a Seth_Element object exists in database
+ * @param mixed $cond Optional SQL condition as string or ORM_Condition object
+ * @param array $params If condition is provided as string, optional array of parameters
+ * @return boolean Returns true if at least one object exists, otherwise false
+ */
+ static function exists($cond = null, $params = array()) {
+ return ORM::exists(self::TYPE, $cond, $params);
+ }
+
+ /**
+ * Shortcut to instantiate multiple Seth_Element objects by primary key
+ * @param array $pks Array of primary keys
+ * @param integer $type One of the ORM ordering constants
+ * @return array Array of Seth_Element objects
+ */
+ static function multiple(array $pks, $type = ORM::ORDERED) {
+ return ORM::multiple(self::TYPE, $pks, $type);
+ }
+
+}
+
+?>
--- /dev/null
+<?php
+/**
+ * This class contains extra description fields for an element in the list.
+ * The difference is that these fields are not searchable/filterable by default
+ *
+ * @author Kristian Kræmmer Nielsen <jkkn@jkkn.dk>
+ * @version $Revision$
+ * @access public
+ * @package Seth_Element
+ */
+class Seth_Model_Element_Descriptions extends ORM_AbstractBase {
+
+ const TYPE = __CLASS__;
+
+ /**
+ * Constructor
+ *
+ * Creates a new Seth_Element_Descriptions object or loads a Seth_Element_Descriptions object by primary key
+ * WARNING: No auto_increment column found, you need to always set primary keys explicit!
+ *
+ * @param integer $element_id primary key
+ * @return object returns Seth_Element_Descriptions object
+ */
+ public function __construct($element_id = null) {
+ parent::__construct($element_id);
+ }
+
+ /**
+ * Configuration of the ORM object
+ *
+ * @param ORM_Configuration $config
+ */
+ public static function _ORMConfiguration(ORM_Configuration $config) {
+ $config->db = 'seth';
+ $config->table = 'element_descriptions';
+ $config->pk = array('element_id');
+ $config->createGet(array('element_id'));
+ $config->createGetSet(array('user_story', 'specifications',
+ 'description', 'url', 'material_in_shared_folder'));
+ }
+
+ /**
+ * Shortcut to find Seth_Element_Descriptions objects from database
+ * @param mixed $cond Optional SQL condition as string or ORM_Condition object
+ * @param array $params If condition is provided as string, optional array of parameters
+ * @return array Array of Seth_Element_Descriptions objects
+ */
+ static function find($cond = null, $params = array()) {
+ return ORM::find(self::TYPE, $cond, $params);
+ }
+
+ /**
+ * Shortcut to count number of Seth_Element_Descriptions objects in database
+ * @param mixed $cond Optional SQL condition as string or ORM_Condition object
+ * @param array $params If condition is provided as string, optional array of parameters
+ * @return integer Number of objects in database
+ */
+ static function count($cond = null, $params = array()) {
+ return ORM::count(self::TYPE, $cond, $params);
+ }
+
+ /**
+ * Shortcut to see if a Seth_Element_Descriptions object exists in database
+ * @param mixed $cond Optional SQL condition as string or ORM_Condition object
+ * @param array $params If condition is provided as string, optional array of parameters
+ * @return boolean Returns true if at least one object exists, otherwise false
+ */
+ static function exists($cond = null, $params = array()) {
+ return ORM::exists(self::TYPE, $cond, $params);
+ }
+
+ /**
+ * Shortcut to instantiate multiple Seth_Element_Descriptions objects by primary key
+ * @param array $pks Array of primary keys
+ * @param integer $type One of the ORM ordering constants
+ * @return array Array of Seth_Element_Descriptions objects
+ */
+ static function multiple(array $pks, $type = ORM::ORDERED) {
+ return ORM::multiple(self::TYPE, $pks, $type);
+ }
+
+}
+
+?>
--- /dev/null
+<?php
+require_once 'Seth/Model/Element.php';
+require_once 'Seth/Model/Filter.php';
+
+/**
+ * This class is used to keep a specific ordering with a given view of the elements
+ *
+ * @author Kristian Kræmmer Nielsen <jkkn@jkkn.dk>
+ * @version $Revision$
+ * @access public
+ * @package Seth_Model_Element
+ */
+class Seth_Model_Element_FilterOrder extends ORM_AbstractBase {
+
+ const TYPE = __CLASS__;
+
+ /**
+ * Constructor
+ *
+ * Creates a new Seth_Element_Filter_Order object or loads a Seth_Element_Filter_Order object by primary keys
+ * WARNING: No auto_increment column found, you need to always set primary keys explicit!
+ *
+ * @param integer $filter_id primary key
+ * @param integer $element_id primary key
+ * @return object returns Seth_Element_Filter_Order object
+ */
+ public function __construct($filter_id = null, $element_id = null) {
+ if (!is_null($filter_id) || !is_null($element_id)) {
+ parent::__construct(array($filter_id, $element_id));
+ } else {
+ parent::__construct();
+ }
+ }
+
+ /**
+ * Configuration of the ORM object
+ *
+ * @param ORM_Configuration $config
+ */
+ public static function _ORMConfiguration(ORM_Configuration $config) {
+ $config->db = 'seth';
+ $config->table = 'element_filter_order';
+ $config->pk = array('filter_id', 'element_id');
+ $config->createGet(array('filter_id', 'element_id'));
+ $config->createGetSet(array('order_index'));
+ }
+
+ /**
+ * Very naive way of placing an element between two elements
+ *
+ * We receive the entire ordering, then reorder all elements each time
+ * @param object $filter Filter displayed
+ * @param object $place Element to place
+ * @param object $above Element that should be above
+ * @param object $below Element that should be below
+ * TODO: Reimplement using a any other algorithm :-)
+ */
+ public static function placeBetween(Seth_Model_Filter $filter,
+ Seth_Model_Element $place, $above, $below) {
+ // Get all elements from list
+ $conds = $filter->getQueryConditions();
+ $elms = ORM::find(Seth_Model_Element::TYPE, $conds);
+ // Get current ordering
+ $ordering = ORM::find(self::TYPE, 'filter_id=?', $filter->getId());
+ // order by element
+ $orderByElm = array();
+ foreach ($ordering as $order) {
+ $orderByElm[$order->getElementId()] = $order;
+ }
+ $placeId = $place->getId();
+ $aboveId = !is_null($above) ? $above->getId() : null;
+ $belowId = !is_null($below) ? $below->getId() : null;
+
+ // reindex
+ $index = 0;
+ $newOrder = array();
+ $wasPlaced = false;
+ foreach ($elms as $elm) {
+ $id = $elm->getId();
+
+ if ($id == $placeId) {
+ continue; // we skip the element we are going to place
+ }
+
+ $index++;
+ if (isset($orderByElm[$id])) {
+ $newOrder[] = $order = $orderByElm[$id];
+ unset($orderByElm[$id]);
+ } else {
+ $newOrder[] = $order = new self($filter->getId(), $id);
+ }
+
+ // check to see if we should place our element above or below this one
+ if ($id == $belowId && !is_null($below) && !$wasPlaced) {
+ $placeIndex = ($index++);
+ $wasPlaced = true;
+ }
+
+ $order->setOrderIndex($index);
+
+ if ($id == $aboveId && !is_null($above) && !$wasPlaced) {
+ $placeIndex = (++$index);
+ $wasPlaced = true;
+ }
+ }
+
+ // Place item
+ if ($wasPlaced) {
+ if (isset($orderByElm[$placeId])) {
+ $newOrder[] = $order = $orderByElm[$placeId];
+ unset($orderByElm[$placeId]);
+ } else {
+ $newOrder[] = $order = new self($filter->getId(), $placeId);
+ }
+ $order->setOrderIndex($placeIndex);
+ }
+
+ // save all
+ ORM_Multiple::save($newOrder);
+ // delete unused
+ ORM_Multiple::delete($orderByElm);
+ }
+
+}
+
+?>
--- /dev/null
+<?php
+/**
+ *
+ * A Filter is used to create the nice difference lists used as tabs
+ * in Seth. It is just different "filters"/views on the same data
+ *
+ * @author Kristian Kræmmer Nielsen <jkkn@jkkn.dk>
+ * @version $Revision$
+ * @access public
+ * @package Seth
+ */
+class Seth_Model_Filter extends ORM_AbstractBase {
+
+ const TYPE = __CLASS__;
+
+ /**
+ * Configuration of the ORM object
+ *
+ * @param ORM_Configuration $config
+ */
+ public static function _ORMConfiguration(ORM_Configuration $config) {
+ $config->db = 'seth';
+ $config->table = 'filter';
+ $config->autoIncrement = 'id';
+ $config->createGet(array('id'));
+ $config->createGetSet(array('title', 'group_id', 'description', 'inherit_id'));
+ }
+
+ /**
+ * Returns possible conditions
+ */
+ public static function getPossibleConditions() {
+ return array('=', '!=', '<', '<=', '>' ,'>=', 'LIKE', 'ILIKE', 'IS NULL', 'IS NOT NULL');
+ }
+
+ /**
+ * Sets what to filter by
+ * @param array $rules Array of rules, of field, cond, value
+ */
+ public function setFilterBy(array $rules) {
+ if (!empty($rules)) {
+ $this->_fields['filter_by'] = serialize($rules);
+ } else {
+ $this->_fields['filter_by'] = null;
+ }
+ }
+
+ /**
+ * Gets what to filter by
+ * @return array Array of rules, of field, cond and value
+ */
+ public function getFilterBy() {
+ if (isset($this->_fields['filter_by']) && !is_null($this->_fields['filter_by'])) {
+ return unserialize($this->_fields['filter_by']);
+ }
+ return null;
+ }
+
+ /**
+ * Sets list of fields to group by
+ *
+ * @param array $fields List or fields to group by or null
+ */
+ public function setGroupBy(array $fields) {
+ $this->_fields['group_by'] =
+ empty($fields) ? null : implode(',', $fields);
+ }
+
+ /**
+ * Returns list of fields to group by
+ *
+ * @return array Array of fields to group by or null
+ */
+ public function getGroupBy() {
+ if (!isset($this->_fields['group_by']) || is_null($this->_fields['group_by'])) {
+ return null;
+ }
+ return explode(',', $this->_fields['group_by']);
+ }
+
+ /**
+ * Sets list of fields to order by
+ *
+ * @param array $fields List or fields to order by or null
+ */
+ public function setOrderBy(array $fields) {
+ $this->_fields['order_by'] =
+ empty($fields) ? null : implode(',', $fields);
+ }
+
+ /**
+ * Returns list of fields to order by
+ *
+ * @return array Array of fields to order by or null
+ */
+ public function getOrderBy() {
+ if (!isset($this->_fields['order_by']) || is_null($this->_fields['order_by'])) {
+ return null;
+ }
+ return explode(',', $this->_fields['order_by']);
+ }
+
+ /**
+ * Returns all filters, including inherited
+ */
+ public function getFilterByIncludeInherited() {
+ $parentFilters = null;
+ // We will start by including all the conditions we inherit
+ if ($this->getInheritId()) {
+ $inherit = new Seth_Model_Filter($this->getInheritId());
+ if (!$inherit->isNew()) {
+ $parentFilters = $inherit->getFilterByIncludeInherited();
+ }
+ }
+ $myFilters = $this->getFilterBy();
+ if (!is_null($myFilters) && !is_null($parentFilters)) {
+ $filters = array_merge($parentFilters, $myFilters);
+ } else {
+ $filters = (!is_null($myFilters)) ? $myFilters : $parentFilters;
+ }
+ return $filters;
+ }
+
+ /**
+ * Filter out filters for more specific rules
+ *
+ * If a filter has a = or IS NULL condition these override any other condition
+ * Useful when determining what to set to match a filter
+ */
+ public static function cleanFilter(array $all) {
+ $ignore = array();
+ $filters = array();
+ foreach ($all as $filter) {
+ if ($filter['cond'] == '=' || $filter['cond'] == 'IS NULL') {
+ $ignore[] = $filter['field'];
+ $filters[] = $filter;
+ }
+ }
+ // now add rest to list (that are not overridden)
+ foreach ($all as $filter) {
+ if (!in_array($filter['field'], $ignore)) {
+ $filters[] = $filter;
+ }
+ }
+ return $filters;
+ }
+
+ /**
+ * Returns all fields to group by
+ */
+ public function getGroupByIncludeInherited() {
+ $parentGB = null;
+ if ($this->getInheritId()) {
+ $inherit = new Seth_Model_Filter($this->getInheritId());
+ if (!$inherit->isNew()) {
+ $parentGB = $inherit->getGroupByIncludeInherited();
+ }
+ }
+ $myGB = $this->getGroupBy();
+ if (!is_null($myGB) && !is_null($parentGB)) {
+ $GB = array_merge($parentGB, $myGB);
+ } else {
+ $GB = (!is_null($myGB)) ? $myGB : $parentGB;
+ }
+ return $GB;
+ }
+
+
+
+ /**
+ * Returns a ORM_Condition object to query a list using this filter
+ * @param boolean $inherting include conditions for subfilters (that inherits this filter)
+ */
+ public function getQueryConditions($inheriting = false) {
+
+ $query = new ORM_Condition();
+
+ // We will start by all including all the conditions we inherit
+ if ($this->getInheritId()) {
+ $inherit = new Seth_Model_Filter($this->getInheritId());
+ if (!$inherit->isNew()) {
+ $query->merge($inherit->getQueryConditions(true));
+ }
+ }
+
+ // load filter options
+ $conds = $this->getFilterBy();
+ $orderby = $this->getOrderBy();
+ $groupby = $this->getGroupBy();
+
+ if ($conds) {
+ foreach ($conds as $cond) {
+ // Ignore value if IS NULL or IS NOT NULL
+ if ($cond['cond'] == 'IS NULL' || $cond['cond'] == 'IS NOT NULL') {
+ $query->add(sprintf('e.%s %s', $cond['field'], $cond['cond']));
+ } else {
+ $query->add(sprintf('e.%s %s ?', $cond['field'], $cond['cond']), $cond['value']);
+ }
+ }
+ }
+
+ // Construct our ordering of the elements
+ if ($groupby) {
+ foreach ($groupby as $order) {
+ $query->addOrderBy('e.'.$order);
+ }
+ }
+ if ($orderby) {
+ foreach ($orderby as $order) {
+ $query->addOrderBy('e.'.$order);
+ }
+ }
+
+ // Default conditions
+ if (!$inheriting) {
+ $query->add('e.group_id = ?', $this->getGroupId());
+ // Always left join and order by the filter's own ordering
+ $query->add('FROM element e '.
+ 'LEFT JOIN element_filter_order o ON e.id=o.element_id AND o.filter_id=? '.
+ 'WHERE e.id=element.id '.
+ 'ORDER BY o.order_index',
+ $this->getId());
+ }
+
+ return $query;
+ }
+
+}
+
+?>
--- /dev/null
+<?php
+/**
+ * This represents groups for seth, so difference groups of people can use
+ * the interface indepedent.
+ *
+ * @author Kristian Kræmmer Nielsen <jkkn@jkkn.dk>
+ * @version $Revision$
+ * @access public
+ * @package Seth
+ */
+class Seth_Model_Group extends ORM_AbstractBase {
+
+ const TYPE = __CLASS__;
+
+ /**
+ * Configuration of the ORM object
+ *
+ * @param ORM_Configuration $config
+ */
+ public static function _ORMConfiguration(ORM_Configuration $config) {
+ $config->db = 'seth';
+ $config->table = '`group`';
+ $config->autoIncrement = 'id';
+ $config->createGet(array('id'));
+ $config->createGetSet(array('title'));
+ }
+
+}
+
+?>
--- /dev/null
+<?php
+/**
+ *
+ * A view is a set of tabs (lists) displayed
+ *
+ * @author Kristian Kræmmer Nielsen <jkkn@jkkn.dk>
+ * @version $Revision$
+ * @access public
+ * @package Seth
+ */
+class Seth_Model_View extends ORM_AbstractBase {
+
+ const TYPE = __CLASS__;
+
+ /**
+ * Configuration of the ORM object
+ *
+ * @param ORM_Configuration $config
+ */
+ public static function _ORMConfiguration(ORM_Configuration $config) {
+ $config->db = 'seth';
+ $config->table = 'view';
+ $config->autoIncrement = 'id';
+ $config->createGet(array('id'));
+ $config->createGetSet(array('title', 'group_id'));
+ }
+
+ /**
+ * Sets list of filters to view
+ *
+ * @param array $fields List or fields to order by or null
+ */
+ public function setFilterOrder(array $filters) {
+ $this->_fields['filter_order'] =
+ implode(',', $filters);
+ }
+
+ /**
+ * Returns list of filters this view has
+ *
+ * @return array Array of filters in order
+ */
+ public function getFilterOrder() {
+ if (!isset($this->_fields['filter_order'])) {
+ return array();
+ }
+ return explode(',', $this->_fields['filter_order']);
+ }
+
+}
+?>
--- /dev/null
+<?php
+require_once 'Seth/Controller/Page.php';
+require_once 'Seth/Controller/Filter.php';
+// process callbacks
+Seth_Controller_Filter::process();
+
+I2_GUI_List::process();
+
+Seth_Controller_Page::printTop();
+
+$controller = new Seth_Controller_Filter();
+$groupId = 0;
+if (isset($_GET['group_id'])) {
+ $controller->setGroupId($groupId = intval($_GET['group_id']));
+} else {
+ echo 'Missing group_id';die();
+}
+
+echo <<<EOF
+<script type="text/javascript">
+$(function() {
+ SethManager.initAddFilter();
+});
+</script>
+EOF;
+
+// List of available groups to choose from
+echo '<h2>Gruppe:</h2>';
+$controller->printGroupSelect();
+
+echo '<h2>Vælg liste:</h2>';
+$list = new I2_GUI_List();
+$list->enableFeatures(I2_GUI_LIST_TOTALCOUNT);
+$list->setHeaders(array('id' => 'ID', 'title'=>'Navn på liste', 'action' => 'Muligheder'));
+$list->setLimit(25);
+$list->setDataORM(Seth_Model_Filter::TYPE, 'group_id=?', array($groupId));
+$list->addOrderBy('title');
+$list->setRowCallback('insertEditLinks', 'Seth_Controller_Filter');
+echo $list->getHTML();
+
+echo '<br/>';
+printf('<button onclick="location.href=\'editfilter.php?group_id=%d\'">Opret ny liste</button>', $groupId);
+
+
+Seth_Controller_Page::printBottom();
+
+?>
--- /dev/null
+html,body,#page, #tabs {
+ height: 100%;
+}
+#page, #tabs {
+ position: relative;
+ overflow: none;
+}
+
+h1 {
+ font: 22pt Arial;
+ font-weight: bold;
+ margin: 0 0 10px 0;
+ text-align: center;
+}
+
+#page {
+ width: 90%;
+ margin: 2pt auto 2pt;
+ padding: 4pt;
+
+ border: 1px solid gray;
+ -moz-box-shadow: 10px 10px 5px #888;
+ -moz-border-radius: 10px;
+}
+
+.table li {
+ list-style-type:none;
+}
+
+ul.table {
+ margin: 0;
+ padding-left: 0;
+ min-height: 15px; /* allow drag'n'drop back */
+}
+
+.new input[type=text] {
+ width: 80%;
+ margin: 0 5px;
+}
+
+div.group-seperator {
+ margin-top: 16px;
+ font-size: 14px;
+ font-weight: bold;
+}
+
+#tabs-list {
+ font-size: 12px;
+}
+
+#actions {
+ font: 11pt verdana bold;
+ text-align: right;
+ padding: 0 10px;
+}
+
+#actions a {
+ color: darkblue;
+ text-decoration: none;
+}
+
+#actions a:hover {
+ background-color: #FFFFB0;
+ text-decoration: underline overline;
+}
+
+.filtered_list {
+ position:relative;
+ height: 80%;
+ overflow: scroll;
+}
+
+.filtered_list a:hover {
+ background-color: #FFFF88;
+}
+
+/* Dialogs */
+
+#addlist, #editelement {
+ padding: 0;
+ margin: 0;
+ overflow: hidden;
+}
+
+#addlistiframe, #editiframe {
+ width: 100%;
+ height: 100%;
+ border: 0;
+}
+
--- /dev/null
+<?php
+require_once 'Seth/Controller/Page.php';
+require_once 'Seth/Controller/Filter.php';
+require_once 'Seth/Model/Element.php';
+require_once 'Seth/Model/Element/Descriptions.php';
+
+// Edit element
+$elementId = isset($_GET['id']) ? intval($_GET['id']) : 0;
+if (!$elementId) {
+ echo 'No element'; die();
+}
+
+$element = new Seth_Model_Element($elementId);
+if ($element->isNew()) {
+ echo 'Element no longer exists'; die();
+}
+
+$elemDescription = new Seth_Model_Element_Descriptions($elementId);
+
+// Fields directly mapped to table (ORM object):
+$ormControlElm = new StraightForm_ORMController($element,
+ array(
+ 'title' => 'Overskrift',
+ 'priority' => 'Prioritet (lavere tal er højest prioritet)',
+ 'area' => 'Område',
+ 'sprint' => 'Sprint',
+ 'contact_person_email' => 'Kontakt person (email)',
+ 'contact_person' => 'Kontakt person',
+ 'assignee' => 'Opgaven tilhører',
+ 'estimate_development' => 'Udviklings estimat',
+ 'estimate_design' => 'Design estimat',
+ 'actual_design' => 'Faktisk designtid',
+ 'actual_development' => 'Faktisk udviklingstid',
+ 'approved' => StraightForm_BasicElements::checkbox('Godkendt'),
+ 'design' => StraightForm_BasicElements::checkbox('Design klar'),
+ 'completed' => StraightForm_BasicElements::checkbox('Afsluttet'),
+ 'deleted' => StraightForm_BasicElements::checkbox('Slettet'),
+ 'bug' => StraightForm_BasicElements::checkbox('Bug/fejlrettelse'),
+ 'deadline' => 'Deadline',
+ 'created_at' => 'Oprettelsestidspunkt',
+ 'completed_date' => 'Færdiggørelsesdag',
+ 'po_time' => 'PO tid',
+ 'site' => 'Site',
+ 'ressource' => 'Ressource',
+ 'product_owner' => 'Product Owner',
+ 'group_id' => StraightForm_BasicElements::select('Gruppe:', Seth_Controller_Filter::getGroups()),
+ ));
+$ormControlElm->setParameters('title', array('text_regex' => '/^.+/'));
+$ormControlElm->setParameters('priority', array('text_regex' => '/^[0-9]*$/'));
+$ormControlElm->setParameters('estimate_design', array('text_regex' => '/^[0-9]*$/'));
+$ormControlElm->setParameters('estimate_development', array('text_regex' => '/^[0-9]*$/'));
+$ormControlElm->setParameters('actual_development', array('text_regex' => '/^[0-9]*$/'));
+$ormControlElm->setParameters('actual_design', array('text_regex' => '/^[0-9]*$/'));
+$ormControlElm->setParameters('sprint', array('text_regex' => '/^[0-9]*$/'));
+$ormControlElm->setParameters('po_time', array('text_regex' => '/^[0-9]*$/'));
+$ormControlElm->setNullify(array('assignee', 'area', 'contact_person', 'estimate_design',
+ 'estimate_development', 'actual_design', 'actual_development', 'contact_person_email',
+ 'deadline', 'sprint', 'completed_date', 'po_time', 'site', 'ressource', 'product_owner'));
+$ormControlElm->setAutoSave(false);
+
+$ormControlElmDesc = new StraightForm_ORMController($elemDescription,
+ array(
+ 'user_story' => StraightForm_BasicElements::textarea('User Story'),
+ 'specifications' => StraightForm_BasicElements::textarea('Specifikationer'),
+ 'description' => StraightForm_BasicElements::textarea('Beskrivelse'),
+ 'url' => 'URL',
+ 'material_in_shared_folder' => StraightForm_BasicElements::checkbox('Findes yderligere materiale på share')
+ ));
+$ormControlElmDesc->setNullify(array('user_story', 'specifications',
+ 'description', 'url', 'material_in_shared_folder'));
+$ormControlElmDesc->setAutoSave(false);
+
+// Form
+$form = new StraightForm(new StraightForm_ChainController($ormControlElmDesc, $ormControlElm));
+
+Seth_Controller_Page::printTop($form->getHead());
+
+// Saving
+if ($form->handleForm()) {
+ // post submit we will convert some fields back to what they really should be
+ $element->save();
+ $elemDescription->save();
+ // completed - return to list page
+ $newTitle = json_encode(utf8_encode($element->getTitle()));
+ echo '<script type="text/javascript">parent.SethManager.updateThenCloseEditDialog('.$element->getId().', '.$newTitle.'); //</script>';
+
+} else {
+ echo $form->getHTML();
+}
+
+Seth_Controller_Page::printBottom();
+
+?>
--- /dev/null
+<?php
+require_once 'Seth/Controller/Page.php';
+require_once 'Seth/Controller/Filter.php';
+require_once 'Seth/Element/Filter.php';
+require_once 'Seth/Model/Element.php';
+
+I2_GUI_List::process();
+
+// Require group_id
+$groupId = isset($_GET['group_id']) ? intval($_GET['group_id']) : 0;
+
+// My controller
+$con = new Seth_Controller_Filter();
+
+// Create new or edit filter
+$filterId = isset($_GET['id']) ? intval($_GET['id']) : 0;
+if ($filterId) {
+ $filter = new Seth_Model_Filter($filterId);
+ $groupId = $filter->getGroupId();
+} else {
+ // create new with group id as default
+ if (!$groupId) {
+ echo 'No group'; die();
+ }
+ $filter = new Seth_Model_Filter();
+ $filter->setGroupId($groupId);
+}
+
+// Fields directly mapped to table (ORM object):
+$ormController = new StraightForm_ORMController($filter,
+ array('title' => 'Navn til filtret:',
+ 'group_id' => StraightForm_BasicElements::select('Gruppe:', Seth_Controller_Filter::getGroups()),
+ 'inherit_id' => StraightForm_BasicElements::select('Byg ovenpå:', Seth_Controller_Filter::getFilters($groupId, $filter)),
+ 'description' => StraightForm_BasicElements::textarea('Beskrivelse:')));
+
+$ormController->setParameters('title', array('text_regex' => '/^\w+/'));
+$ormController->setNullify(array('inherit_id'));
+$ormController->setAutoSave(false);
+
+// Form
+$form = new StraightForm($ormController);
+
+// Fields added for prettifying gui making it easier to add conditions:
+$fields = Seth_Model_Element::getFilterFields();
+sort($fields);
+$fields = array_combine($fields, $fields); // same keys/values
+$fields = array_merge(array(0=>''), $fields); // Empty default
+$default = 0;
+
+$conds = Seth_Model_Filter::getPossibleConditions();
+$conds = array_combine($conds, $conds); // same keys/values
+
+$formFilters = array();
+$rules = $filter->getFilterBy();
+if (!is_null($rules)) reset($rules);
+
+for ($i=0; $i<6; $i++) {
+ $formFilters[] = $f = $form->addElement('filter'. $i, new Seth_Element_Filter($i?null:'Filtre:', $fields, $conds));
+ if (!is_null($rules) && current($rules)) {
+ $f->setValue(current($rules));
+ next($rules);
+ }
+}
+
+// Add group by fields
+$formGroupBy = array();
+
+$groupBy = $filter->getGroupBy();
+if (!is_null($groupBy)) reset($groupBy);
+
+for ($i=0; $i<5; $i++) {
+ $formGroupBy[] = $f = $form->addElement('groupby'.$i,
+ StraightForm_BasicElements::select($i?null:'Gruppér efter:', $fields, $default));
+ if (!is_null($groupBy) && current($groupBy)) {
+ $f->setValue(current($groupBy));
+ next($groupBy);
+ }
+}
+
+// Add order by fields
+$formOrderBy = array();
+
+$orderBy = $filter->getOrderBy();
+if (!is_null($orderBy)) reset($orderBy);
+
+for ($i=0; $i<5; $i++) {
+ $formOrderBy[] = $f = $form->addElement('orderby'.$i,
+ StraightForm_BasicElements::select($i?null:'Sortér efter:', $fields, $default));
+ if (!is_null($orderBy) && current($orderBy)) {
+ $f->setValue(current($orderBy));
+ next($orderBy);
+ }
+}
+
+
+if ($form->handleForm()) {
+ // post submit we will convert filters to a serialized field
+ $rules = array();
+ foreach ($formFilters as $selection) {
+ $rule = $selection->getValue();
+ if (!is_null($rule)) {
+ // if used add to list
+ $rules[] = $rule;
+ }
+ }
+ // group by
+ $groupby = array();
+ foreach ($formGroupBy as $by) {
+ $value = $by->getValue();
+ var_dump($value);
+ if ((string)$value != (string)$default) {
+ $groupby[] = $value;
+ }
+ }
+
+ // order by
+ $orderby = array();
+ foreach ($formOrderBy as $by) {
+ $value = $by->getValue();
+ if ((string)$value != (string)$default) {
+ $orderby[] = $value;
+ }
+ }
+
+ $filter->setFilterBy($rules);
+ $filter->setGroupBy($groupby);
+ $filter->setOrderBy($orderby);
+ $filter->save();
+ // completed - return to list page
+ header('Location: addfilter.php?group_id=' . $filter->getGroupId());
+ exit;
+}
+
+Seth_Controller_Page::printTop($form->getHead());
+
+echo $form->getHTML();
+
+Seth_Controller_Page::printBottom();
+
+?>
--- /dev/null
+<?php
+require_once 'Seth/Model/Group.php';
+require_once 'Seth/Controller/Group.php';
+I2_GUI_List::process();
+Seth_Controller_Group::process();
+$vendor = getSite('vendor');
+
+$head = <<<EOH
+<script type="text/javascript" src="http://$vendor/js/jquery.js"></script>
+<script type="text/javascript" src="js/group.js"></script>
+EOH;
+
+$page = new I2_GUI_Informational('Seth Projekt Organizer', 'TV 2 Net'."\n".'Seth is design and implemented by Kristian Kræmmer Nielsen <jkkn@tv2.dk>, Copyright (c) 2010');
+$page->printTop($head);
+$page->printSubtitle('Beskrivelse:');
+
+echo 'Seth, opkaldt efter den egytiske gud for kaos.<br/>I den filosofi at er du venner med Seth har du bedre chance for at få styr på dit projekt og ikke ende ud i kaos.<br/><br/>';
+
+$page->printSubtitle('Grupper:');
+
+$list = new I2_GUI_List();
+$list->enableFeatures(I2_GUI_LIST_TOTALCOUNT);
+$list->setHeaders(array(
+ 'id' => 'Id',
+ 'title' => 'Titel',
+ 'actions' => 'Muligheder'));
+$list->setLimit(25);
+$list->setDataORM(Seth_Model_Group::TYPE);
+$list->setOrderBy('title');
+$list->setRowCallback('insertActions', 'Seth_Controller_Group');
+$list->setRowLink('views.php?group_id=%d', 'id');
+echo $list->getHTML();
+
+?>
+<br/>
+<button onclick="SethGroup.createNew()">Opret nyt gruppe</button>
+
+<?php
+
+$page->printBottom();
+
+?>
--- /dev/null
+/*
+ * Group list
+ */
+
+SethGroup = {
+
+ createNew: function() {
+ var name = prompt('Indtast navn:');
+ if (name) {
+ name = name.replace(/\s*/, "")
+ if (name != '') {
+ // do callback
+ $.ajax({
+ type: 'POST',
+ data: {
+ action: 'new',
+ title: name
+ },
+ dataType: 'json',
+ error: function() {
+ alert('Server fejl');
+ },
+ success: function(data) {
+ if (data && data.ok) {
+ window.location.reload();
+ }
+ }
+ });
+ }
+ }
+ },
+
+ rename: function(id, title) {
+ var name = prompt('Indtast navn:', unescape(title));
+ if (name) {
+ name = name.replace(/\s*/, "")
+ if (name != '') {
+ // do callback
+ $.ajax({
+ type: 'POST',
+ data: {
+ action: 'rename',
+ id: id,
+ title: name
+ },
+ dataType: 'json',
+ error: function() {
+ alert('Server fejl');
+ },
+ success: function(data) {
+ if (data && data.ok) {
+ window.location.reload();
+ }
+ }
+ });
+ } else {
+ alert('Ugyldigt navn');
+ }
+ }
+ },
+
+ remove: function(id, title) {
+ if (confirm('Er du sikker på at slettet "'+unescape(title)+'" ?')) {
+ // do callback
+ $.ajax({
+ type: 'POST',
+ data: {
+ action: 'remove',
+ id: id,
+ },
+ dataType: 'json',
+ error: function() {
+ alert('Server fejl');
+ },
+ success: function(data) {
+ if (data && data.ok) {
+ window.location.reload();
+ }
+ }
+ });
+ }
+ }
+
+}
--- /dev/null
+
+var SethManager = {
+
+ view_id: null, // ID of current view
+ group_id: null, // ID of group shown
+
+ next_tab_no: 0,
+
+ disableEditLinks: false, // manual disable all links when dragging
+ cancelMove: true, // cancels moves (used when moving into tabs)
+ currentTabNo: -1, // tab_no of current tab
+
+ init: function(view_id, group_id, list_of_tabs) {
+ this.view_id = view_id;
+ this.group_id = group_id;
+
+ // create addList dialog
+ $("#addlist").dialog({
+ modal: true,
+ autoOpen: false,
+ height: 600,
+ width: 800,
+ buttons: {
+ "Fortryd": function() {
+ SethManager.closeAddListDialog();
+ }
+ },
+ close: function() {
+ SethManager.emptyListsAndUpdateCurrent();
+ $('#addlistiframe').attr('src', 'about:blank'); // make F5 work
+ }
+ });
+
+ // create edit element dialog
+ $("#editelement").dialog({
+ modal: true,
+ autoOpen: false,
+ height: 600,
+ width: 800,
+ buttons: {
+ "Fortryd": function() {
+ SethManager.closeEditDialog();
+ },
+ "Gem": function() {
+ SethManager.saveAndCloseEditDialog();
+ },
+ "Slet": function() {
+ SethManager.deleteAndCloseEditDialog();
+ }
+ },
+ close: function() {
+ $("#editiframe").attr('src', 'about:blank'); // make F5 work
+ }
+ });
+
+ // turn add/edit list links into open dialog
+ $("#addlist-link").click(function() {
+ SethManager.openAddListDialog();
+ return false;
+ });
+ $("#editlist-link").click(function() {
+ SethManager.openEditListDialog();
+ return false;
+ });
+
+ // remove list link
+ $("#removelist-link").click(function() {
+ SethManager.removeCurrentList();
+ return false;
+ });
+
+ // create filter tabs
+ $("#tabs").tabs({
+ show: function(event, ui) {
+ SethManager.showTab(event, ui);
+ SethManager.currentTabNo = $(ui.panel).data('tab_no');
+ }
+ });
+ var tabsul = $("#tabs-list");
+
+ // Allow to sort tabs
+ tabsul.sortable();
+
+ tabsul.bind('sortupdate', function(event, ui) {
+ SethManager.storeFilterOrder();
+ });
+
+ // create tabs
+ $.each(list_of_tabs, function(index, value) {
+ SethManager.addList(value.id, value.title);
+ });
+
+ },
+
+ // Callback and get a fresh list
+ updateList: function(tabno) {
+ var listDom = $("#list-" + tabno);
+ var list_id = listDom.data('list_id');
+ listDom.data('list_status', 'loading');
+
+ // do callback
+ $.ajax({
+ type: 'POST',
+ data: {
+ action: 'get',
+ listid: list_id
+ },
+ dataType: 'json',
+ error: function() {
+ SethManager.commError(this);
+ },
+ success: function(data) {
+ if (data && data.list) {
+ SethManager.fillInList(tabno, data.list);
+ } else {
+ SethManager.commError(this, data);
+ }
+ }
+ });
+ },
+
+ // Fill in a list using return data
+ fillInList: function(tabno, listdata) {
+ // check for error (list returned as nested result)
+ if (listdata.err) {
+ this.commError(null, listdata);
+ return;
+ }
+
+ var listDom = $("#list-" + tabno);
+ var first = true;
+
+ listDom.empty();
+
+ listDom.data('list_status', 'loaded');
+
+ // We need to create a ul for each group
+ $.each(listdata.groups, function(index, value) {
+ var group = $('<div class="group-seperator">'+value.title+'</div>');
+ group.data('group_defined_as', value.defined_as);
+ group.data('group_when_placing_here', value.when_placing_here);
+ listDom.append(group);
+
+ var table = $('<ul class="ul-helper-reset table"/>');
+ // and for each element in the group create it
+ $.each(value.elements, function(index, value) {
+ var elmLi = $('<li class="ui-state-default element" />');
+ var elmDiv = $('<div class="draggable" />');
+ var elmA = $('<a class="element-'+value.id+'" href="#"/>');
+ // Add mouseover
+ elmDiv.attr('title', value.description);
+ // store meta data
+ elmLi.data('element_id', value.id);
+ // set onclick event
+ elmA.click(function() {
+ if (!SethManager.disableEditLinks) {
+ SethManager.editElement(value.id);
+ }
+ return false;
+ });
+ // set link title
+ elmA.text(value.title);
+ // append element
+ elmDiv.append(elmA);
+ elmLi.append(elmDiv);
+ table.append(elmLi);
+ });
+
+ listDom.append(group).append(table);
+
+ // create a new element button for each group
+ if (value.defined_as) {
+ var button = $('<input type="submit" value="Tilføj ny"/>').button();
+ var input = $('<input id="list-new-'+index+'" type="text"/>');
+ var form = $('<form class="ui-helper-reset"/>').submit(function() {
+ SethManager.createNew(this);
+ return false;
+ });
+
+ var new_li = $('<div class="ui-state-default new"></div>');
+ form.append(new_li.append(input).append(button));
+ listDom.append(form);
+ }
+ });
+
+ var tables = listDom.find("ul.table");
+ // Make drag'n'drop able
+ tables.sortable({
+ start: function() {
+ // disable links while dragging
+ SethManager.disableEditLinks = true;
+ SethManager.cancelMove = false;
+ },
+ stop: function() {
+ SethManager.cancelMove = true;
+ // Hack, but cannot seem to find any event that triggers at a proper time
+ setTimeout(function() {
+ SethManager.disableEditLinks = false;
+ }, 10);
+ },
+ connectWith: '#list-' + tabno + ' ul',
+ update: function(event, ui) {
+ SethManager.sortElement(event, ui);
+ }
+ });
+
+ },
+
+ // Create new element
+ createNew: function(form) {
+ form = $(form);
+ var text = form.find('input').get(0);
+ var input_id = text.id; // going back to this one afterwards
+ var list_id = form.parent().data('list_id');
+ var tab_no = form.parent().data('tab_no');
+ var list = form.prev();
+ var group_defined_as = list.prev().data('group_defined_as');
+ var title = text.value;
+
+ if ($.trim(title) == '') return; // have to enter something
+
+ text.disabled = true;
+
+ // create new element and add it to the list (before server update)
+ this.addNewElement(list, title);
+
+ // do callback
+ $.ajax({
+ type: 'POST',
+ data: {
+ action: 'new',
+ obj: {
+ title: title,
+ filter_id: list_id,
+ group_defined_as: group_defined_as
+ }
+ },
+ dataType: 'json',
+ error: function() {
+ SethManager.commError(this);
+ },
+ success: function(data) {
+ if (data && data['new']) {
+ // success
+ // update list with result
+ SethManager.fillInList(tab_no, data.list);
+ // flush other tabs
+ SethManager.emptyAllListsButCurrent();
+ // Set focus back
+ $("#"+input_id).focus();
+ } else {
+ SethManager.commError(this, data);
+ }
+ }
+ });
+
+ },
+
+ commError: function(obj, data) {
+ if (data && data.err) {
+ alert(data.err);
+ } else {
+ /* silent ignore */
+// alert('Fejl ved kommunikation med server - genindlæs siden!');
+ }
+ },
+
+ // Add one element to the list
+ addNewElement: function(list, text) {
+ var li = $('<li class="ui-state-default">test</li>');
+ li.text(text);
+
+ list.append(li);
+ },
+
+ // Add a list to the tabs
+ addList: function(id, title, addedByUser) {
+ var tab_no = (this.next_tab_no++);
+ var tabs = $("#tabs");
+ var content = $('<div id="list-'+tab_no+'" class="filtered_list"></div>');
+
+ // store list id as meta data
+ content.data('tab_no', tab_no);
+ content.data('list_id', id);
+ content.data('list_status', 'not-loaded');
+
+ tabs.append(content);
+
+ // new tab
+ tabs.tabs("add", "#list-"+tab_no, title);
+
+ // allow to drop element on the tab
+ var tab = tabs.find("> ul:first > li > a[href='#list-"+tab_no+"']").parent();
+ var list_id = id;
+ tab.droppable({
+ accept: "ul.table li",
+ hoverClass: "ui-state-hover",
+ tolerance: 'pointer',
+ drop: function(ev, ui) {
+ var item = $(ui.draggable);
+ var element_id = item.data('element_id');
+ // Lets check if its the current list so we can ignore this
+ var cur_list_id = item.parents('div.filtered_list').data('list_id');
+ if (cur_list_id == list_id) {
+ alert('Allerede i den liste.');
+ } else {
+ // we have to mark the element dirty to avoid moving it
+ // back again, since the jquery sortable api will also generate an
+ // event
+ SethManager.cancelMove = true;
+ item.remove();
+ SethManager.moveElementToOtherList(element_id, list_id);
+ }
+ }
+ });
+
+ if (addedByUser) {
+ // select the tab and load it
+ tabs.tabs("select", "list-"+tab_no);
+ // store order
+ this.storeFilterOrder();
+ }
+
+ },
+
+ // Remove a list from tabs
+ removeCurrentList: function() {
+ var tabs = $("#tabs");
+ var selected = tabs.tabs('option', 'selected');
+ if (selected != -1) {
+ tabs.tabs('remove', selected);
+ SethManager.storeFilterOrder();
+ }
+ },
+
+ // Store new filter order in view
+ storeFilterOrder: function() {
+ var tabs = $("#tabs ul:first > li > a");
+ var order = [];
+ $.each(tabs, function() {
+ order[order.length] = $($(this).attr("href")).data('list_id');
+ });
+
+ // do callback
+ $.ajax({
+ type: 'POST',
+ data: {
+ action: 'setfilterorder',
+ view_id: this.view_id,
+ order: order
+ },
+ dataType: 'json',
+ error: function() {
+ SethManager.commError(this);
+ },
+ success: function(data) {
+ if (data && data.save) {
+ // success, be quietly happy :-)
+ } else {
+ SethManager.commError(this, data);
+ }
+ }
+ });
+ },
+
+ // Drag element from one list to another
+ moveElementToOtherList: function(id, filter_id) {
+ // do callback
+ $.ajax({
+ type: 'POST',
+ data: {
+ action: 'move_to_list',
+ element_id: id,
+ filter_id: filter_id
+ },
+ dataType: 'json',
+ error: function() {
+ SethManager.commError(this);
+ },
+ success: function(data) {
+ if (data && data.save) {
+ // success
+ SethManager.emptyListsAndUpdateCurrent();
+ } else {
+ SethManager.commError(this, data);
+ }
+ }
+ });
+ },
+
+ // Sort element between groups or in same list
+ sortElement: function(event, ui) {
+ // ignore events send from other list (group by)
+ // (the other group will handle it)
+ if (ui.sender) return;
+
+ // ignore if moving into a tab
+ if (this.cancelMove) return;
+
+ // we need to find where we were placed
+ // element before, after, and which group
+ var item = $(ui.item)
+ var elmId = item.data('element_id');
+ var above = item.prev('li');
+ var aboveId = above ? $(above).data('element_id') : null;
+ var below = item.next('li');
+ var belowId = below ? $(below).data('element_id') : null;
+
+ // find our filter id
+ var filterList = item.parents('div.filtered_list');
+ var filterId = filterList.data('list_id');
+ var tabno = filterList.data('tab_no');
+
+ // expect to have a group header just above the <ul />
+ var group_defined_as = item.parent().prev().data('group_defined_as');
+
+ if (!group_defined_as) {
+ alert('Fix me, cannot find my group');
+ }
+
+ // do callback
+ $.ajax({
+ type: 'POST',
+ data: {
+ action: 'move_element',
+ obj: {
+ element_id: elmId,
+ above_id: aboveId,
+ below_id: belowId,
+ group_defined_as: group_defined_as,
+ filter_id: filterId
+ }
+ },
+ dataType: 'json',
+ error: function() {
+ SethManager.commError(this);
+ },
+ success: function(data) {
+ if (data && data.moved && data.list) {
+ SethManager.fillInList(tabno, data.list);
+ // flush other tabs
+ SethManager.emptyAllListsButCurrent();
+ } else {
+ SethManager.commError(this, data);
+ }
+ }
+ });
+ },
+
+ // Flushes all lists after update and updates the current one
+ // excludeCurrent - true to not update current (done otherwise)
+ emptyListsAndUpdateCurrent: function(excludeCurrent) {
+ var tabs = $("#tabs");
+ var selected = tabs.tabs('option', 'selected');
+ var curTabNo = null;
+ var curListId = null;
+ if (selected != -1) {
+ curTabNo = this.currentTabNo;
+ curListId = "#list-" + curTabNo;
+ }
+ // wipe all lists except current
+ $.each(tabs.find("> div"), function() {
+ if (("#"+this.id) != curListId) {
+ $(this).empty();
+ $(this).data('list_status', 'not-loaded');
+ }
+ });
+
+ // update current list
+ if (curListId && !excludeCurrent) {
+ this.updateList(curTabNo);
+ }
+ },
+
+ // Flush all lists except current
+ emptyAllListsButCurrent: function() {
+ this.emptyListsAndUpdateCurrent(true);
+ },
+
+ // Display tab, load content is not already done
+ showTab: function(event, ui) {
+ var panel = $(ui.panel);
+ if (panel.data('list_status') == 'not-loaded') {
+ this.updateList(panel.data('tab_no'));
+ }
+ },
+
+ // Delete specific element
+ deleteElement: function(id) {
+ // do callback
+ $.ajax({
+ type: 'POST',
+ data: {
+ action: 'delete_element',
+ element_id: id
+ },
+ dataType: 'json',
+ error: function() {
+ SethManager.commError(this);
+ },
+ success: function(data) {
+ if (data && data['delete']) {
+ SethManager.emptyListsAndUpdateCurrent();
+ } else {
+ SethManager.commError(this, data);
+ }
+ }
+ });
+ },
+
+// ---------------------- Edit Element dialog ----------------------------------
+//
+ // Open dialog to edit element
+ editElement: function(id) {
+ $("#editelement").data('element_id', id);
+ $("#editiframe").attr('src', 'edit.php?id=' + id);
+ $("#editelement").dialog("open");
+ },
+
+ closeEditDialog: function() {
+ $("#editelement").dialog("close");
+ },
+
+ updateThenCloseEditDialog: function(id, title) {
+ $(".element-"+id).text(title);
+ this.emptyListsAndUpdateCurrent();
+ this.closeEditDialog();
+ },
+
+ saveAndCloseEditDialog: function () {
+ $("#editiframe").contents().find("form").submit();
+ },
+
+ deleteAndCloseEditDialog: function () {
+ if (confirm('Er du sikker på at slette dette element?')) {
+ var elementId = $("#editelement").data('element_id');
+ $.each($(".element-"+elementId), function() {
+ $(this).parent().parent().remove();
+ });
+ this.deleteElement(elementId);
+ this.closeEditDialog();
+ }
+ },
+
+// ---------------------- Add Filter dialog -------------------------------------
+
+ // Open dialog to add list
+ openAddListDialog: function(id) {
+ $("#addlistiframe").attr('src', 'addfilter.php?group_id=' + this.group_id);
+ $("#addlist").dialog("open");
+ },
+
+ // Open dialog to edit list
+ openEditListDialog: function(id) {
+ var tabs = $("#tabs");
+ var selected = tabs.tabs('option', 'selected');
+ if (selected != -1) {
+ var id = $('#list-' + this.currentTabNo).data('list_id');
+
+ $("#addlistiframe").attr('src', 'editfilter.php?id=' + id);
+ $("#addlist").dialog("open");
+ }
+ },
+
+ closeAddListDialog: function() {
+ $("#addlist").dialog("close");
+ },
+
+ addListAndCloseDialog: function(id, title) {
+ // Notice that the dialog uses will escape the title parameter
+ // as an URL-encoded string so we must decode:
+ title = unescape(title);
+ this.closeAddListDialog();
+ this.addList(id, title, true);
+ },
+
+// ------------------------ addFilter.php ---------------------------------------
+ initAddFilter: function() {
+ // auto submit form when changing group
+ $("#group_id").bind('change', function() {
+ $(this).parents('form').get(0).submit();
+ });
+ },
+
+ removeFilter: function(id, title) {
+ if (confirm('Er du sikker på at slettet "'+unescape(title)+'" ?')) {
+ // do callback
+ $.ajax({
+ type: 'POST',
+ data: {
+ action: 'remove',
+ id: id,
+ },
+ dataType: 'json',
+ error: function() {
+ alert('Server fejl');
+ },
+ success: function(data) {
+ if (data && data.ok) {
+ window.location.reload();
+ }
+ }
+ });
+ }
+ }
+
+}
--- /dev/null
+/*
+ * View list
+ */
+
+SethView = {
+
+ createNew: function(group_id) {
+ var name = prompt('Indtast navn:');
+ if (name) {
+ name = name.replace(/\s*/, "")
+ if (name != '') {
+ // do callback
+ $.ajax({
+ type: 'POST',
+ data: {
+ action: 'new',
+ title: name,
+ group_id: group_id
+ },
+ dataType: 'json',
+ error: function() {
+ alert('Server fejl');
+ },
+ success: function(data) {
+ if (data && data.ok) {
+ window.location.reload();
+ }
+ }
+ });
+ }
+ }
+ },
+
+ rename: function(id, title) {
+ var name = prompt('Indtast navn:', unescape(title));
+ if (name) {
+ name = name.replace(/\s*/, "")
+ if (name != '') {
+ // do callback
+ $.ajax({
+ type: 'POST',
+ data: {
+ action: 'rename',
+ id: id,
+ title: name
+ },
+ dataType: 'json',
+ error: function() {
+ alert('Server fejl');
+ },
+ success: function(data) {
+ if (data && data.ok) {
+ window.location.reload();
+ }
+ }
+ });
+ } else {
+ alert('Ugyldigt navn');
+ }
+ }
+ },
+
+ remove: function(id, title) {
+ if (confirm('Er du sikker på at slettet "'+unescape(title)+'" ?')) {
+ // do callback
+ $.ajax({
+ type: 'POST',
+ data: {
+ action: 'remove',
+ id: id,
+ },
+ dataType: 'json',
+ error: function() {
+ alert('Server fejl');
+ },
+ success: function(data) {
+ if (data && data.ok) {
+ window.location.reload();
+ }
+ }
+ });
+ }
+ }
+
+}
--- /dev/null
+<?php
+ require_once 'Seth/Controller/Page.php';
+ require_once 'Seth/Controller/List.php';
+
+ // Check view
+ $view_id = isset($_GET['id']) ? intval($_GET['id']) : null;
+
+ // Process callbacks
+ $list = new Seth_Controller_List($view_id);
+ $list->process();
+
+ $view = new Seth_Model_View($view_id);
+ if ($view->isNew()) {
+ echo 'View has been deleted/does not exist: ' . $view_id;
+ die();
+ }
+
+ Seth_Controller_Page::printTop();
+ $order = $list->getFilterOrder($view);
+
+ $title = $view->getTitle();
+ $group_id = $view->getGroupId();
+
+?>
+
+<script type="text/javascript">
+$(function() {
+ SethManager.init(<?=$view_id?>, <?=$group_id?>, <?=$order?>);
+});
+</script>
+
+<div id="page">
+
+<h1>Seth: <?=htmlentities($title)?></h1>
+
+<!-- Main page, tabs and lists -->
+
+<div id="actions">
+<a id="removelist-link" href="#">Fjern liste</a> |
+<a id="editlist-link" href="#">Redigér liste</a> |
+<a id="addlist-link" href="#">Tilføj ny liste</a>
+</div>
+
+<div id="tabs"><ul id="tabs-list"></ul></div>
+
+</div>
+
+<!-- Dialog to create a new list (iframe) -->
+<div id="addlist">
+ <iframe id="addlistiframe"></iframe>
+</div>
+
+<!-- Dialog to edit an element (iframe) -->
+<div id="editelement">
+ <iframe id="editiframe"></iframe>
+</div>
+
+
+<?php
+ Seth_Controller_Page::printBottom();
+?>
--- /dev/null
+<?php
+require_once 'Seth/Model/View.php';
+require_once 'Seth/Model/Group.php';
+require_once 'Seth/Controller/View.php';
+I2_GUI_List::process();
+Seth_Controller_View::process();
+
+$group_id = isset($_GET['group_id']) ? intval($_GET['group_id']) : null;
+$group = new Seth_Model_Group($group_id);
+if ($group->isNew()) {
+ echo 'Forkert gruppe';
+ die();
+}
+
+$vendor = getSite('vendor');
+
+$head = <<<EOH
+<script type="text/javascript" src="http://$vendor/js/jquery.js"></script>
+<script type="text/javascript" src="js/view.js"></script>
+EOH;
+
+$page = new I2_GUI_Informational('Seth Projekt Organizer', 'TV 2 Net'."\n".'Seth is design and implemented by Kristian Kræmmer Nielsen <jkkn@tv2.dk>, Copyright (c) 2010');
+$page->printTop($head);
+$page->printSubtitle('Gruppe:');
+echo htmlentities($group->getTitle()) . '<br/><br/>';
+
+$page->printSubtitle('Views for '.$group->getTitle().':');
+$list = new I2_GUI_List();
+$list->enableFeatures(I2_GUI_LIST_TOTALCOUNT);
+$list->setHeaders(array(
+ 'id' => 'Id',
+ 'title' => 'Titel',
+ 'actions' => 'Muligheder'));
+$list->setLimit(25);
+$list->setOrderBy('title');
+$list->setDataORM(Seth_Model_View::TYPE, 'group_id=?', $group_id);
+$list->setRowCallback('insertActions', 'Seth_Controller_View');
+$list->setRowLink('list.php?id=%d', 'id');
+echo $list->getHTML();
+
+?>
+<br/>
+<button onclick="SethView.createNew(<?=$group_id?>)">Opret nyt view</button>
+
+<?php
+
+$page->printBottom();
+
+?>
--- /dev/null
+<?php
+require_once 'Seth/Model/Element.php';
+require_once 'Seth/Model/Element/Descriptions.php';
+
+/** Convert Uploaded Access-tables */
+
+define('NL', "\n");
+
+// Table to group
+$tables =
+ array('Gazelle' => 3,
+ 'Entreprenør' => 2);
+
+$mapToElement =
+ array(
+ 'Prioritet' => 'priority',
+ 'Område' => 'area',
+ 'Overskrift' => 'title',
+ 'Tidsestimat' => 'estimate_development',
+ 'Estimat point' => 'estimate_development',
+ 'Faktisk tid' => 'actual_development',
+ 'Godkendt' => 'approved',
+ 'Design' => 'design',
+ 'Kontaktperson' => 'contact_person',
+ 'Kontaktperson - mail' => 'contact_person',
+ 'Færdig' => 'completed',
+ 'Slet' => 'deleted',
+ 'Bug' => 'bug',
+ 'Deadline' => 'deadline',
+ 'Designestimat' => 'estimate_design',
+ 'Grafiker faktisk tid' => 'actual_design',
+ 'Sprint' => 'sprint',
+ 'Færdiggørelsesdato' => 'completed_date',
+ 'PO tid' => 'po_time',
+ 'site' => 'site',
+ 'Ressource' => 'ressource',
+ 'Product Owner' => 'product_owner');
+$mapToDesc = array(
+ 'User story' => 'user_story',
+ 'Specifikationer' => 'specifications',
+ 'Beskrivelse i gazellemappe' => 'material_in_shared_folder',
+ 'URL' => 'url');
+
+$bitFields = array('Godkendt', 'Design', 'Færdig', 'Slet', 'Bug', 'Beskrivelse i gazellemappe');
+
+$db = DB_PDO::get('seth');
+
+foreach ($tables as $table => $group) {
+
+ORM_Multiple::delete(ORM::find(Seth_Model_Element::TYPE, 'group_id=?', $group));
+
+echo 'Importing '.$table.'...' .NL;
+
+ $rows = $db->getAll('SELECT * FROM ' . $table);
+
+ foreach ($rows as $row) {
+
+ $element = new Seth_Model_Element();
+
+ // convert bits
+ foreach ($bitFields as $field) {
+ if (isset($row[$field])) {
+ $row[$field] = ord($row[$field][0]) == 1; // Fix converting boolean from mysql
+ }
+ }
+
+ if ($table == 'Entreprenør') {
+ // Entreprenør
+ if (isset($row['Product Owner'])) {
+ $row['Product Owner'] = $db->getOne('SELECT Initialer FROM `Entreprenør_Product Owners` WHERE ID=?', $row['Product Owner']);
+ }
+ if (isset($row['Område'])) {
+ $row['Område'] = $db->getOne('SELECT Område FROM Entreprenør_Område WHERE Id=?', $row['Område']);
+ }
+ }
+
+ // Gazelle: Slå sites op og ressource
+ if ($table == 'Gazelle') {
+ if (isset($row['Ressource'])) {
+ $row['Ressource'] = $db->getOne('SELECT Felt1 FROM Gazelle_Ressource WHERE Id=?', $row['Ressource']);
+ }
+ if (isset($row['Område'])) {
+ $row['Område'] = $db->getOne('SELECT Område FROM Gazelle_Område WHERE Id=?', $row['Område']);
+ }
+ if (isset($row['site'])) {
+ $row['site'] = $db->getOne('SELECT Site FROM Gazelle_Sites WHERE Id=?', $row['site']);
+ }
+ $row['Product Owner'] = 'JEPH';
+ }
+
+ foreach ($mapToElement as $from => $to) {
+ if (isset($row[$from])) {
+ $element->set($to, $row[$from]);
+ }
+ }
+ $element->setGroupId($group);
+ $element->save();
+ $desc = new Seth_Model_Element_Descriptions($element->getId());
+ foreach ($mapToDesc as $from => $to) {
+ if (isset($row[$from])) {
+ $desc->_fields[$to] = $row[$from]; // hack
+ }
+ }
+ $desc->save();
+ }
+
+}
+
+echo "Done.".NL.NL;
+