<?php

$data = array(
    array(
        "status" => "new",
        "type" => "type1",
        "source" => "source1",
        "other" => "other1",
        "count" => "1",
    ),
    array(
        "status" => "new",
        "type" => "type1",
        "source" => "source1",
        "other" => "other1",
        "count" => "2",
    ),
    array(
        "status" => "new",
        "type" => "type1",
        "source" => "source1",
        "other" => "other1",
        "count" => "5",
    ),
    array(
        "status" => "done",
        "type" => "type1",
        "source" => "source1",
        "other" => "other1",
        "count" => "1",
    ),
    array(
        "status" => "done",
        "type" => "type1",
        "source" => "source2",
        "other" => "other1",
        "count" => "3",
    ),
    array(
        "status" => "done",
        "type" => "type2",
        "source" => "source1",
        "other" => "other1",
        "count" => "1",
    ),
    array(
        "status" => "done",
        "type" => "type2",
        "source" => "source1",
        "other" => "other2",
        "count" => "5",
    ),
    array(
        "status" => "done",
        "type" => "type3",
        "source" => "source1",
        "other" => "other1",
        "count" => "1",
    ),
    array(
        "status" => "done",
        "type" => "type3",
        "source" => "source1",
        "other" => "other2",
        "count" => "5",
    ),
);

$groups = array("status", "type", "source", "other");

function groupBy($data = array(), $groupBy = array())
{

    $seen = array();
    $result = array();

    foreach($data as $node){
        if(!in_array($node[$groupBy[0]], $seen)) {
            // Insert first grouping level as checked level
            $seen[] = $node[$groupBy[0]];

            // Setting starting parent path
            $path = array();
            $path[$groupBy[0]] = $node[$groupBy[0]];

            // Get first group children
            $result[] = getChildren($node[$groupBy[0]], 0, $data, $groupBy, $path);
        }
    }

    return $result;
}

function getChildren($parent, $currentLevel, $data = array(), $groupBy, $path = array())
{
    $nextLevel = $currentLevel + 1;

    // Don't group deeper than needed and escape
    if($nextLevel > count($groupBy)){
        return array();
    }

    $seen = array();
    $children = array();
    $dataSet = array();


    foreach($data as $node){

        // Add next record to $path (to trace each record parent)
        if($groupBy[$nextLevel]) {
            $path[$groupBy[$nextLevel]] = $node[$groupBy[$nextLevel]];
        }

        // Some magic, to check if we are on correct segment (trace the same as should be)
        $goInside = true;
        foreach($path as $fieldName => $fieldValue) {
            if($node[$fieldName] != $fieldValue) {
                $goInside = false;
                break;
            }
        }

        // Just add data
        if($node[$groupBy[$currentLevel]] == $parent && $goInside) {
            $dataSet[] = $node;
        }

        // TODO: I don't know why, but this is working
        if($node[$groupBy[$currentLevel]] == $parent && !in_array($node[$groupBy[$nextLevel]], $seen) && $goInside) {
            $seen[] = $node[$groupBy[$nextLevel]];
            $tempChildren = getChildren($node[$groupBy[$nextLevel]], $nextLevel, $data, $groupBy, $path);
            if($tempChildren && ($tempChildren["data"] || $tempChildren["children"])) {
                $children[] = $tempChildren;
            }
        }
    }

    $result = array(
        'fieldName' => $groupBy[$currentLevel],
        'value' => $parent,
    );

    if($children) {
        $result['children'] = $children;
    } else {
        if($dataSet) {
            $result['data'] = $dataSet;
        }
    }

    return $result;
}

echo "<pre>";
print_r(groupBy($data, $groups));
echo "</pre>";