Index: trunk/extensions/Translate/MessageGroups.php |
— | — | @@ -1391,66 +1391,111 @@ |
1392 | 1392 | } |
1393 | 1393 | |
1394 | 1394 | /** |
1395 | | - * Returns group strucuted into sub groups. First group in each subgroup is |
1396 | | - * considered as the main group. |
| 1395 | + * Returns a tree of message groups. First group in each subgroup is |
| 1396 | + * the aggregate group. Groups can be nested infinitely, though in practice |
| 1397 | + * other code might not handle more than two (or even one) nesting levels. |
| 1398 | + * One group can exist multiple times in differents parts of the tree. |
| 1399 | + * In other words: [Group1, Group2, [AggGroup, Group3, Group4]] |
| 1400 | + * @throws MWException If cyclic structure is detected. |
1397 | 1401 | * @return array |
1398 | 1402 | */ |
1399 | 1403 | public static function getGroupStructure() { |
1400 | | - global $wgTranslateGroupStructure; |
1401 | | - |
1402 | 1404 | $groups = self::getAllGroups(); |
1403 | | - |
| 1405 | + |
| 1406 | + // Determine the top level groups of the tree |
| 1407 | + $tree = $groups; |
1404 | 1408 | $structure = array(); |
1405 | 1409 | foreach ( $groups as $id => $o ) { |
1406 | | - if ( !MessageGroups::getGroup( $id )->exists() ) { |
| 1410 | + if ( !$o->exists() ) { |
| 1411 | + unset( $groups[$id], $tree[$id] ); |
1407 | 1412 | continue; |
1408 | 1413 | } |
1409 | 1414 | |
1410 | | - foreach ( $wgTranslateGroupStructure as $pattern => $hypergroup ) { |
1411 | | - if ( preg_match( $pattern, $id ) ) { |
1412 | | - // Emulate deepArraySet, because AFAIK php does not have one |
1413 | | - self::deepArraySet( $structure, $hypergroup, $id, $o ); |
1414 | | - // We need to continue the outer loop, because we have finished this item. |
1415 | | - continue 2; |
| 1415 | + if ( $o instanceof AggregateMessageGroup ) { |
| 1416 | + foreach ( $o->getGroups() as $sid => $so ) { |
| 1417 | + unset( $tree[$sid] ); |
1416 | 1418 | } |
1417 | 1419 | } |
1418 | | - |
1419 | | - // Does not belong to any subgroup, just shove it into main level. |
1420 | | - $structure[$id] = $o; |
1421 | 1420 | } |
1422 | 1421 | |
1423 | | - // Sort top-level groups according to labels, not ids |
1424 | | - $labels = array(); |
1425 | | - foreach ( $structure as $id => $data ) { |
1426 | | - // Either it is a group itself, or the first group of the array |
1427 | | - $nid = is_array( $data ) ? key( $data ) : $id; |
1428 | | - $labels[$id] = $groups[$nid]->getLabel(); |
| 1422 | + /* Now we have two things left in $tree array: |
| 1423 | + * - solitaries: top-level non-aggregate message groups |
| 1424 | + * - top-level aggregate message groups */ |
| 1425 | + usort( $tree, array( __CLASS__, 'groupLabelSort' ) ); |
| 1426 | + foreach ( $tree as $index => $group ) { |
| 1427 | + if ( $group instanceof AggregateMessageGroup ) { |
| 1428 | + $tree[$index] = self::subGroups( $group ); |
| 1429 | + } |
1429 | 1430 | } |
1430 | | - natcasesort( $labels ); |
1431 | 1431 | |
1432 | | - $sorted = array(); |
1433 | | - foreach ( array_keys( $labels ) as $id ) { |
1434 | | - $sorted[$id] = $structure[$id]; |
| 1432 | + /* Essentially we are done now. Cyclic groups can cause part of the |
| 1433 | + * groups not be included at all, because they have all unset each |
| 1434 | + * other in the first loop. So now we check if there are groups left |
| 1435 | + * over. */ |
| 1436 | + $used = array(); |
| 1437 | + // Hack to allow passing by reference |
| 1438 | + array_walk_recursive( $tree, array( __CLASS__, 'collectGroupIds' ), array( &$used ) ); |
| 1439 | + $unused = array_diff( array_keys( $groups ), array_keys( $used ) ); |
| 1440 | + if ( count( $unused ) ) { |
| 1441 | + foreach ( $unused as $index => $id ) { |
| 1442 | + if ( !$groups[$id] instanceof AggregateMessageGroup ) { |
| 1443 | + unset( $unused[$index] ); |
| 1444 | + } |
| 1445 | + } |
| 1446 | + |
| 1447 | + // Only list the aggregate groups, other groups cannot cause cycles |
| 1448 | + $participants = implode( ', ', $unused ); |
| 1449 | + throw new MWException( "Found cyclic aggregate message groups: $participants" ); |
1435 | 1450 | } |
1436 | 1451 | |
1437 | | - return $sorted; |
| 1452 | + return $tree; |
1438 | 1453 | } |
1439 | 1454 | |
1440 | | - /** |
1441 | | - * Function do do $array[level1][level2]...[levelN][$key] = $value, if we have |
1442 | | - * the indexes in an array. |
1443 | | - * @param $array |
1444 | | - * @param $indexes array |
1445 | | - * @param $key |
1446 | | - * @param $value |
1447 | | - */ |
1448 | | - public static function deepArraySet( &$array, array $indexes, $key, $value ) { |
1449 | | - foreach ( $indexes as $index ) { |
1450 | | - if ( !isset( $array[$index] ) ) $array[$index] = array(); |
1451 | | - $array = &$array[$index]; |
| 1455 | + /// See getGroupStructure, just collects ids into array |
| 1456 | + public static function collectGroupIds( $value, $key, $used ) { |
| 1457 | + $used[0][$value->getId()] = true; |
| 1458 | + } |
| 1459 | + |
| 1460 | + /// Sorts groups by label value |
| 1461 | + public static function groupLabelSort( $a, $b ) { |
| 1462 | + $al = $a->getLabel(); |
| 1463 | + $bl = $b->getLabel(); |
| 1464 | + return strcasecmp( $al, $bl ); |
| 1465 | + } |
| 1466 | + |
| 1467 | + /// Helper for getGroupStructure |
| 1468 | + protected static function subGroups( AggregateMessageGroup $parent ) { |
| 1469 | + static $recursionGuard = array(); |
| 1470 | + |
| 1471 | + $pid = $parent->getId(); |
| 1472 | + if ( isset( $recursionGuard[$pid] ) ) { |
| 1473 | + $tid = $pid; |
| 1474 | + $path = array( $tid ); |
| 1475 | + do { |
| 1476 | + $tid = $recursionGuard[$tid]; |
| 1477 | + $path[] = $tid; |
| 1478 | + // Until we have gone full cycle |
| 1479 | + } while ( $tid !== $pid ); |
| 1480 | + $path = implode( ' > ', $path ); |
| 1481 | + throw new MWException( "Found cyclic aggregate message groups: $path" ); |
1452 | 1482 | } |
1453 | 1483 | |
1454 | | - $array[$key] = $value; |
| 1484 | + // We don't care about the ids. |
| 1485 | + $tree = array_values( $parent->getGroups() ); |
| 1486 | + usort( $tree, array( __CLASS__, 'groupLabelSort' ) ); |
| 1487 | + // Expand aggregate groups (if any left) after sorting to form a tree |
| 1488 | + foreach ( $tree as $index => $group ) { |
| 1489 | + if ( $group instanceof AggregateMessageGroup ) { |
| 1490 | + $sid = $group->getId(); |
| 1491 | + $recursionGuard[$pid] = $sid; |
| 1492 | + $tree[$index] = self::subGroups( $group ); |
| 1493 | + unset( $recursionGuard[$pid] ); |
| 1494 | + } |
| 1495 | + } |
| 1496 | + |
| 1497 | + // Parent group must be first item in the array |
| 1498 | + array_unshift( $tree, $parent ); |
| 1499 | + return $tree; |
1455 | 1500 | } |
1456 | 1501 | |
1457 | 1502 | } |