Looping through database to get details, then send email with said values - php

I am bringing in brochures selected by visitors, and they can select multiple brochures. After three days they are meant to get an email reminding them of the brochures they have chosen.
Here is what I have so far:
$time_query = "SELECT * FROM users WHERE time < (now() - INTERVAL 1 minute)"; //" //GROUP BY time does group them into an array... well.. it doesnt display duplicate timestamps, so assume it saves it to an array'";
$time_query_result = mysql_query($time_query, $db) or
die("Could not execute sql: $time_query");
$users = array();
while($row = mysql_fetch_array($time_query_result)) {
if (!array_key_exists($users[$row["id"]], $users)) {
$users[$row["id"]] = array('email' => $row["email"], 'brochures' => array());
$users[$row["id"]]["brochures"] = array('b' => $row["brochures"], 't' => $row["time"]);
}
}
foreach ($users as $user) {
$text = '<html><body><p>Brochure reminder</p>';
$i = 0;
foreach ($user["brochures"] as $brochure) {
$text .= 'Brochures:<br />'.$i++ . $row["b"];
}
$text .= '</body></html>';
mail($user["email"], $subject, $text, $headers);
}
I am getting numbers through the emails instead of brochure names, and I think its something to do with the array_key_exists fuinction.
Each time a user selects a brochure, it creates its own row in the DB, and the idea was to pull in the multiple brochures a user selected at a time (by the time column), as many users can select brochures over a time period.
Any help would be appreciated :)

In your 'while' loop, you're creating a new 'brochures' element in your 'users' array, when I think you're wanting to append to it.
if (!array_key_exists($row["id"], $users)) {
$users[$row["id"]] = array('email' => $row["email"], 'brochures' => array());
}
$users[$row["id"]]["brochures"][] = array('b' => $row["brochures"], 't' => $row["time"]);
then in your 'foreach', you will want to use the brochure variable:
foreach ($user["brochures"] as $brochure) {
$text .= 'Brochures:<br />'.$i++ . $brochure["b"];
}

Your current code builds an array of users containing another array with the index 'brochures'. This array will always contain tow values.
{
'b' => $row["brochures"]
't' => $row["time"])
}
Regarding this fact the following statements doesn't make sense:
foreach ($user["brochures"] as $brochure) {
}
All you do is iterate over the two values with the index 'b' and 't'. If you want to iterate over a collection of brochures you need to adapt your code.
On the other hand you have a few important mistakes:
foreach ($user["brochures"] as $brochure) {
$text .= 'Brochures:<br />'.$i++ . $row["b"];
}
Why use a foreach if you don't even use the $brochure variable?
$text .= 'Brochures:<br />'.$i++ . $row["b"];
$row contains the last row, which is definitively not what you wanted. In fact, $row is out of scope, in a serious programming language you would see this.
$row["id"]
You use this about three times. So why not store it in a variable $id? Accessing arrays with indexes is a more expensive operation than simply accessing a variable.
In general I strongly encourage you to switch to an object oriented approach so you get rid of these ugly array in array in array solution...

Related

How to sort a multidimensional array of columns by a specific column in PHP?

I found on the PHP documentation the function "array_multisort" that is meant to sort an array "of columns". They provide one example where the user has an array of rows and then the user has to transform this array of rows into an array of columns. In my case the array is already set by columns, such as:
tablearray
{
['Employee_ID'] = {0 => row1, 1 => row2, 2 => row3, 3 => row4}
['First_Name'] = {0 => row1, 1 => row2, 2 => row3, 3 => row4}
['LastName'] = {0 => row1, 1 => row2, 2 => row3, 3 =>row4}
}
I want to sort by Employee_ID and I need all the other columns to follow the same order. I tried:
array_multisort($tablearray['Employee_ID'], SORT_ASC);
But it only sorts the first column (which becomes a mess). The array has more than 10 columns and it changes the column names depending on the search (the columns names are its keys).
On PHP's documentation for this function, the example provided shows that the after transforming the rows array into a columns array, we should use the original array as a third parameter to match the keys - I don't have the "original" array to do the match since I didn't transform anything.
Thank you.
Desired output, as suggested by one user:
Original:
array
{
['Employee_ID'] = (1002, 4508, 0002, 1112)
['Business_Unit'] = ('UER', 'ABC', 'XYZ', 'EER')
['LastName'] = ('Smith', 'Vicente', 'Simpson', 'Thompson')
}
Sorted by Employee ID:
array
{
['Employee_ID'] = (0002, 1002, 1112, 4508)
['Business_Unit'] = ('XYZ', 'UER', 'EER', 'ABC')
['LastName'] = ('Simpson','Smith', 'Thompson', 'Vicente')
}
--
My original array is a database query output:
Array
(
[0] => Array
(
[Employee_ID] => 0000
[Supervisor_ID] => 00000
[Location_Descr] => somewhere
[Start_Date] => 06/03/2002
[Service_Date] => 06/03/2002
[Rehire_Date] => 00/00/00
[Business_Unit] => YYYY
[Job_Title] => Manager
[Email] => email#example.com
[Dept_Desc] => bla bla bla
[Employee_Name_LF] => Last, First
[Supervisor_Name_LF] => Last, First
[Term_Date] => 00/00/00
[Preferred_Name] => Someone
[Source] => Sheet2
)
)
There a several more rows.
The main purpose is to show the results as an HTML table and to generate a CSV file. I already made those functions using the modified structure (the first that I posted). I thought it would be easier to deal with that structure... Indeed it was, but not for sorting unfortunately.
The array_multisort documentation (http://php.net/manual/en/function.array-multisort.php) suggests separating each column as an individual array.. However, as you can see I have several columns (and the user can select more or less to be shown before performing the query.. So I can't just list all of them on the statement).
I a willing to change everything just to make the code better to be worked with.
Ugly - would be a lot easier if you formatted the input tables.
$arr = array(
'Employee_ID' => array('1002', '4508', '0002', '1112'),
'Business_Unit' => array('UER', 'ABC', 'XYZ', 'EER'),
'LastName' => array('Smith', 'Vicente', 'Simpson', 'Thompson')
);
$employees = array();
foreach (range(0, sizeof($arr[current(array_keys($arr))]) - 1) as $k) {
$emp = array();
foreach ($arr as $col => $vals) {
$emp[$col] = $arr[$col][$k];
}
$employees[] = $emp;
}
$sort = array();
foreach ($employees as $k => $v) {
$sort[$k] = $v['Employee_ID'];
}
array_multisort($sort, SORT_ASC, $employees);
print_r($employees);
And to put back in the original format:
$arr_sorted = array();
foreach (array_keys($arr) as $col) {
$arr_sorted[$col] = array();
foreach ($employees as $emp) {
$arr_sorted[$col][] = $emp[$col];
}
}
print_r($arr_sorted);
Thank you for posting the extra details in your question, as they did help in understanding the intent of your question.Now, you didn't tell us how that table should look; If you want the employees one per row, or one per column. Which is kind of crucial to know. Normally one would have one employee per line, especially if this is to be exported to CVS. However, I have a suspicion that it's the latter you want. Otherwise you've gone about this in a very overly complicated manner.Point in case: Normal one-per-row layout:
<?php
$db = new PDO();
// Defining the fields we need here, to avoid having too long a string for the query.
$fields = "e.employee_id, e.first_name, e.lastname, u.business_unit, s.email";
// Do the sorting in the database itself. Not only is this faster, but it
// is also a lot easier to sort it exactly as you'd like.
// Note that I don't use prepared statements here, as there is no user-input.
$query = <<<outSQL
SELECT {$Fields} FROM `unit` AS u
INNER JOIN `employee` AS e ON e.employee_id = u.unit_id
INNER JOIN `employee` AS s ON s.employee_id = u.supervisor_id
ORDER BY e.`employee_id`
outSQL;
$data = $db->query($query);
// Creating a printf() template for the output, to make the code easier to maintain.
$rowTemplate = <<<outHTML
<tr>
<td>%1\$d</td>
<td>%2\$s</td>
<td>%3\$s</td>
</tr>
outHTML;
// Generate the table template, using placeholders for where the data will be added..
$tableTemplate = <<<outHTML
<table>
<thead>
<tr>
<th>ID</th>
<th>First name</th>
<th>Last name</th>
</tr>
</thead>
<tbody>
%s
</tbody>
</table>
outHTML;
// Regular table output, one employee per line.
$temp = '';
foreach ($data as $e) {
// hs() is a shortcut function to htmlspecialchars (), to prevent against XSS.
$temp .= sprintf($rowTemplate, $e['employee_id'], hs($e['first_name']), hs($e['lastname']));
}
// Add the rows to the table, so that you can echo the completed product wherever you need.
$employeeTable = sprintf($tableTemplate, $temp);
If you want to do it one per column, it becomes a bit more intricate. Though, still a bit easier than your first attempt. :)
Namely, something like this:
<?php
$db = new PDO();
// Defining the fields we need here, to avoid having too long a string for the query.
$fields = "employee_id, first_name, lastname";
// Do the sorting in the database itself. Not only is this faster, but it
// is also a lot easier to sort it exactly as you'd like.
// Note that I don't use prepared statements here, as there is no user-input.
$data = $db->query("SELECT {$Fields} FROM `employees` ORDER BY `employee_id`");
// We need to know how many columns we'll have. One per employee.
$columns = count ($data);
// Rows have a header in front of each line, and one td tag for each employee.
$rowTemplate = "\t\t<th>%s</th>\n".str_repeat("\t\t\t<td>%s</td>\n", $columns);
// Generate the table template, using placeholders for where the data will be added..
$tableTemplate = <<<outHTML
<table>
<tbody>
%s
</tbody>
</table>
outHTML;
// Reformat the array to give us the data per-column.
$temp = array ();
foreach ($data as $field => $e) {
// Since we've already sorted the data in the database we don't need to do any further sorting here.
// Also note that I'm doing the escaping here, seeing as this array will only be used for output.
$temp['Employee ID'][] = intval($e['employee_id']);
$temp['First name'][] = hs($e['first_name']);
$temp['Last name'][] = hs($e['lastname']);
}
// Now we do the same as in the above example.
$rows = '';
foreach ($temp as $label => $l) {
// We have the label as the first template variable to be added, so put it as the first element.
array_unshift($l, $label);
// Add the current row of items to the output, using the previously established template.
$rows = vprintf($rowTemplate, $l);
}
// Add the rows to the table, so that you can echo the completed product wherever you need.
$employeeTable = sprintf($tableTemplate, $temp);
PS: Haven't tested the code, but it should work.
I ran into his problem and after much angst found a really nice solution in the notes on the php manual page - I now have the following function which i use whenever I need to solve this type of problem.
function fnArrayOrderby(){
//function to sort a database type array of rows by the values in one or more column
//source http://php.net/manual/en/function.array-multisort.php - user notes
//example of use -> $sorted = fnArrayOrderby($data, 'volume', SORT_DESC, 'edition', SORT_ASC);
$args = func_get_args(); //Gets an array of the function's argument list (which can vary in length)
//echo "sorting ".$args[0]."<br>";
if (!isset($args[0])) { return;}
$data = array_shift($args); //Shift an element off the beginning of array
foreach ($args as $n => $field) {
if (is_string($field)) {
$tmp = array();
foreach ($data as $key => $row)
$tmp[$key] = $row[$field];
$args[$n] = $tmp;
}
}
$args[] = &$data;
call_user_func_array('array_multisort', $args);
return array_pop($args);
}

Similarity algorithm advice, using two dimensional associative array

The main goal of this algorithm is to find similar titles of news articles from different sources of web and group them, let's say above 55.55% similarity.
My current approach of the algorithm consist of following steps:
Feed data from MYSQL database into a two-dimensional array ex. $arrayOne.
Make another copy of that array into ex. $arrayTwo.
Create a clean array which will only contain similar titles and other content ex. $array_smlr.
Loop, foreach $arrayOne article_title check for similarity with $arrayTwo article_title
If similarity of between two titles is above 55% and if the article is not from the same news source (this way I don't check same articles from the same source) add it to $array_smlr
Sort the $array_smlr based on percentages of similarity, this way I end up grouping titles that are similar.
Below is my code for the above tasks mentioned.
$result = mysqli_query($conn,"SELECT id_articles,article_img,article_title,LEFT(article_content , 200),psource, date_fetched FROM project.articles WHERE " . rtrim($values,' or') . " ORDER BY date_fetched DESC LIMIT 70");
$arrayOne=array();
$arrayTwo=array();
while($row = mysqli_fetch_assoc($result)){
$arrayOne[] = $row;
}
$arrayTwo = $arrayOne;
$array_smlr=array();
foreach ($arrayOne as $rowOne) {
foreach($arrayTwo as $rowTwo){
$compare = similar_text($rowOne['article_title'], $rowTwo['article_title'], $p);
if ( round($p,2) >= 55.50 and $rowOne['psource'] != $rowTwo['psource'] ){
$data = array('percentage' => round($p,2), 'article_title' => $rowTwo['article_title'], 'psource' => $rowTwo['psource'], 'id_articles' => $rowTwo['id_articles'], 'date_fetched' =>$rowTwo['date_fetched']);
$array_smlr[]=$data;
}
}
}
array_multisort($array_smlr);
foreach($array_smlr as $row3){
echo $row3['percentage'] . $row3['article_title'] . $row3['psource'] . $row3['id_articles'] . $row3['date_fetched'] . "<br><br>";
}
This would work with limited functionality, only if I had two similar titles, but let's say if I had 3 similar titles, it would include duplicated rows of data in $array_smlr.
I would appreciate if you have any suggestions on optimization of this algorithm in order to improve the performance.
Thanks,
You don't really need 2 arrays instead of the foreach loop without $key wildcard you can use it with $key and skip the solver when the $key is the same. Then you also avoid dupes.
foreach ($arrayOne as $key => $rowOne) {
foreach($arrayOne as $ikey => $rowTwo){
if ($ikey != $key) {
$compare = similar_text($rowOne['article_title'],$rowTwo['article_title'], $p);
if ( round($p,2) >= 55.50 and $rowOne['psource'] != $rowTwo['psource'] ){
$data = array('percentage' => round($p,2), 'article_title' => $rowTwo['article_title'], 'psource' => $rowTwo['psource'], 'id_articles' => $rowTwo['id_articles'], 'date_fetched' =>$rowTwo['date_fetched']);
$array_smlr[$rowTwo['id_articles']]=$data;
}
}
}

How to iterate through a mysql array and append corresponding text from an PHP array

I am making a pricing table to upgrade membership.
In mySQL I have a table with limits on what each membership can allow see picture
I grab the info from the table and try to iterate through it and append some text to it from another array. I have tried thoroughly looking online for an answer but I'm probably not even searching for the right terms. For the purpose of this question i'm using 2 fields but in reality i will be iterating and appending many columns.
Here's my code:
$sql = "SELECT maxProducts,maxImages FROM memproducts_membership_settings WHERE useThis='1'";
$rs = mysql_query($sql);
while($row = mysql_fetch_array($rs,MYSQL_ASSOC))
{
foreach($row as $field)
{
$append = array('Product Slots','Images per product');
print $field.' '.$append.'<br>';
}
}
All i get is:
5 Array
2 Array
and not what i need which would be:
5 Product Slots
2 Images per product
I'm sure there is a way to do this but i just cannot figure it out, please anyone give me some pointers? Many regards :)
This is pretty basic stuff...
while($row = mysql_fetch_array($rs,MYSQL_ASSOC))
{
echo $row['maxProducts'] . ' Product Slots<br>';
echo $row['maxImages'] . ' Images per product<br>';
}
Or if you are needing to do this with dynamic fields in whatever order you return from database like your question suggests, perhaps you might consider providing a mapping of database column names to the appropriate label content like this:
$labels = array(
'maxProducts' => 'Product Slots',
'maxImages' => 'Images per product',
...
);
...
while($row = mysql_fetch_array($rs,MYSQL_ASSOC)) {
foreach ($row as $key => $value) {
echo $value . ' ' . $labels[$key] . '<br>';
}
}
Note if you are just learning PHP, you REALLY should NOT learn mysql_* functions. They are deprecated and need to die. Please learn to use PDO or mysqli.
To append values to the end of an array use array_push()
http://php.net/manual/en/function.array-push.php
There's plenty about your code and your approach that I don't like at all, but the most immediate solution to your current problem would be to iterate the array of strings from within the foreach loop, using each():
$strings = array('Product Slots','Images per product');
while($row = mysql_fetch_array($rs,MYSQL_ASSOC))
{
reset($strings);
foreach($row as $field)
{
$append = each($strings);
print $field.' '.$append[1].'<br>';
}
}
You should at very least make yourself aware of:
the deprecation of ext/mysql (use either mysqli or PDO); and
the risk of XSS attacks (escape any HTML in values from your database before returning them to the browser).
It's doing what it should... You have created an array and asked it to print its name. As print can't automatically iterate an array it just tells you what the variable type is.

Query DB to get multiple rows where timestamps are the same, and email addresses are the same

<?php
$curr = time();
$time_query = "SELECT * FROM users ";
$time_query_result = mysql_query($time_query, $db) or
die("Could not execute sql: $time_query");
$num_result = mysql_num_rows($time_query_result);
for ($i1=0; $i1 < $num_result; $i1++){
$row = mysql_fetch_array($time_query_result);
if ($curr > $row["time"] + 259200)//Do if it has been three days since brochure was selected
{
$arr = implode (',', $row);//TURN THE ROW[BROCHURES] ARRAY INTO IMPLODE THING INSIDE THIS LOOP!?!?!
echo $arr;
$message1 = '<html><body><p>Brochure reminder</p>';
$message1 .= 'Brochures:<br />'.$arr .$row["brochures"]. $row["time"];
$message1 .= '</body></html>';
// Mail it
mail($row['email'], $subject, $message1, $headers);
}
} ?>
So basically, I want to send an email to customers selecting a brochure. That part is done and fine, but I want them to receive another email after three days reminding them about the brochures they have chosen. I have set up a cron job so this piece of code gets repeated every three days.
When the mail is sent, it sends it to the correct person but the selected brochures are split into seperate emails. For example, if somebody selects 5 brochures, after three days they will reveice 5 seperate emails, with a different brochure in each one.
I need it so they receive all brochures in one email after three days. Would really appreciate this, been driving me mad for days. It seems like the answer could be really simple but I cant figure it out.
Thanks!
You should move the time logic into your database query - there's no point in fetching ALL rows, only to throw away some (most? all?) of the rows if there's nothing to do:
SELECT *
FROM users
WHERE `time` < (now() - INTERVAL 3 DAY)
Once that's done, I'd suggest moving the actual mail() portion out of the database fetching loop, so that you can batch together each user's brochures first:
$users = array();
while($row = mysql_fetch_array($time_query_result)) {
if (!array_key_exists($row['userID'], $users)) {
$users[$row['userID']] = array('email' => $row['email'], 'brochures' => array());
$users[$row['userID']]['brochures'] = array('b' => $row['brochures'], 't' => $row['time']);
}
}
You'd then loop over this $users array to build emails for the users:
foreach ($users as $user) {
$text = '<html><body><p>Brochure reminder</p>';
$i = 1;
foreach ($user['brochures'] as $brochure) {
$text .= 'Brochures:<br />'.$i++ .$row['b']. $row['b'];
}
$text .= '</body></html>';
mail($user['email'], $subject, $text, $headers);
}
This way, you fetch all the user details in one go. You then build a SINGLE email to each user, listing all of their brochures, and (hopefully) problem solved.

How can I generate a tree structure from a table in a database?

I'm trying to generate a tree structure from a table in a database. The table is stored flat, with each record either having a parent_id or 0. The ultimate goal is to have a select box generated, and an array of nodes.
The code I have so far is :
function init($table, $parent_id = 0)
{
$sql = "SELECT id, {$this->parent_id_field}, {$this->name_field} FROM $table WHERE {$this->parent_id_field}=$parent_id ORDER BY display_order";
$result = mysql_query($sql);
$this->get_tree($result, 0);
print_r($this->nodes);
print_r($this->select);
exit;
}
function get_tree($query, $depth = 0, $parent_obj = null)
{
while($row = mysql_fetch_object($query))
{
/* Get node */
$this->nodes[$row->parent_category_id][$row->id] = $row;
/* Get select item */
$text = "";
if($row->parent_category_id != 0) {
$text .= " ";
}
$text .= "$row->name";
$this->select[$row->id] = $text;
echo "$depth $text\n";
$sql = "SELECT id, parent_category_id, name FROM product_categories WHERE parent_category_id=".$row->id." ORDER BY display_order";
$nextQuery = mysql_query($sql);
$rows = mysql_num_rows($nextQuery);
if($rows > 0) {
$this->get_tree($nextQuery, ++$depth, $row);
}
}
}
It's almost working, but not quite. Can anybody help me finish it off?
You almost certainly, should not continue down your current path. The recursive method you are trying to use will almost certainly kill your performance if your tree ever gets even slightly larger. You probably should be looking at a nested set structure instead of an adjacency list if you plan on reading the tree frequently.
With a nested set, you can easily retrieve the entire tree nested properly with a single query.
Please see these questions for a a discussion of trees.
Is it possible to query a tree structure table in MySQL in a single query, to any depth?
Implementing a hierarchical data structure in a database
What is the most efficient/elegant way to parse a flat table into a tree?
$this->nodes[$row->parent_category_id][$row->id] = $row;
This line is destroying your ORDER BY display_order. Change it to
$this->nodes[$row->parent_category_id][] = $row;
My next issue is the $row->parent_category_id part of that. Shouldn't it just be $row->parent_id?
EDIT: Oh, I didn't read your source closely enough. Get rid of the WHERE clause. Read the whole table at once. You need to post process the tree a second time. First you read the database into a list of arrays. Then you process the array recursively to do your output.
Your array should look like this:
Array(0 => Array(1 => $obj, 5 => $obj),
1 => Array(2 => $obj),
2 => Array(3 => $obj, 4 => $obj),
5 => Array(6 => $obj) );
function display_tree() {
// all the stuff above
output_tree($this->nodes[0], 0); // pass all the parent_id = 0 arrays.
}
function output_tree($nodes, $depth = 0) {
foreach($nodes as $k => $v) {
echo str_repeat(' ', $depth*2) . $v->print_me();
// print my sub trees
output_tree($this->nodes[$k], $depth + 1);
}
}
output:
object 1
object 2
object 3
object 4
object 5
object 6
I think it's this line here:
if($row->parent_category_id != 0) {
$text .= " ";
}
should be:
while ($depth-- > 0) {
$text .= " ";
}
You are only indenting it once, not the number of times it should be indented.
And this line:
$this->get_tree($nextQuery, ++$depth, $row);
should be:
$this->get_tree($nextQuery, $depth + 1, $row);
Note that you should probably follow the advice in the other answer though, and grab the entire table at once, and then process it at once, because in general you want to minimize round-trips to the database (there are a few use cases where the way you are doing it is more optimal, such as if you have a very large tree, and are selecting a small portion of it, but I doubt that is the case here)

Categories