Loop through menu items recursively starting from second level - php

I have navigation which looks like this in the system:
At the moment, I am able to print an array of objects containing the menu items recursively, and the code looks like this:
public function get() {
$current_path = \Drupal::service('path.current')->getPath();
$path_items = explode ('/',$current_path);
$menu_array = [];
$menu = $path_items[4];
if(!empty ($menu) ){
$sub_nav = \Drupal::menuTree()->load($menu, new \Drupal\Core\Menu\MenuTreeParameters());
foreach($sub_nav as $nav){
array_push($menu_array, $nav);
}
$manipulators = array(
array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'),
);
$sub_nav = \Drupal::menuTree()->transform($sub_nav, $manipulators);
$this->generateSubMenuTree($menu_array, $sub_nav);
$response = array_values($menu_array);
}
return (new ResourceResponse($response))->addCacheableDependency(array('#cache' => array('max-age' => 0)));
}
private function generateSubMenuTree(&$output, &$input, $parent = FALSE) {
$input = array_values($input);
$items = 0;
foreach($input as $key => $item) {
//If menu element disabled skip this branch
if ($item->link->isEnabled()) {
if ($item->link instanceof \Drupal\menu_link_content\Plugin\Menu\MenuLinkContent) {
$uuid = $item->link->getDerivativeId();
$entity = \Drupal::service('entity.repository')
->loadEntityByUuid('menu_link_content', $uuid);
}
$link_data['name'] = $item->link->getTitle();
if(!empty($item->link->pluginDefinition['url'])){
$link_data['href'] = $item->link->pluginDefinition['url'];
}
//If not root element, add as child
if ($parent === FALSE) {
$output[$key] = $link_data;
} else {
$output['children'][$key] = $link_data;
}
if ($item->hasChildren) {
if ($item->depth == 1) {
$this->generateSubMenuTree($output[$key], $item->subtree, $key);
} else {
$this->generateSubMenuTree($output['children'][$key], $item->subtree, $key);
}
}
$items ++;
}
}
}
The code above results into the following:
However my specification now changed in the sense that for Nav Data and Departments, I should only start including from the child elements onwards.
The result should be something like this:
{
[
{
name : "Shop"
children : [
{
name : "Promotions"
tabletColumn : 0
desktopColumn : 0
children : [...] 3 items
},
{ ... }
]
},
{...}
],
[
{
name : "Personal"
children : [...] 0 items
selected : true
}
],
{...}
{...}
}
Have been struggling with this logic for 24 hours already.

Related

Implementing Hooks, add_filter and apply_filters

So i try to make a hooking API like wordpress add_filter and apply_filters,
So this is my implementation
My problem is that apply_filters does not filter the hook
$hook = new Hook();
$hook->add_filter('filter',function($test) {
return ' stackoverflow';
});
$hook->add_filter('filter',function($test) {
return $test .' world';
},1);
echo $hook->apply_filters('filter','hello');
// Result is hello world
What's wrong with my code, I want to result like this
hello world stackoverflow
For adding filters
public function add_filter(string $tag,mixed $object,int $priority = 10,int $args_limit = 1): void
{
$hooks = static::$hooks;
if(isset($hooks[$tag])) {
static::$hooks[$tag]['sorted'] = false;
static::$hooks[$tag]['priority'][] = $priority;
static::$hooks[$tag]['object'][] = $object;
static::$hooks[$tag]['args_limit'][] = $args_limit;
} else {
static::$hooks[$tag] = [
'sorted' => true,
'priority' => [$priority],
'object' => [$object],
'args_limit' => [$args_limit]
];
}
}
and for applying the filters
public function apply_filters(string $tag,mixed $value,mixed ...$args): mixed
{
$hooks = static::$hooks;
// Shift value to args
array_unshift($args,$value);
$num_args = count($args);
if(isset($hooks[$tag])) {
$hooks = $hooks[$tag];
if(!$hooks['sorted']) {
// Sort filter by priority
array_multisort(
$hooks['priority'],
SORT_NUMERIC,
$hooks['object'],
$hooks['args_limit']
);
$hooks['sorted'] = true;
}
foreach($hooks['object'] as $key => $object) {
$args_limit = $hooks['args_limit'][$key];
if(0 === $args_limit) {
$value = call_user_func($object);
} elseif($args_limit >= $num_args) {
$value = call_user_func_array($object,$args);
} else {
// Slice arguments if not meet from the second statement
$value = call_user_func_array($object,array_slice($args,0,$args_limit));
}
}
}
return $value;
}
So I try to look wordpress core code but the too complicated.
Thanks
Finally solved it! , I just forgot that $value doesn't pass to the callbacks
Here's the fix, for future references
public function apply_filters(string $tag,mixed $value,mixed ...$args): mixed
{
$hooks = static::$hooks;
// +1 for $value parameter
$num_args = count($args) + 1;
if(isset($hooks[$tag])) {
$hooks = $hooks[$tag];
if(!$hooks['sorted']) {
// Sort filter by priority
array_multisort(
$hooks['priority'],
SORT_NUMERIC,
$hooks['object'],
$hooks['args_limit']
);
$hooks['sorted'] = true;
}
foreach($hooks['object'] as $key => $object) {
$args_limit = $hooks['args_limit'][$key];
if(0 === $args_limit) {
$value = call_user_func($object);
} elseif($args_limit >= $num_args) {
$value = call_user_func($object,$value,...$args);
} else {
// Slice arguments if not meet from the second statement
$slice = array_slice($args,0,$args_limit);
$value = call_user_func($object,$value,...$slice);
}
}
}
return $value;
}

WooCommerce: get a shipping methods by Country code

I'm trying to get the shipping available methods by country code that giving in the request, in the above code I'm facing an issue/conflict
That the available locations can be both a country or a continent so I need to check if this country code is a part of some continent.
the problem is with the first zone I get all methods in all zones not just within the first ( the callback return ).
The second zone which has the continents/countries ( rest of the world ) I get no issues with it but as far I guess that's because its the end of the loop. ( as I have two zones for now )
add_action("rest_api_init", function () {
register_rest_route(
"test-api/v1",
"shipping-cost",
array(
'callback' => function ($req) {
$country_code = $req->get_param('country_code');
$quantity = $req->get_param('quantity');
$shipping_cost = 0;
$methodes = [];
if (class_exists('WC_Shipping_Zones')) {
$all_zones = WC_Shipping_Zones::get_zones();
if (!empty($all_zones)) {
foreach ($all_zones as $zone) {
if (!empty($zone['zone_locations'])) {
foreach ($zone['zone_locations'] as $location) {
$wc_contries = new WC_Countries();
$continent_code = $wc_contries->get_continent_code_for_country($country_code);
if ($country_code === $location->code || $continent_code === $location->code) {
if (!empty($zone['shipping_methods'])) {
$shipping_method_ctrl = new WC_REST_Shipping_Zone_Methods_Controller();
foreach ($zone['shipping_methods'] as $flat_rate) {
$shipping_method = $shipping_method_ctrl->prepare_item_for_response($flat_rate, $req);
$methodes[] = (object) $shipping_method;
}
}
}
}
}
}
}
}
return $methodes;
}
)
);
});
Here's the answer: ( adding break with the number of the array want it to be stop )
Register_rest_route(
"kefan-api/v1",
"shipping-cost",
array(
'callback' => function ($req) {
$country_code = $req->get_param('country_code');
$methodes = [];
if (class_exists('WC_Shipping_Zones')) {
$all_zones = WC_Shipping_Zones::get_zones();
if (!empty($all_zones)) {
foreach ($all_zones as $zone) {
if (!empty($zone['zone_locations'])) {
foreach ($zone['zone_locations'] as $location) {
$wc_contries = new WC_Countries();
$continent_code = $wc_contries->get_continent_code_for_country($country_code);
if ($country_code === $location->code || $continent_code === $location->code) {
if (!empty($zone['shipping_methods'])) {
$shipping_method_ctrl = new WC_REST_Shipping_Zone_Methods_Controller();
foreach ($zone['shipping_methods'] as $flat_rate) {
$shipping_method = $shipping_method_ctrl->prepare_item_for_response($flat_rate, $req);
$methodes[] = (object) $shipping_method;
}
break 2;
}
}
}
}
}
}
}
return $methodes;
}
)
);

Array skips the numeric index when adding element?

I've faced funny bug when modifying the e-store engine:
// ...
$this->toolbar_title[] = 'Products';
// ...
print_r($this->toolbar_title);
/*
Array (
[0] => Products
)
*/
$this->toolbar_title[] = 'Filtered by: name';
print_r($this->toolbar_title);
/*
Array
(
[0] => Products
[2] => Filtered by: name
)
*/
// ...
wat??? where is the "1" index??
Tried to reproduce this in clean stand-alone php script - nope! the added element has index "1" as expected. but when doing the same within the engine code - new element has index "2".
There are no setters, no "[]" overloading found, even no any access to the $this->toolbar_title elements by index, only pushing via [];
What the magic is this? What and where should I seek to find the reason?
PHP 5.6, PrestaShop 1.6 engine.
Thanks a lot in advance for any clue.
UPD: the exact code fragment from engine
if ($filter = $this->addFiltersToBreadcrumbs()) {
echo'131-';print_r($this->toolbar_title);
$this->toolbar_title[] = $filter;
echo'132-';var_dump($this->toolbar_title);
}
where addFiltersToBreadcrumbs returns the string and make NO any access to toolbar_title
UPD2:
public function addFiltersToBreadcrumbs()
{
if ($this->filter && is_array($this->fields_list)) {
$filters = array();
foreach ($this->fields_list as $field => $t) {
if (isset($t['filter_key'])) {
$field = $t['filter_key'];
}
if (($val = Tools::getValue($this->table.'Filter_'.$field)) || $val = $this->context->cookie->{$this->getCookieFilterPrefix().$this->table.'Filter_'.$field}) {
if (!is_array($val)) {
$filter_value = '';
if (isset($t['type']) && $t['type'] == 'bool') {
$filter_value = ((bool)$val) ? $this->l('yes') : $this->l('no');
} elseif (isset($t['type']) && $t['type'] == 'date' || isset($t['type']) && $t['type'] == 'datetime') {
$date = Tools::unSerialize($val);
if (isset($date[0])) {
$filter_value = $date[0];
if (isset($date[1]) && !empty($date[1])) {
$filter_value .= ' - '.$date[1];
}
}
} elseif (is_string($val)) {
$filter_value = htmlspecialchars($val, ENT_QUOTES, 'UTF-8');
}
if (!empty($filter_value)) {
$filters[] = sprintf($this->l('%s: %s'), $t['title'], $filter_value);
}
} else {
$filter_value = '';
foreach ($val as $v) {
if (is_string($v) && !empty($v)) {
$filter_value .= ' - '.htmlspecialchars($v, ENT_QUOTES, 'UTF-8');
}
}
$filter_value = ltrim($filter_value, ' -');
if (!empty($filter_value)) {
$filters[] = sprintf($this->l('%s: %s'), $t['title'], $filter_value);
}
}
}
}
if (count($filters)) {
return sprintf($this->l('filter by %s'), implode(', ', $filters));
}
}
}
For php, if you unset one index in array, the index will appear discontinuous growth.
$this->toolbar_title = array_values($this->toolbar_title);
will rebuild the index.

Cannot use object of type stdClass as array using laravel

I have made function to display all my privileges against the menu
My Controller code below:
<?php
class PrivilegesController extends Controller
{
public function getAllPrivileges()
{
$privileges = DB::table('prev_definition')->orderBy('id', 'asc')->get();
$privileges = $this->build_heirarchy($privileges);
$resultArray = ['status' => true, 'message' => 'Privileges found!', 'data' => $privileges];
return json_encode($resultArray);
}
public function build_heirarchy($result_set, $parent_id = 0)
{
$rs = array();
foreach ($result_set as $row) {
$row['is_checked'] = 1; // here is error
if ($row['parent_id'] == $parent_id) {
$children = $this->build_heirarchy($result_set, $row['id']);
if ($children) {
if ($children[0]['type'] == 'menu') {
$type = 'submenu';
} else {
$type = 'permission';
}
$row[$type] = $children;
}
$rs[] = $row;
}
}
return $rs;
}
}
?>
But I'm getting error of Cannot use object of type stdClass as array I'm so confused how I can make it happen and working
your help will be highly appreciated!
{
"data": [
{
"created_at": "2019-05-20 15:48:34",
"deletedAt": null,
"display_group": "patient",
"icon": "NULL",
"id": 297,
"isDeleted": null,
"is_checked": 1,
"parent_id": 0,
"priv_key": "can_access_patient",
"priv_title": "Patient",
"type": "menu",
"updated_at": "2019-05-20 15:48:34"
}
],
"message": "Privileges found!",
"status": true
}
class PrivilegesController extends Controller
{
public function getAllPrivileges()
{
$privileges = DB::table('prev_definition')->orderBy('id', 'asc')->get();
$privileges = $this->build_heirarchy($privileges);
$resultArray = ['status' => true, 'message' => 'Privileges found!', 'data' => $privileges];
return json_encode($resultArray);
}
function build_heirarchy($result_set, $parent_id = 0)
{
$rs = array();
foreach ($result_set as $row) {
$row->is_checked = 1; // here is error
if ($row->parent_id == $parent_id) {
$children = $this->build_heirarchy($result_set, $row->id);
if ($children) {
if ($children[0]->type == 'menu') {
$type = 'submenu';
} else {
$type = 'permission';
}
$row->{$type} = $children; // keys to object can only set using curly braces if variable
}
$rs[] = $row;
}
}
return $rs;
}
}
This is my final controller code and i have also shared the response with you can look into this and can let me know if there is any more changges
As per your suggestions and comments I modified your code, once check and let me know.
class PrivilegesController extends Controller
{
public function getAllPrivileges()
{
$privileges = DB::table('prev_definition')->orderBy('id', 'asc')->get();
// add below line, keep your old code as it is and check
$privileges = json_decode(json_encode($privileges), true);
$privileges = $this->build_heirarchy($privileges);
$resultArray = ['status' => true, 'message' => 'Privileges found!', 'data' => $privileges];
return json_encode($resultArray);
}
public function build_heirarchy($result_set, $parent_id = 0)
{
$rs = array();
foreach ($result_set as $row) {
$row['is_checked'] = 1; // here is error
if ($row['parent_id'] == $parent_id) {
$children = $this->build_heirarchy($result_set, $row['id']);
if ($children) {
if ($children[0]['type'] == 'menu') {
$type = 'submenu';
} else {
$type = 'permission';
}
$row[$type] = $children;
}
$rs[] = $row;
}
}
return $rs;
}
}
EDIT
There is only record with parent id 0, so it will create only one parent record, and recursively it will have appended child data. Check this.
Now for parent id 1 it has 2 records in screenshot, so two elements will appended inside parent single most element.
Now for parent id 2 it has 4 records in screenshot, so 4 elements will be appended inside element with id 2.
This will go on inside to inside of every element considering parent
id.
Subsequently all data will be appended to every parent of it. So index is only one and data is appended recursively to its every relevant parent.
I hope I am clear.
if ($children)
{
if($children[0]->type=='menu')
$type = 'submenu';
else
$type = 'permission';
$row[$type] = $children;
}

How to get static page dropdown in OctoberCMS with get page tree?

I am open this based from my question in my own comment from Static page dropdown within static page in OctoberCMS.
I have problem when adding $pageList->getPageTree(true). The child pages did not display. My current code is repeating foreach from $pageObject->subpages which is not a good practice.
Below here is my code example:
Plugin.php
<?php namespace MyPlugin\CustomPlugin
use System\Classes\PluginBase;
public function boot() {
\RainLab\Pages\Classes\Page::extend(function($model) {
$model->addDynamicMethod('getPageOptions', function() {
$theme = \Cms\Classes\Theme::getEditTheme();
$pageList = new \RainLab\Pages\Classes\PageList($theme);
$pages = [];
foreach ($pageList->getPageTree(true) as $name => $pageObject) {
$pages[$pageObject->page->url] = $pageObject->page->title;
if ($pageObject->subpages) {
foreach ($pageObject->subpages as $name => $pageObject) {
$pages[$pageObject->page->url] = ' ' . $pageObject->page->title;
if ($pageObject->subpages) {
foreach ($pageObject->subpages as $name => $pageObject) {
$pages[$pageObject->page->url] = ' ' . $pageObject->page->title;
}
}
}
}
}
return $pages;
});
});
}
Appreciate if anyone could help.
may be this will help you.
function onStart() {
$theme = \Cms\Classes\Theme::getEditTheme();
$pageList = new \RainLab\Pages\Classes\PageList($theme);
$treePageList = $pageList->getPageTree(true);
$pages = [];
$this->getRecursivePage($pages, $treePageList);
dd($pages);
}
function getRecursivePage(&$pages, $subpages, $level = 0) {
$level++;
foreach($subpages as $pageArr) {
$pages[$pageArr->page->url] =
str_repeat('-',$level) . ' ' . $pageArr->page->title;
if(count($pageArr->subpages) > 0) {
$this->getRecursivePage($pages, $pageArr->subpages, $level);
}
}
}
Output
array:9 [▼
"/content" => "- Content"
"/content/pages" => "-- Static Pages"
"/content/content" => "-- Content"
"/content/models" => "-- Models"
"/content/urls" => "-- URLs"
"/content/urls/tesets" => "--- tesets"
"/test-sp" => "- test-sp"
"/test-sp/oks" => "-- oks"
"/test" => "- test"
]
here you can just replace - to or just remove that part [ I added because you had that in code so, may be useful to you. ]
For your code
public function boot() {
\RainLab\Pages\Classes\Page::extend(function($model) {
$model->addDynamicMethod('getPageOptions', function() {
$theme = \Cms\Classes\Theme::getEditTheme();
$pageList = new \RainLab\Pages\Classes\PageList($theme);
$treePageList = $pageList->getPageTree(true);
$pages = [];
$this->getRecursivePage($pages, $treePageList);
return $pages;
});
});
}
public function getRecursivePage(&$pages, $subpages, $level = 0) {
$level++;
foreach($subpages as $pageArr) {
$pages[$pageArr->page->url] =
str_repeat('-',$level) . ' ' . $pageArr->page->title;
if(count($pageArr->subpages) > 0) {
$this->getRecursivePage($pages, $pageArr->subpages, $level);
}
}
}
If any doubt please comment.

Categories