diff --git a/includes/t-data/dao_tasks.inc.php b/includes/t-data/dao_tasks.inc.php index 7cbb3a4..1695738 100644 --- a/includes/t-data/dao_tasks.inc.php +++ b/includes/t-data/dao_tasks.inc.php @@ -171,8 +171,10 @@ class DAO_Tasks . 'ORDER BY i.item_name , t.task_priority DESC , t.task_title' )->execute( $id ); $task->possibleDependencies = $this->query( 'SELECT t.task_id AS id , t.task_title AS title , t.item_id AS item , ' - . 'i.item_name AS item_name ' + . 'i.item_name AS item_name , l.badness <> 0 AS blocked , ' + . 'l.completed_at IS NOT NULL AS completed ' . 'FROM tasks_possible_dependencies( $1 ) t ' + . 'INNER JOIN tasks_list l ON t.task_id = l.id ' . 'LEFT OUTER JOIN items i USING ( item_id ) ' . 'ORDER BY i.item_name , t.task_priority , t.task_title' )->execute( $id ); $task->lineage = null; diff --git a/includes/t-tasks/controllers.inc.php b/includes/t-tasks/controllers.inc.php index 9322389..de5862d 100644 --- a/includes/t-tasks/controllers.inc.php +++ b/includes/t-tasks/controllers.inc.php @@ -527,6 +527,188 @@ class Ctrl_DependencyAdd } +class Ctrl_DependencyAddFiltering + extends Controller + implements FormAware +{ + private $filtering; + private $selector; + private $task; + + private $dependencies; + + public function __construct( Form $selector , $task ) + { + $this->selector = $selector; + $this->task = $task; + } + + public function setForm( Form $form ) + { + $this->filtering = $form; + } + + public function handle( Page $page ) + { + $this->filterTaskDependencies( ); + $this->addDependencySelector( ); + $this->copyFiltersToSelector( ); + return null; + } + + private function filterTaskDependencies( ) + { + $this->dependencies = array( ); + $text = trim( $this->getField( 'text' ) ); + if ( $text == '' ) { + $text = array( ); + } else { + $text = array_unique( preg_split( '/\s+/' , $text ) ); + } + + $state = $this->getField( 'state' ); + $sActive = ( $state == '' || strstr( $state , 'a' ) !== false ); + $sBlocked = ( $state == '' || strstr( $state , 'b' ) !== false ); + $sCompleted = ( $state == '' || strstr( $state , 'c' ) !== false ); + + foreach ( $this->task->possibleDependencies as $dep ) { + // Check for text + $ok = true; + foreach ( $text as $tCheck ) { + $ok = stristr( $dep->title , $tCheck ); + if ( !$ok ) { + break; + } + } + if ( !$ok ) { + continue; + } + + // Check state + $isBlocked = ( $dep->blocked === 't' ); + $isCompleted = ( $dep->completed === 't' ); + if ( $isBlocked && !$sBlocked || $isCompleted && !$sCompleted + || !( $isBlocked || $isCompleted ) && !$sActive ) { + continue; + } + + $this->dependencies[] = $dep; + } + } + + private function addDependencySelector( ) + { + $this->selector->addField( $select = Loader::Create( 'Field' , 'dependency' , 'select' ) + ->setDescription( 'Dependency to add:' ) + ->addOption( '' , '(please select a task)' ) ); + + if ( $this->task->parent_task === null ) { + $depsByItem = $this->getDependenciesByItem( ); + $items = $this->getItemsToDisplay( $depsByItem ); + foreach ( $items as $item ) { + $prefix = '-' . str_repeat( '--' , $item->depth ); + $name = $prefix . ' ' . $item->name; + $select->addOption( 'I' . $item->id , $name , true ); + if ( ! array_key_exists( $item->id , $depsByItem ) ) { + continue; + } + + foreach ( $depsByItem[ $item->id ] as $task ) { + $select->addOption( $task->id , $prefix . '-> ' . $task->title ); + } + } + } else { + foreach ( $this->dependencies as $task ) { + $select->addOption( $task->id , $task->title ); + } + } + return true; + + } + + private function getItemsToDisplay( $depsByItem ) + { + $dao = Loader::DAO( 'items' ); + $found = array( ); + foreach ( array_keys( $depsByItem ) as $id ) { + if ( array_key_exists( $id , $found ) ) { + continue; + } + $item = $dao->get( $id ); + foreach ( $dao->getLineage( $item ) as $parent ) { + $found[ $parent ] = 1; + } + $found[ $id ] = 1; + } + + $fByItem = $this->getField( 'items' ); + $fByItem = ( $fByItem == '' ) ? null : ( (int) $fByItem ); + $fChildren = ( $this->getField( 'item-children' ) === '1' ); + $fOKChildren = false; + $fDepth = -1; + + $result = array( ); + foreach ( $dao->getTreeList( ) as $item ) { + if ( $fByItem !== null && $fChildren ){ + if ( $item->id == $fByItem ) { + $fOKChildren = true; + $fDepth = $item->depth; + } else if ( $fOKChildren && $item->depth <= $fDepth ) { + $fOKChildren = false; + } + } + + if ( $fByItem !== null && $item->id != $fByItem && !$fOKChildren ) { + continue; + } + if ( array_key_exists( $item->id , $found ) ) { + array_push( $result , $item ); + } + } + return $result; + } + + private function getDependenciesByItem( ) + { + $dbi = array( ); + foreach ( $this->dependencies as $pDep ) { + $dbi[ $pDep->item ][] = $pDep; + } + return $dbi; + } + + private function copyFiltersToSelector( ) + { + $fields = array( 'text' , 'state' , 'items' , 'item-children' ); + foreach ( $fields as $f ) { + $v = $this->getField( $f ); + $this->selector->addField( + Loader::Create( 'Field' , 'filters-' . $f , 'hidden' ) + ->setMandatory( false ) + ->setDefaultValue( $v ) ); + } + } + + public function getFiltersFromSelector( ) + { + $fields = array( 'text' , 'state' , 'items' , 'item-children' ); + foreach ( $fields as $f ) { + $field = $this->filtering->field( $f ); + if ( $field !== null ) { + $fv = $this->getParameter( 'filters-' . $f , 'POST' ); + $field->setFormValue( $fv ); + } + } + } + + private function getField( $name ) + { + $fld = $this->filtering->field( $name ); + return $fld ? $fld->value( ) : ''; + } +} + + class Ctrl_DependencyDelete extends Controller implements FormAware diff --git a/includes/t-tasks/package.inc.php b/includes/t-tasks/package.inc.php index 3c13298..992cca5 100644 --- a/includes/t-tasks/package.inc.php +++ b/includes/t-tasks/package.inc.php @@ -20,6 +20,7 @@ $package[ 'ctrls' ][] = 'delete_task_form'; $package[ 'ctrls' ][] = 'delete_task'; $package[ 'ctrls' ][] = 'dependency_add'; $package[ 'ctrls' ][] = 'dependency_add_form'; +$package[ 'ctrls' ][] = 'dependency_add_filtering'; $package[ 'ctrls' ][] = 'dependency_delete'; $package[ 'ctrls' ][] = 'dependency_delete_form'; $package[ 'ctrls' ][] = 'edit_note_form'; diff --git a/includes/t-tasks/page_controllers.inc.php b/includes/t-tasks/page_controllers.inc.php index a15c628..5d469b5 100644 --- a/includes/t-tasks/page_controllers.inc.php +++ b/includes/t-tasks/page_controllers.inc.php @@ -454,78 +454,90 @@ class Ctrl_DependencyAddForm $page->setTitle( $task->title . ' (task)' ); // Generate form - $form = Loader::Create( 'Form' , 'Add dependency' , 'add-dep' ) + $form = Loader::Create( 'Form' , 'Add dependency' , 'add-dep' , 'Select dependency' ) ->addField( Loader::Create( 'Field' , 'to' , 'hidden' ) - ->setDefaultValue( $id ) ); - $this->addDependencySelector( $form , $task->possibleDependencies , $task->parent_task === null ); - return $form->setURL( 'tasks/view?id=' . $id ) - ->addController( Loader::Ctrl( 'dependency_add' ) ) - ->controller( ); + ->setDefaultValue( $id ) ) + ->setURL( 'tasks/view?id=' . $id ) + ->addController( Loader::Ctrl( 'dependency_add' ) ); + $filters = $this->handleFiltering( $page , $form , $task ); + + return array( $form->controller( ) , $filters ); } - private function addDependencySelector( $form , $possibleDependencies , $topLevel ) + private function handleFiltering( Page $page , Form $form , $task ) { - $form->addField( $select = Loader::Create( 'Field' , 'dependency' , 'select' ) - ->setDescription( 'Dependency to add:' ) - ->addOption( '' , '(please select a task)' ) ); + $fCtrl = Loader::Ctrl( 'dependency_add_filtering' , $form , $task ); + $filters = $this->makeFilteringForm( $form , $fCtrl , $task ); - if ( $topLevel ) { - $depsByItem = $this->getDependenciesByItem( $possibleDependencies ); - $items = $this->getItemsToDisplay( $depsByItem ); - foreach ( $items as $item ) { - $prefix = '-' . str_repeat( '--' , $item->depth ); - $name = $prefix . ' ' . $item->name; - $select->addOption( 'I' . $item->id , $name , true ); - if ( ! array_key_exists( $item->id , $depsByItem ) ) { - continue; - } - - foreach ( $depsByItem[ $item->id ] as $task ) { - $select->addOption( $task->id , $prefix . '-> ' . $task->title ); - } - } - } else { - foreach ( $possibleDependencies as $task ) { - $select->addOption( $task->id , $task->title ); - } + // Was the filters form submitted? + try { + $submitted = $this->getParameter( 'filter-deps-submit' , 'POST' ); + } catch ( ParameterException $e ) { + $submitted = null; + } + if ( $submitted !== null ) { + return $filters->controller( )->handle( $page ); } - return true; + // Was the main form submitted? + try { + $submitted = $this->getParameter( 'to' , 'POST' ); + } catch ( ParameterException $e ) { + $submitted = null; + } + if ( $submitted !== null ) { + $fCtrl->getFiltersFromSelector( ); + } + + // Fake handling the form + $fCtrl->handle( $page ); + return $filters->view( ); } - private function getDependenciesByItem( $possibleDependencies ) + private function makeFilteringForm( Form $form , Controller $ctrl , $task ) { - $dbi = array( ); - foreach ( $possibleDependencies as $pDep ) { - $dbi[ $pDep->item ][] = $pDep; + // Generate filtering form, and handle it immediately + $filters = Loader::Create( 'Form' , 'Apply' , 'filter-deps' , 'Filter dependencies' ) + ->addController( $ctrl ) + ->setAction( '?' ) + ->addField( Loader::Create( 'Field' , 'to' , 'hidden' ) + ->setDefaultValue( $task->id ) ) + ->addField( Loader::Create( 'Field' , 'text' , 'text' ) + ->setDescription( 'Name must contain:' ) + ->setMandatory( false ) ) + ->addField( Loader::Create( 'Field' , 'state' , 'select' ) + ->setDescription( 'Task state:' ) + ->setMandatory( false ) + ->addOption( 'abc' , 'Indifferent' ) + ->addOption( 'ab' , 'Active or blocked' ) + ->addOption( 'a' , 'Active' ) + ->addOption( 'b' , 'Blocked' ) + ->addOption( 'c' , 'Completed' ) ); + if ( $task->parent_task === null ) { + $itemSelect = Loader::Create( 'Field' , 'items' , 'select' ) + ->setDescription( 'Limit to items:' ) + ->setMandatory( false ) + ->addOption( '' , '(Any item)' ); + $this->addItemSelector( $itemSelect ); + $filters->addField( $itemSelect ) + ->addField( Loader::Create( 'Field' , 'item-children' , 'select' ) + ->setDescription( 'Include child items:' ) + ->setMandatory( false ) + ->addOption( '1' , 'Yes' ) + ->addOption( '0' , 'No' ) ); } - return $dbi; + return $filters; } - private function getItemsToDisplay( $depsByItem ) + // FIXME: duplicate code + private function addItemSelector( $select ) { - $dao = Loader::DAO( 'items' ); - $allItems = $dao->getTreeList( ); - $found = array( ); - foreach ( array_keys( $depsByItem ) as $id ) { - if ( array_key_exists( $id , $found ) ) { - continue; - } - $item = $dao->get( $id ); - foreach ( $dao->getLineage( $item ) as $parent ) { - $found[ $parent ] = 1; - } - $found[ $id ] = 1; + $items = Loader::DAO( 'items' )->getTreeList( ); + foreach ( $items as $item ) { + $name = '-' . str_repeat( '--' , $item->depth ) . ' ' . $item->name; + $select->addOption( $item->id , $name ); } - - $result = array( ); - foreach ( $allItems as $item ) { - if ( array_key_exists( $item->id , $found ) ) { - array_push( $result , $item ); - } - } - return $result; } }