Filtering through a foreach function - php

I am pulling my hair out here, I simply cannot get this to work.
I need to do a foreach loop to get all authors in a website, I then need to filter the ones with 0 published articles out and then echo the authors with articles into a UL LI with a special tag for the last author in the array:
My code at the moment has two functions, one to prefilter all authors that have at least one article and then in the second function count the number of authors left in the filtered array to then give the last entry in the array a special li tag. Code so far:
/*********************
Echo Filtered List
*********************/
function filtered_list() {
$authors = get_users('orderby=nicename');
$all_authors = array();
if ( count_user_posts( $author->id ) >= 1 ) {
return true;
}
}
function contributors() {
$i = 0;
filtered_list();
$len = count($all_authors);
foreach ($all_authors as $author ) {
if ( count_user_posts( $author->id ) >= 1 ) {
if ($i == $len - 1) {
echo "<li class='author-last clearfix'>";}
else {
echo "<li class='author clearfix'>";}
$i++;

If you read through your code you would probably see why it doesn't work.
First: Scopes
Read about variable scopes in the PHP manual. Basically, a variable declared inside a function is only available inside that function, so $all_authors is null inside contributors() as it has never been initialized.
The filtered_list function should return a filtered list of authors, so you should loop, though $authors and add the author to $all_authors if, and only if she has 1 or more posts. After the loop, return the array.
Now you can get the filtered list by setting the return value of the fist function to the $all_authors in contributors (or better yet, just call them $authors).
Now you are ready to iterate over the list of authors and find their post. To do this, you need two loops. One for authors, and one for the posts.
foreach author in authors
foreach post in author->posts
if post is last post
print special stuff
else
print normal stuff
endif
endforeach
endforeach
Hope this helps, and that you'll learn something from it. Point is: Read though your code line by line and explain to yourself what it does.

Related

Laravel 9.x creating a table row by nesting loops with Many-to-Many relationships

I'm having a CRUD issue. I'm not sure of the right syntax to make my Controller create table rows with the right relationships to other tables, namely while in a loop.
I'm creating a dictionary where a Term hasMany Glosses, a Gloss belongsToMany Sentences (many Sentences may have a particular Term->Gloss) & a Sentence belongsToMany Glosses (a Sentence has several Term->Glosses).
In the Entry Creator, I have the field "term[term]" & several like "glosses[0][gloss]". In the store() method in the TermController, I have:
$term = Term::create($term);
foreach ($request->glosses as $gloss) {
if ($gloss['gloss'] == true) {
$gloss = array_merge($gloss, ['term_id' => $term->id]);
$gloss = Gloss::create($gloss);
}
}
So far, so good. Now, I think in theory I would link a Sentence to a Gloss like this:
foreach ($request->sentences as $sentence) {
if ($sentence['sentence'] == true) {
$sentence = Sentence::create($sentence);
$sentence->glosses()->attach($gloss);
}
}
However, I know this would only work if I were trying to attach every Sentence in the form to a single Gloss. In reality, any Term may have >1 Gloss & each one may have >1 Sentence that I only want to associate with the Gloss that is currently being looped through. I was thinking the following could be a solution:
foreach ($request->glosses as $glossKey => $gloss) {
if ($gloss['gloss'] == true) {
$gloss = array_merge($gloss, ['term_id' => $term->id]);
$gloss = Gloss::create($gloss);
}
foreach ($request->sentences as $sentence) {
if ($request->input('glossKey') == $glossKey) {
$sentence = Sentence::create($sentence);
$sentence->glosses()->attach($gloss);
}
}
}
In the HTML, I would have:
<div class="auth-field">
<x-label for="sentences[0][sentence]" :value="__('Sentence')" />
<x-input id="sentences[0][sentence]" name="sentences[0][sentence]" ... glossKey="1" />
</div>
My thought was that if we are on the first loop ($glossKey == 1), the Controller would scan every sentence & only create the ones that are identified in the HTML as belonging to the first loop. However, I'm not sure if it's even allowed to add this sort of custom identifier to the HTML input, because when I dd($request->sentences), the result is very odd: it only shows me the very last sentence, even if I specify $request->sentences[0]. In any case, the glossKey attribute doesn't seem to be part of the object at all.
Any ideas on how I can accomplished the desired behavior?

Nested loops for dynamics arrays

I have some results from a web form.
I'm working with WP and I get a lot of term_id (from different taxonomy).
In the frontend the form is "dynamic" for each kind of taxonomy (and i dont know before how many they are), I made a multiselect (all select have same NAME ex: terms[] ).
When I process the term_IDs after form Submit I check each term_Id and put it in the right array. EX:
$option = isset($_POST['terms']) ? $_POST['terms'] : false;
if ($option) {
$sql = "SELECT term_id,taxonomy FROM wp_term_taxonomy WHERE term_id IN (".implode(',',$option).") ";
$term_tax = $wpdb->get_results($sql);//print_r($option); exit();
foreach ($term_tax as $tt)
{
$global[$tt->taxonomy][] = $tt->term_id;
}
So in the end I have an array with 1 or N arrays.
For each sub-array i have to do a nested cycle
EX: after process terms i have $gloabl[category] and $global[tag] I need to do
foreach($global[category] as $t)
{
foreach($global[tag] as $x)
{
//do query with $t and $x
}
}
I need to do a query with all combinations of term_ids for each subarray dynamically.

How to paginate a foreach loop?

I have this foreach loop wich shows the supporters located in the post_meta from a custom post type. What I want to do is add pagination to the foreach loop. I have already found a way to decide how many supporters are shown by slicing the array, but now I am at a loss. And have no idea how to proceed.
Function to get the supporters array
function getSupporters($petitieID){
$support = get_post_meta(get_the_ID(), 'supporters', true);
if (!empty($support)){
return $support;
}}
Function to show the individual supporters in the array
function showSupporters($petitieID){
$supporters = getSupporters($petitieID);
if (!empty($supporters)){
foreach (array_slice($supporters, 0, 2) as $supporter){
$supporterID = $supporter->post_author;
the_author_meta('first_name', $supporterID);
}
}else {
echo 'no votes';
}
}
You could determine which page is currently shown in a GET variable in your address
.../supporters.php?page=1
Then you could set the offset of your array_slice function accordingly
$nItemsPerPage = 2;
$page = isset($_GET['page'])?$_GET['page']:1;
array_slice($supporters, $nItemsPerPage*($page-1), $nItemsPerPage)

Magento: paginate filtered product collection

i want to filter and paginate a product collection. everything is fine - except pagination. im just getting the whole collection back, instead of 3 items for the first page.
//fetch all visible products
$product_collection = Mage::getModel('catalog/product')->getCollection();
//set wanted fields (nescessary for filter)
$product_collection->addAttributeToSelect('name');
$product_collection->addAttributeToSelect('description');
$product_collection->addAttributeToSelect('price');
$product_collection->addAttributeToFilter('visibility', array('neq' => 1));
//filter by name or description
$product_collection->addFieldToFilter(array(
array('attribute'=>'name','like'=>$sTerm),
array('attribute'=>'description','like'=>$sTerm)
));
//filter for max price
foreach ($product_collection as $key => $item) {
if($item->getPrice() >= $priceTo){
$product_collection->removeItemByKey($key);
}
}
//pagination (THIS DOESNT WORK!)
$product_collection->setPageSize(3)->setCurPage(1);
//TEST OUTPUT
foreach ($product_collection as $product) {
echo $product->getName().'<br />';
}
thanks for your support!
You are so close! Try moving that $product_collection->setPageSize(3)->setCurPage(1); line before the first foreach() iteration over the collection.
Magento collections are lazy-loaded. Until you directly load() them (or implicitly load them via a call to count() or foreach()) you can modify the collection properties which affect the underlying query (EDIT: see note below). Once the collection has been loaded explicitly or implicitly though you will only get the members of the _items property that have been set.
FYI you can call clear() to leave the original query-affecting properties (filters, sorters, limits, joins, etc) in place and then add further properties.
HTH
EDIT: Actually, adjusting query properties is always possible regardless of _items load state, but the effect won't be visible until the collection is regenerated.
Thanks #Ben! You gave me the right hint. Now it does work! Basically I'm creating another collection and filter this one by the ids of the already filtered items. Afterwards its easy to add pagination to that new collection. That's the working code:
//fetch all visible products
$product_collection = Mage::getModel('catalog/product')->getCollection();
//set wanted fields (nescessary for filter)
$product_collection->addAttributeToSelect('name');
$product_collection->addAttributeToSelect('description');
$product_collection->addAttributeToSelect('price');
$product_collection->addAttributeToFilter('visibility', array('neq' => 1));
//filter by name or description
$product_collection->addFieldToFilter(array(
array('attribute'=>'name','like'=>$sTerm),
array('attribute'=>'description','like'=>$sTerm)
));
//filter for max price
foreach ($product_collection as $key => $item) {
if($item->getPrice() >= $priceTo){
$product_collection->removeItemByKey($key);
}
}
//build id array out of filtered items (NEW!)
foreach($product_collection as $item){
$arrProductIds[]=$item->getId();
}
//recreate collection out of product ids (NEW)
$product_filtered_collection = Mage::getModel('catalog/product')->getCollection();
$product_filtered_collection->addAttributeToFilter('entity_id', array('in'=>$arrProductIds));
//add pagination (on new collection) (NEW)
$product_filtered_collection->setPageSize(3)->setCurPage(1);
//TEST OUTPUT
foreach ($product_filtered_collection as $product) {
echo $product->getName().'<br />';
}

Create a Dynamic Nav on the Fly

I'm looking for the best way to create a complex navigation element on the fly. I have all of the elements in a database (title, id, parentId) and I want to efficiently take them out of the DB and display them correctly. I also want to collapse all of the navigation elements that aren't active. So if I was browsing through "Sofas" I wouldn't see "Chandeliers" or any of the categories under lighting but I would see "Lighting".
This is what I want the final product to look like:
Furniture
Living Room
Sofas
Chairs
Ottomans
Bedroom
Beds
Nightstands
Lighting
Chandeliers
Floor Lamps
Sconces
Rugs & Textiles
Contemporary
Vintage
My current method is
write one SQL query that pulls down all of the category names, ids, and parent ids
Iterate through the categories and put into a sorted multi-dimensional array with child categories stored under their parents.
Iterate through the new array and add another entry to mark the appropriate categories as open (all categories are closed by default)
iterate through the array and write HTML
I'm trying to to this with as few interations as possible and I'm sure the code I have right now is inefficient. Especially step 2 I iterate through the array several times. There has to be a general solution to this (common?) problem.
Consider adding a new field to your database table: level.
Main-categories will have level 0.
Sub-categories will have level 1.
Sub-sub-categories will have level 2.
etc.
This trick will help you to know which sub-categories to disable without 2nd iteration of the array.
I believe this is the perfect place to generate your html code using recursion.
I used this function a while ago. It is working with a multi-dimensional array (tree)
function buildMenu($menu_array, $is_sub=FALSE) {
$attr = (!$is_sub) ? 'id="menu"' : 'class="submenu"';
$menu = "<ul $attr>\n";
foreach($menu_array as $id => $elements) {
foreach($elements as $key => $val) {
if(is_array($val)) {
$sub = buildMenu($val, TRUE);
}
else {
$sub = NULL;
$$key = $val;
}
}
if(!isset($url)) {
$url = $id;
}
$menu .= "<li>$display$sub</li>\n";
unset($url, $display, $sub);
}
return $menu . "</ul>\n";
}
echo buildMenu($menu_array);
This adds css properties too. If you wish to mark the currently active page you can use the strpos() function to find your current url. If you need some more functionality you can easily add them to buildMenu()
Using level as mentioned in the answer above will help too. If you were using the nested set model in your database I could also help you with my query which is a single select returning the whole menu data.

Categories