179 lines
5.4 KiB
Elixir
179 lines
5.4 KiB
Elixir
![]() |
defmodule Content.UpdateMenu do
|
||
|
alias Content.{Menu, Post, Postmeta, Repo, TermRelationship}
|
||
|
alias Ecto.Multi
|
||
|
|
||
|
import Ecto.Query
|
||
|
|
||
|
def run(id, new_menu_params) do
|
||
|
current_posts = Menu.nav_menu_items_for_id(id)
|
||
|
post_ids_in_new_menu = recursive_post_ids(new_menu_params)
|
||
|
deleted_post_ids =
|
||
|
current_posts
|
||
|
|> Enum.reject(& &1."ID" in post_ids_in_new_menu)
|
||
|
|> Enum.map(& &1."ID")
|
||
|
|
||
|
Multi.new()
|
||
|
|> process_nodes(id, 0, new_menu_params |> add_order())
|
||
|
|> Multi.delete_all(:stale_nodes, from(p in Post, where: p."ID" in ^deleted_post_ids))
|
||
|
|> Repo.transaction()
|
||
|
end
|
||
|
|
||
|
def add_order(tree) do
|
||
|
{_next_order, nodes} = add_order_starting_at(tree, 1)
|
||
|
|
||
|
nodes
|
||
|
end
|
||
|
|
||
|
def add_order_starting_at(tree, starting_at) do
|
||
|
tree
|
||
|
|> Enum.reduce({starting_at, []}, fn node, {order, new_nodes} ->
|
||
|
{next_order, new_child_nodes} = add_order_starting_at(node["children"], order + 1)
|
||
|
new_node =
|
||
|
node
|
||
|
|> Map.merge(%{
|
||
|
"order" => order,
|
||
|
"children" => new_child_nodes,
|
||
|
})
|
||
|
{next_order, new_nodes ++ [new_node]}
|
||
|
end)
|
||
|
end
|
||
|
|
||
|
defp process_nodes(multi, menu_id, parent_id, nodes) do
|
||
|
nodes
|
||
|
|> Enum.reduce(multi, fn node, m ->
|
||
|
case node["post_id"] do
|
||
|
nil ->
|
||
|
create_node(m, menu_id, parent_id, node)
|
||
|
_id ->
|
||
|
update_node(m, menu_id, parent_id, node)
|
||
|
end
|
||
|
end)
|
||
|
end
|
||
|
|
||
|
defp create_node(multi, menu_id, parent_id, node) do
|
||
|
post =
|
||
|
Post.changeset(
|
||
|
%Post{},
|
||
|
%{
|
||
|
post_author: 1,
|
||
|
post_title: node["title"],
|
||
|
post_status: "publish",
|
||
|
comment_status: "closed",
|
||
|
ping_status: "closed",
|
||
|
menu_order: node["order"],
|
||
|
post_type: "nav_menu_item",
|
||
|
comment_count: 0,
|
||
|
}
|
||
|
)
|
||
|
|
||
|
step_name = "#{parent_id}.create_node.#{node["order"]}"
|
||
|
|
||
|
multi
|
||
|
|> Multi.insert(step_name, post)
|
||
|
|> Multi.run("#{step_name}.term_relationship", fn _repo, %{^step_name => post} ->
|
||
|
tr =
|
||
|
TermRelationship.changeset(
|
||
|
%TermRelationship{},
|
||
|
%{
|
||
|
object_id: post."ID",
|
||
|
term_taxonomy_id: menu_id,
|
||
|
term_order: 0,
|
||
|
}
|
||
|
)
|
||
|
|
||
|
Repo.insert(tr)
|
||
|
end)
|
||
|
|> Multi.merge(fn %{^step_name => post} ->
|
||
|
Multi.new()
|
||
|
|> insert_metas(type_of_node(node), post, parent_id, node)
|
||
|
end)
|
||
|
|> Multi.merge(fn %{^step_name => post} ->
|
||
|
Multi.new()
|
||
|
|> process_nodes(menu_id, post."ID", node["children"])
|
||
|
end)
|
||
|
end
|
||
|
|
||
|
defp insert_metas(multi, "post", post, parent_id, node) do
|
||
|
multi
|
||
|
|> update_meta(post."ID", "_menu_item_type", "post_type")
|
||
|
|> update_meta(post."ID", "_menu_item_object", "page")
|
||
|
|> update_meta(post."ID", "_menu_item_object_id", node["target_id"])
|
||
|
|> update_meta(post."ID", "_menu_item_menu_item_parent", parent_id)
|
||
|
end
|
||
|
|
||
|
defp insert_metas(multi, "category", post, parent_id, node) do
|
||
|
multi
|
||
|
|> update_meta(post."ID", "_menu_item_type", "taxonomy")
|
||
|
|> update_meta(post."ID", "_menu_item_object", "category")
|
||
|
|> update_meta(post."ID", "_menu_item_object_id", node["target_id"])
|
||
|
|> update_meta(post."ID", "_menu_item_menu_item_parent", parent_id)
|
||
|
end
|
||
|
|
||
|
defp insert_metas(multi, "link", post, parent_id, node) do
|
||
|
multi
|
||
|
|> update_meta(post."ID", "_menu_item_type", "custom")
|
||
|
|> update_meta(post."ID", "_menu_item_object", "custom")
|
||
|
|> update_meta(post."ID", "_menu_item_object_id", post."ID")
|
||
|
|> update_meta(post."ID", "_menu_item_url", node["url"])
|
||
|
|> update_meta(post."ID", "_menu_item_menu_item_parent", parent_id)
|
||
|
end
|
||
|
|
||
|
defp type_of_node(%{"url" => url}) when url != nil, do: "link"
|
||
|
defp type_of_node(%{"related_item" => %{"resource" => "posts"}}), do: "post"
|
||
|
defp type_of_node(%{"related_item" => %{"resource" => "category"}}), do: "category"
|
||
|
|
||
|
defp update_node(multi, menu_id, parent_id, node) do
|
||
|
multi
|
||
|
|> update_meta(node["post_id"], "_menu_item_menu_item_parent", parent_id)
|
||
|
|> update_order(node["post_id"], node["order"])
|
||
|
|> process_nodes(menu_id, node["post_id"], node["children"])
|
||
|
end
|
||
|
|
||
|
defp update_meta(multi, post_id, meta_key, new_value) when is_integer(new_value),
|
||
|
do: update_meta(multi, post_id, meta_key, new_value |> Integer.to_string())
|
||
|
|
||
|
defp update_meta(multi, post_id, meta_key, new_value) do
|
||
|
step_name = "#{post_id}.update_meta.#{meta_key}"
|
||
|
type = Postmeta.__schema__(:type, :meta_value)
|
||
|
cast_value = Ecto.Type.cast(type, new_value)
|
||
|
|
||
|
Postmeta
|
||
|
|> where([pm], pm.meta_key == ^meta_key)
|
||
|
|> where([pm], pm.post_id == ^post_id)
|
||
|
|> Repo.one()
|
||
|
|> case do
|
||
|
nil ->
|
||
|
multi
|
||
|
|> Multi.insert(
|
||
|
step_name,
|
||
|
Postmeta.changeset(
|
||
|
%Postmeta{},
|
||
|
%{
|
||
|
post_id: post_id,
|
||
|
meta_key: meta_key,
|
||
|
meta_value: new_value
|
||
|
}
|
||
|
)
|
||
|
)
|
||
|
%{meta_value: ^cast_value} ->
|
||
|
# No change needed
|
||
|
multi
|
||
|
meta ->
|
||
|
multi
|
||
|
|> Multi.update(step_name, Postmeta.changeset(meta, %{meta_value: new_value}))
|
||
|
end
|
||
|
end
|
||
|
|
||
|
defp update_order(multi, post_id, new_order) do
|
||
|
step_name = "#{post_id}.update_order"
|
||
|
|
||
|
multi
|
||
|
|> Multi.update_all(step_name, from(p in Post, where: p."ID" == ^post_id), [set: [menu_order: new_order]])
|
||
|
end
|
||
|
|
||
|
defp recursive_post_ids(params) do
|
||
|
params
|
||
|
|> Enum.flat_map(& [&1["post_id"]|recursive_post_ids(&1["children"])])
|
||
|
end
|
||
|
end
|