TheNiceGuy
TheNiceGuy

Reputation: 3730

Sort parent and childs, maximum 2 levels

I am having a rather unique problem on which I'm pulling my teeth since days now.

What I am basically trying to do is to write an importer that can convert bbPress forums to my own Laravel forums. Importing users works perectly fine, the next step are categories. That is a big issue for the following reason:

bbPress can have unlimited sub forums, my own forums can only have 2 levels, meaning that each category can have one sub category. Sub categories cannot have any more sub categories. There is a tag system that replaces the need to have sub-categories of sub-categories. Example:

Support
   Product line --> Available Tags: Product 1, Product 2..

I already created an array out of the bbPress export file and added all data that I need. It's important to keep in mind that the bbPress import can have unlimited sub categories, my example does not have too many, that might be different in other cases. The code needs to be able to handle then anyways. It is obvious to me that there will be somewhat of a recursive use, unfortnately I was not able to figure out where that should be and how it should be. My current example looks like this:

array:14 [
  0 => array:5 [
    "exists" => true
    "post_id" => "2983"
    "parent_id" => 3058
    "data" => array:4 [
      "name" => "Product Downloader"
      "description" => ""
      "icon" => ""
      "locked" => 0
    ]
    "id" => 11
  ]
  1 => array:5 [
    "exists" => true
    "post_id" => "2985"
    "parent_id" => 2983
    "data" => array:4 [
      "name" => "Plugins"
      "description" => ""
      "icon" => ""
      "locked" => 0
    ]
    "id" => 3
  ]
  2 => array:5 [
    "exists" => true
    "post_id" => "2987"
    "parent_id" => 2985
    "data" => array:4 [
      "name" => "Module 1"
      "description" => ""
      "icon" => ""
      "locked" => 0
    ]
    "id" => 4
  ]
  3 => array:5 [
    "exists" => true
    "post_id" => "3058"
    "parent_id" => 0
    "data" => array:4 [
      "name" => "Support"
      "description" => ""
      "icon" => ""
      "locked" => 0
    ]
    "id" => 1
  ]
  4 => array:5 [
    "exists" => true
    "post_id" => "3065"
    "parent_id" => 3058
    "data" => array:4 [
      "name" => "Protection Services"
      "description" => ""
      "icon" => ""
      "locked" => 0
    ]
    "id" => 12
  ]
  5 => array:5 [
    "exists" => true
    "post_id" => "3279"
    "parent_id" => 3058
    "data" => array:4 [
      "name" => "Tutorials & How-Tos"
      "description" => ""
      "icon" => ""
      "locked" => 0
    ]
    "id" => 13
  ]
  6 => array:5 [
    "exists" => true
    "post_id" => "3471"
    "parent_id" => 2985
    "data" => array:4 [
      "name" => "Module 2"
      "description" => ""
      "icon" => ""
      "locked" => 0
    ]
    "id" => 5
  ]
  7 => array:5 [
    "exists" => true
    "post_id" => "3472"
    "parent_id" => 2985
    "data" => array:4 [
      "name" => "Module 3"
      "description" => ""
      "icon" => ""
      "locked" => 0
    ]
    "id" => 6
  ]
  8 => array:5 [
    "exists" => true
    "post_id" => "3475"
    "parent_id" => 2985
    "data" => array:4 [
      "name" => "Module 4"
      "description" => ""
      "icon" => ""
      "locked" => 0
    ]
    "id" => 7
  ]
  9 => array:5 [
    "exists" => true
    "post_id" => "3476"
    "parent_id" => 2985
    "data" => array:4 [
      "name" => "Module 5"
      "description" => ""
      "icon" => ""
      "locked" => 0
    ]
    "id" => 8
  ]
  10 => array:5 [
    "exists" => true
    "post_id" => "3979"
    "parent_id" => 2985
    "data" => array:4 [
      "name" => "Module 6"
      "description" => ""
      "icon" => ""
      "locked" => 0
    ]
    "id" => 9
  ]
  11 => array:5 [
    "exists" => true
    "post_id" => "4683"
    "parent_id" => 3058
    "data" => array:4 [
      "name" => "Protection Services Advanced"
      "description" => ""
      "icon" => ""
      "locked" => 0
    ]
    "id" => 14
  ]
  12 => array:5 [
    "exists" => true
    "post_id" => "6618"
    "parent_id" => 2985
    "data" => array:4 [
      "name" => "Module 7"
      "description" => ""
      "icon" => ""
      "locked" => 0
    ]
    "id" => 10
  ]
]

For this issue, the only relevant keys are post_id and parent_id. parent_id refers to post_id, ignore the id field. What should happen is the following:

The script creates all categories that do not have a parent id, meaning that the parent id is equal to 0. That is the easy part. After that it needs to do the logic with the parent_id. It needs to check if the category that should be imported has a parent_id. If so, it needs to check if that parent has another parent.

As explained, there should only be 2 levels, all other levels should be replaced with tags, that get assigned to the lowest parent that has been created.

It's important to keep in mind that the post_id is not always lower than the parent_id. One could have created the child first, then created the parent and then edited the child and set the parent. In that case the order would be messed up, that means that usort() won't be of much help, at least from what I have tried. The code really needs to "resolve" the parent from the id.

The above example should come out like this:

Support
   Product Downloader --> Tags: Plugins, Module 1 - Module 7
   Protection Services
   Protection Services Advanced
   Tutorials & How-Tos

You can just add a placeholder/dummy code to the category and tag creation, e.g.

$newCategory = create_category($name, $parent_id)

and

$newTag = create_tag($name, $category_id);

Both of those calls will return the primary key of the created category/tag.

The issue is really about getting the data sorted and resolved correctly. I do not add code to the question due to the fact that there is currently no code that would be of any help, I had several attempts but none of them worked out as expected, they all fail at resolving the parents properly and decide if there needs to be a tag instead of creating another category. The json code for the example array is the following:

[{"exists":true,"post_id":"2983","parent_id":3058,"data":{"name":"Product Downloader","description":"","icon":"","locked":0},"id":6},{"exists":false,"post_id":"2985","parent_id":2983,"data":{"name":"Plugins","description":"","icon":"","locked":0},"id":8},{"exists":false,"post_id":"2987","parent_id":2985,"data":{"name":"Module 1","description":"","icon":"","locked":0},"id":9},{"exists":true,"post_id":"3058","parent_id":0,"data":{"name":"Support","description":"","icon":"","locked":0},"id":3},{"exists":true,"post_id":"3065","parent_id":3058,"data":{"name":"Protection Services","description":"","icon":"","locked":0},"id":4},{"exists":false,"post_id":"3279","parent_id":3058,"data":{"name":"Tutorials & How-Tos","description":"","icon":"","locked":0},"id":10},{"exists":false,"post_id":"3471","parent_id":2985,"data":{"name":"Module 2","description":"","icon":"","locked":0},"id":11},{"exists":false,"post_id":"3472","parent_id":2985,"data":{"name":"Module 3","description":"","icon":"","locked":0},"id":12},{"exists":false,"post_id":"3475","parent_id":2985,"data":{"name":"Module 4","description":"","icon":"","locked":0},"id":13},{"exists":false,"post_id":"3476","parent_id":2985,"data":{"name":"Module 5","description":"","icon":"","locked":0},"id":14},{"exists":false,"post_id":"3979","parent_id":2985,"data":{"name":"Module 6","description":"","icon":"","locked":0},"id":15},{"exists":true,"post_id":"4683","parent_id":3058,"data":{"name":"Protection Services Advanced","description":"","icon":"","locked":0},"id":5},{"exists":false,"post_id":"6618","parent_id":2985,"data":{"name":"Module 7","description":"","icon":"","locked":0},"id":16},{"exists":false,"post_id":"6618","parent_id":2985,"data":{"name":"Module 7","description":"","icon":"","locked":0},"id":16}]

# Edit: We can also have the input array have the keys represent the post_id if that is of any help, that would result in the following json data:

{"2983":{"exists":true,"post_id":"2983","parent_id":3058,"data":{"name":"Product Downloader","description":"","icon":"","locked":0},"id":6},"2985":{"exists":true,"post_id":"2985","parent_id":2983,"data":{"name":"Plugins","description":"","icon":"","locked":0},"id":8},"2987":{"exists":true,"post_id":"2987","parent_id":2985,"data":{"name":"Module 1","description":"","icon":"","locked":0},"id":9},"3058":{"exists":true,"post_id":"3058","parent_id":0,"data":{"name":"Support","description":"","icon":"","locked":0},"id":3},"3065":{"exists":true,"post_id":"3065","parent_id":3058,"data":{"name":"Protection Services","description":"","icon":"","locked":0},"id":4},"3279":{"exists":true,"post_id":"3279","parent_id":3058,"data":{"name":"Tutorials & How-Tos","description":"","icon":"","locked":0},"id":10},"3471":{"exists":true,"post_id":"3471","parent_id":2985,"data":{"name":"Module 2","description":"","icon":"","locked":0},"id":11},"3472":{"exists":true,"post_id":"3472","parent_id":2985,"data":{"name":"Module 3","description":"","icon":"","locked":0},"id":12},"3475":{"exists":true,"post_id":"3475","parent_id":2985,"data":{"name":"Module 4","description":"","icon":"","locked":0},"id":13},"3476":{"exists":true,"post_id":"3476","parent_id":2985,"data":{"name":"Module 5","description":"","icon":"","locked":0},"id":14},"3979":{"exists":true,"post_id":"3979","parent_id":2985,"data":{"name":"Module 6","description":"","icon":"","locked":0},"id":15},"4683":{"exists":true,"post_id":"4683","parent_id":3058,"data":{"name":"Protection Services Advanced","description":"","icon":"","locked":0},"id":5},"6618":{"exists":true,"post_id":"6618","parent_id":2985,"data":{"name":"Module 7","description":"","icon":"","locked":0},"id":16}}

Edit 2: Current code:

            $categoriesMapped = [];
            $posts = $categoriesToBeProcessed;
            // find all the categories
            foreach ($posts as $post) {
                if (!$post['parent_id']) {
                    $categoryCreate = $this->category->create($post['data']);
                    $categories[] = $categoryCreate;
                    $categoriesMapped[$categoryCreate->id] = $post['post_id'];
                }
            }

            // now find all the sub-categories and tags
            foreach ($categories as $category) {
                foreach ($posts as $post) {
                    if ($post['parent_id'] == $categoriesMapped[$category->id]) {
                        $data = $post['data'];
                        $data['parent_id'] = $category->id;
                        $thisSubcategory = $this->category->create($data);
                        $thisSubcategory->tags = $this->find_tags($categoriesMapped, $post['post_id'], $posts);
                        $category->subcategories[] = $thisSubcategory;
                    }
                }
            }

Edit 3: Latest code:

private function find_tags($categoriesMapped, $id, $posts) {
    $tags = array();
    foreach ($posts as $post) {
        if ($post['parent_id'] == $categoriesMapped[$id]) {
            $tag = $this->tag->create($post['data']);
            $tags[] = $tag;
            $tag->category()->associate($id);
            // any subcategories below this?
            // TODO: How to fix this?
            $tags = array_merge($tags, $this->find_tags($categoriesMapped, $post['post_id'], $posts));
        }
    }
    return array_unique($tags, SORT_REGULAR);
}

Code in my main function:

 $categoriesMapped = [];
            $posts = $categoriesToBeProcessed;
            // find all the categories
            foreach ($posts as $post) {
                if (!$post['parent_id']) {
                    $categoryCreate = $this->category->create($post['data']);
                    $categories[] = $categoryCreate;
                    $categoriesMapped[$categoryCreate->id] = $post['post_id'];
                }
            }

            // now find all the sub-categories
            foreach ($categories as $category) {
                foreach ($posts as $post) {
                    if ($post['parent_id'] == $categoriesMapped[$category->id]) {
                        $data = $post['data'];
                        $data['parent_id'] = $category->id;
                        $thisSubcategory = $this->category->create($data);
                        $category->subcategories[] = $thisSubcategory;
                        $categoriesMapped[$thisSubcategory->id] = $post['post_id'];
                    }

Any help is highly appreciated.

Upvotes: 0

Views: 75

Answers (1)

Nick
Nick

Reputation: 147156

I think code modeled on this should do what you want:

$posts = json_decode('[{"exists":true,"post_id":"2983","parent_id":3058,"data":{"name":"Product Downloader","description":"","icon":"","locked":0},"id":6},{"exists":false,"post_id":"2985","parent_id":2983,"data":{"name":"Plugins","description":"","icon":"","locked":0},"id":8},{"exists":false,"post_id":"2987","parent_id":2985,"data":{"name":"Module 1","description":"","icon":"","locked":0},"id":9},{"exists":true,"post_id":"3058","parent_id":0,"data":{"name":"Support","description":"","icon":"","locked":0},"id":3},{"exists":true,"post_id":"3065","parent_id":3058,"data":{"name":"Protection Services","description":"","icon":"","locked":0},"id":4},{"exists":false,"post_id":"3279","parent_id":3058,"data":{"name":"Tutorials & How-Tos","description":"","icon":"","locked":0},"id":10},{"exists":false,"post_id":"3471","parent_id":2985,"data":{"name":"Module 2","description":"","icon":"","locked":0},"id":11},{"exists":false,"post_id":"3472","parent_id":2985,"data":{"name":"Module 3","description":"","icon":"","locked":0},"id":12},{"exists":false,"post_id":"3475","parent_id":2985,"data":{"name":"Module 4","description":"","icon":"","locked":0},"id":13},{"exists":false,"post_id":"3476","parent_id":2985,"data":{"name":"Module 5","description":"","icon":"","locked":0},"id":14},{"exists":false,"post_id":"3979","parent_id":2985,"data":{"name":"Module 6","description":"","icon":"","locked":0},"id":15},{"exists":true,"post_id":"4683","parent_id":3058,"data":{"name":"Protection Services Advanced","description":"","icon":"","locked":0},"id":5},{"exists":false,"post_id":"6618","parent_id":2985,"data":{"name":"Module 7","description":"","icon":"","locked":0},"id":16},{"exists":false,"post_id":"6618","parent_id":2985,"data":{"name":"Module 7","description":"","icon":"","locked":0},"id":16}]');

$categories = array();

class Category {
    public $name;
    public $id;
    public $subcategories;

    public function __construct($post) {
        $this->name = $post->data->name;
        $this->id = $post->post_id;
        $this->subcategories = array();
    }
}

class Subcategory {
    public $name;
    public $id;
    public $tags;

    public function __construct($post) {
        $this->name = $post->data->name;
        $this->id = $post->post_id;
        $this->tags = array();
    }
}

class Tag {
    public $name;
    public $id;

    public function __construct($post) {
        $this->name = $post->data->name;
        $this->id = $post->post_id;
    }
}

function find_tags($id, $posts) {
    $tags = array();
    foreach ($posts as $post) {
        if ($post->parent_id == $id) {
            $tags[] = new Tag($post);
            // any subcategories below this?
            $tags = array_merge($tags, find_tags($post->post_id, $posts));
        }
    }
    return array_unique($tags, SORT_REGULAR);  
}

// find all the categories
foreach ($posts as $post) {
    if (!$post->parent_id) {
        $categories[] = new Category($post);
    }
}

// now find all the sub-categories and tags
foreach ($categories as $category) {
    foreach ($posts as $post) {
        if ($post->parent_id == $category->id) {
            $thisSubcategory = new Subcategory($post);
            $thisSubcategory->tags = find_tags($post->post_id, $posts);
            $category->subcategories[] = $thisSubcategory;
        }
    }
}

print_r($categories);

If you need to create all the subcategories before creating any of the tags, you could change the last loop (prefaced by // now find all the sub-categories and tags) to these two. This is quite a bit less efficient which is why I didn't write it this way in the first place.

// now find all the sub-categories
foreach ($categories as $category) {
    foreach ($posts as $post) {
        if ($post->parent_id == $category->id) {
            $category->subcategories[] = new Subcategory($post);
        }
    }
}

// now find all the sub-categories and tags
foreach ($categories as $category) {
    foreach ($category->subcategories as $subcategory) {
        $subcategory->tags = find_tags($subcategory->id, $posts);
    }
}

Upvotes: 1

Related Questions