2929 *
3030 * @author Jose Antonio Cuello Principal <yopli2000@gmail.com>
3131 */
32- class TreeFilter extends AutocompleteFilter
32+ class TreeFilter extends SelectFilter
3333{
3434 /** @var string */
35- public string $ fieldparent ;
35+ public $ fieldcode ;
36+
37+ /** @var string */
38+ public $ fieldparent ;
39+
40+ /** @var string */
41+ public $ fieldtitle ;
42+
43+ /** @var string */
44+ public $ table ;
45+
46+ /** @var array */
47+ public $ where ;
3648
3749 /**
3850 * @param string $key
@@ -46,9 +58,13 @@ class TreeFilter extends AutocompleteFilter
4658 */
4759 public function __construct (string $ key , string $ field , string $ label , string $ table , string $ fieldparent , string $ fieldcode = '' , string $ fieldtitle = '' , array $ where = [])
4860 {
49- parent ::__construct ($ key , $ field , $ label , $ table , $ fieldcode , $ fieldtitle , $ where );
50-
61+ $ this ->table = $ table ;
5162 $ this ->fieldparent = $ fieldparent ;
63+ $ this ->fieldcode = empty ($ fieldcode ) ? $ field : $ fieldcode ;
64+ $ this ->fieldtitle = empty ($ fieldtitle ) ? $ this ->fieldcode : $ fieldtitle ;
65+ $ this ->where = $ where ;
66+
67+ parent ::__construct ($ key , $ field , $ label , $ this ->loadValues ());
5268 }
5369
5470 /**
@@ -95,4 +111,100 @@ private function getIds(): array
95111 }
96112 return $ result ;
97113 }
114+
115+ private function loadValues (): array
116+ {
117+ // obtener todos los elementos (cod, titulo, codPadre)
118+ $ dataBase = new DataBase ();
119+ $ sql = "SELECT " . $ this ->fieldcode . ", " . $ this ->fieldtitle . ", " . $ this ->fieldparent . " FROM " . $ this ->table ;
120+
121+ // aplicar where
122+ if (!empty ($ this ->where )) {
123+ if (isset ($ this ->where [0 ]) && $ this ->where [0 ] instanceof DataBaseWhere) {
124+ $ sql .= DataBaseWhere::getSQLWhere ($ this ->where );
125+ } else {
126+ $ sql .= ' WHERE ' . implode (' AND ' , $ this ->where );
127+ }
128+ }
129+ $ sql .= " ORDER BY " . $ this ->fieldtitle ;
130+
131+ // realizar consulta, construir y "maquillar" el árbol
132+ $ rows = $ dataBase ->select ($ sql );
133+ $ tree = $ this ->buildTree ($ rows );
134+ return $ this ->flattenTree ($ tree );
135+ }
136+
137+ /**
138+ * obtiene los elementos en formato [[cod = x, titulo = x, codPadre = x]...] y los devuelve
139+ * en un array de [codPadre = [codHijo...], ...] en donde codHijo es [cod = x, titulo = x, codPadre = x]
140+ * y el elemento padre de todos está dentro de la clave 'ROOT'
141+ */
142+ private function buildTree (array $ elements ): array
143+ {
144+ // crear tabla con solo clave id, valor true
145+ $ ids = [];
146+ foreach ($ elements as $ element ) {
147+ $ ids [$ element [$ this ->fieldcode ]] = true ;
148+ }
149+
150+ // agrupar en una tabla codPadre = [codHijo...] con cada elemento
151+ $ grouped = [];
152+ foreach ($ elements as $ element ) {
153+ $ pid = $ element [$ this ->fieldparent ];
154+ if (empty ($ pid ) || !isset ($ ids [$ pid ])) {
155+ $ pid = 'ROOT ' ;
156+ }
157+ $ grouped [$ pid ][] = $ element ;
158+ }
159+
160+ return $ this ->buildTreeRecursive ($ grouped , 'ROOT ' );
161+ }
162+
163+ /**
164+ * Recibe un array de [codPadre = [codHijo...], ...] en donde codHijo es [cod = x, titulo = x, codPadre = x]
165+ * y el argumento del id del padre o rama a construir.
166+ *
167+ * Hace una llamada recursiva a la función y construye un array en forma de arbol,
168+ * crea una rama y hace una llamada recursiva a las siguientes ramas.
169+ *
170+ * Devuelve un array en forma de arbol al final, con children = [[cod = x, titulo = x, codPadre = x, children = [elementosHijos...]]] donde
171+ * elementosHijos es otro [[cod = x, titulo = x, codPadre = x, children = [elementosHijos...]]].
172+ */
173+ private function buildTreeRecursive (array &$ grouped , $ parentId ): array
174+ {
175+ $ branch = [];
176+ if (isset ($ grouped [$ parentId ])) {
177+ foreach ($ grouped [$ parentId ] as $ element ) {
178+ $ children = $ this ->buildTreeRecursive ($ grouped , $ element [$ this ->fieldcode ]);
179+ if ($ children ) {
180+ $ element ['children ' ] = $ children ;
181+ }
182+ $ branch [] = $ element ;
183+ }
184+ }
185+ return $ branch ;
186+ }
187+
188+ /**
189+ * Recibe un array recursivo de elementos $tree = [elemento1, elemento2, elemento3...] donde
190+ * cada elemento tiene a su vez un $elemento['children'] = $tree con sus hijos.
191+ *
192+ * Lo formatea para una salida más intuitiva
193+ */
194+ private function flattenTree (array $ tree , int $ level = 0 ): array
195+ {
196+ $ result = [];
197+ foreach ($ tree as $ node ) {
198+ $ prefix = str_repeat (' ' , $ level );
199+ $ result [] = [
200+ 'code ' => $ node [$ this ->fieldcode ],
201+ // si es el nivel superior entonces no mostrar espacio y bolita
202+ 'description ' => ($ level !== 0 ? $ prefix . '• ' : '' ) . htmlspecialchars ($ node [$ this ->fieldtitle ])
203+ ];
204+ if (isset ($ node ['children ' ])) {
205+ $ result = array_merge ($ result , $ this ->flattenTree ($ node ['children ' ], $ level + 1 ));
206+ }
207+ }
208+ return $ result ;
209+ }
98210}
0 commit comments