Skip to content

Commit d0aa5f6

Browse files
authoredNov 14, 2023
Add files via upload
1 parent ae616a4 commit d0aa5f6

13 files changed

+1922
-0
lines changed
 

‎db_config.php

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?
2+
$conn_hostname="localhost"; //mysql hostname
3+
$conn_username=''; // mysql username
4+
$conn_password=''; //mysql password
5+
$conn_database=''; // mysql database
6+
$from_email=''; // email address to use for notifications and reports. It should be the same email address that pipes to tasks_me.php so user can simply reply to the emails to enter their tasks.
7+
$main_reminder_subject='What did you do today?'; //email subject for the daily reminder.
8+
$group_name="Example Group Name"; // name of the group. Used in email
9+
$html2text_loc='html2text/'; // The location of html2text.php
10+
date_default_timezone_set('America/Los_Angeles'); //Sets the timezone used for scheduling reports
11+
$time_to_act=15; // Emails are to be sent once when the script is run within $time_to_act minutes past the designated time. Default value is 15
12+
//The cron job should be run at a smaller interval than this value
13+
14+
$cool_down_time=5; // Timestamps for the previous email are deleted after the $cool_down_time minutes after the timestamp. This is to prevent accidental spam
15+
?>

‎html2text/LICENSE.md

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2019 Jevon Wright
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

‎html2text/html2text.php

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
/**
3+
* This file is available if you still want to use functions rather than
4+
* autoloading classes.
5+
*/
6+
7+
require_once(__DIR__ . "/src/Html2Text.php");
8+
require_once(__DIR__ . "/src/Html2TextException.php");
9+
10+
function convert_html_to_text($html, $ignore_error = false) {
11+
return Soundasleep\Html2Text::convert($html, $ignore_error);
12+
}
13+
14+
function fix_newlines($text) {
15+
return Soundasleep\Html2Text::fixNewlines($text);
16+
}
17+
?>

‎html2text/src/Html2Text.php

+505
Large diffs are not rendered by default.

‎html2text/src/Html2TextException.php

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace Soundasleep;
4+
5+
class Html2TextException extends \Exception {
6+
7+
var $more_info;
8+
9+
public function __construct($message = "", $more_info = "") {
10+
parent::__construct($message);
11+
$this->more_info = $more_info;
12+
}
13+
14+
}

‎read_me.txt

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
Introduction:
2+
This project contains two main components.
3+
4+
tasks_me.php: A program to process emails sent by registered user and record the content as tasks. The program presumes 1 tasks per line in the email.
5+
A email forwarder must be setup to pipe to it.
6+
7+
task_reports.php: A program meant to run as a cron script, to sort the recorded tasks by time and user, and sends out a report to all appropriate users at the specified time of day. It also sends out reminders and extra reminders each day at specified time.
8+
The assumption to not send out reports is currently hardcoded into the program.
9+
10+
Third party requirement:
11+
PHP 5.6+
12+
Included a copy of html2text from https://github.com/soundasleep/html2text per MIT License.
13+
14+
Installation steps.
15+
16+
1. tasks_me.php needs to be set to 0755 permission so it's executable.
17+
2. The top of tasks_me.php needs to point to the correct php path
18+
3. tasks_me.php must have Unix EOL or the piping will not work
19+
4. html2text must be installed, and its path specified in db_config.php. A copy is included since it is MIT License
20+
5. db_config.php must be filled in with mysql user credential with all privileges.
21+
6. Run setup.php.
22+
7. Copy the tasks_admin folder to a web accessible location. It is a dashboard for modifying settings and add users.
23+
8. The db_config.php file in the admin folder must correctly include the real db_config.php in the same folder as setup.php
24+
9. Access the dashboard in /tasks_admin/index.php. Register users and configure the report schedule.
25+
User types
26+
Normal: Gets regular, extra reminder, and reports. Can input complete tasks.
27+
Super: Like Normal user, but does not get extra reminder.
28+
Script: Is an automated emailing script. Can input complete task, but does not receive any email.
29+
30+
10. Add task_reports to cron. 5 minutes intervals recommended.
31+

‎setup.php

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?
2+
/*
3+
Checks if settings in db_config can connect to the database.
4+
Checks if tasks_me.php has preferred permissions set
5+
Checks if html2text.php exists in specified location
6+
Validates email address contained in $from_email
7+
Create tables if doesn't exist.
8+
Will also set and reset the admin password.
9+
*/
10+
if (file_exists('db_config.php'))
11+
{
12+
require_once('db_config.php');
13+
}
14+
else
15+
{
16+
die ('Error: cannot find db_config.php');
17+
}
18+
if (!filter_var($from_email, FILTER_VALIDATE_EMAIL))
19+
{
20+
die ('Error: $from_email variable in db_config must contain a valid email address from your domain.');
21+
}
22+
23+
if (!file_exists($html2text_loc.'html2text.php'))
24+
{
25+
26+
echo 'Error: html2text.php cannot be found in the location specified in db_config.php'."\n";
27+
}
28+
$tasks_me_permission = substr(sprintf('%o',fileperms('tasks_me.php')), -4);
29+
30+
if ($tasks_me_permission!='0755')
31+
{
32+
echo 'Permissibon for tasks_me.php is currently set to '.$tasks_me_permission.". It is highly recommended that the permission is set to 0755.\n";
33+
}
34+
35+
$dbh = mysqli_connect($conn_hostname, $conn_username, $conn_password,$conn_database);
36+
if (!$dbh)
37+
{
38+
echo 'Error: Cannot connect to database. Please check settings in db_config.php'."\n";
39+
}
40+
41+
//Create config table
42+
43+
$SQL="CREATE TABLE IF NOT EXISTS `config` (
44+
`config_id` int(11) NOT NULL AUTO_INCREMENT,
45+
`admin_pw` blob NOT NULL,
46+
`daily_report_time` time NOT NULL,
47+
`reminder_time` int(11) NOT NULL,
48+
`daily_reminder_time` time NOT NULL,
49+
PRIMARY KEY (config_id)
50+
) ENGINE=InnoDB DEFAULT CHARSET=latin1;";
51+
mysqli_query($dbh, $SQL) or die (mysqli_error($dbh));
52+
53+
// Create email_history table
54+
$SQL="CREATE TABLE IF NOT EXISTS `email_history` (
55+
`email_type` varchar(200) NOT NULL,
56+
`prev_email_unix_time` int(13) NOT NULL DEFAULT '0',
57+
PRIMARY KEY (email_type)
58+
) ENGINE=InnoDB DEFAULT CHARSET=latin1;";
59+
mysqli_query($dbh, $SQL) or die (mysqli_error($dbh));
60+
61+
62+
$SQL="CREATE TABLE IF NOT EXISTS tasks (`task_id` int(11) NOT NULL AUTO_INCREMENT,`user_id` int(11) NOT NULL ,`body` text COLLATE utf8_unicode_ci NOT NULL,`occurred_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (task_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;";
63+
mysqli_query($dbh, $SQL) or die (mysqli_error($dbh));
64+
65+
$SQL="CREATE TABLE IF NOT EXISTS `users` (
66+
`user_id` int(11) NOT NULL AUTO_INCREMENT,
67+
`user_email_address` varchar(200) NOT NULL,
68+
`user_name` varchar(200) NOT NULL,
69+
`status` varchar(30) DEFAULT NULL,
70+
`user_type` varchar(100) DEFAULT NULL,
71+
PRIMARY KEY (user_id)
72+
) ENGINE=InnoDB DEFAULT CHARSET=latin1;";
73+
mysqli_query($dbh, $SQL) or die (mysqli_error($dbh));
74+
75+
$done=false;
76+
while ($done==false)
77+
{
78+
echo "Set Admin password (8 characters minimum): ";
79+
$handle = fopen ("php://stdin","r");
80+
$password = trim(fgets($handle));
81+
if (strlen($password)>=8)
82+
{
83+
echo "Please confirm password again: ";
84+
$handle = fopen ("php://stdin","r");
85+
$password_cc = trim(fgets($handle));
86+
if ($password==$password_cc)
87+
{
88+
$done=true;
89+
}
90+
else
91+
{
92+
echo "Passwords do not match\n";
93+
}
94+
}
95+
}
96+
$options = ['cost' => 11];
97+
$hash=password_hash($password, PASSWORD_BCRYPT, $options);
98+
99+
$hash = base64_encode($hash);
100+
$hash = mysqli_real_escape_string($dbh,$hash);
101+
$SQL="INSERT INTO config (config_id,admin_pw,daily_report_time,reminder_time,daily_reminder_time) VALUES (1,'$hash','12:00:00',1,'19:00:00') ON DUPLICATE KEY UPDATE admin_pw = '$hash'";
102+
103+
mysqli_query($dbh,$SQL);
104+
echo mysqli_error($dbh);
105+
echo "Admin password set.\nPlease complete the configuration using the admin dashboard.";
106+
?>

‎task_reports.php

+528
Large diffs are not rendered by default.

‎tasks_admin/db_config.php

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?
2+
include '/var/www/sites/fpincludes/tasks/db_config.php';
3+
4+
?>

‎tasks_admin/index.php

+438
Large diffs are not rendered by default.

‎tasks_admin/proc_user_changes.php

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?
2+
3+
require_once('db_config.php');
4+
session_start();
5+
if (isset($_SESSION['authenticated']) && $_SESSION['authenticated']==1 && isset($_SESSION['login_expiration']) && $_SESSION['login_expiration'] >=time() && $_SESSION['login_expiration'] <=time()+10*60)
6+
{
7+
//file_put_contents('/var/www/sites/savetzpublishing.com/tasks_admin/post_log.txt', print_r($_POST, true).print_r($_SESSION,true),FILE_APPEND);
8+
$dbh = mysqli_connect($conn_hostname, $conn_username, $conn_password,$conn_database);
9+
$error=mysqli_error($dbh);
10+
if ($error)
11+
{
12+
die ($error);
13+
}
14+
$user_email_address=trim($_POST['user_email_address']);
15+
$user_name=trim($_POST['user_name']);
16+
$valid_status_array=array('active','inactive');
17+
$valid_user_type_array=array('normal','super','inactive','script');
18+
$user_type=trim($_POST['user_type']);
19+
$status=trim($_POST['status']);
20+
if (!in_array($status,$valid_status_array))
21+
{
22+
die('Error: Invalid user status.');
23+
}
24+
if (!in_array($user_type,$valid_user_type_array))
25+
{
26+
die('Error: Invalid user type.');
27+
}
28+
if ($user_name=='')
29+
{
30+
die ('Error: Empty user name.');
31+
}
32+
if (!filter_var($user_email_address, FILTER_VALIDATE_EMAIL))
33+
{
34+
die ('Invalid user email');
35+
}
36+
// check email duplicate
37+
$user_id=intval(trim($_POST['user_id']));
38+
39+
$user_email_address=mysqli_real_escape_string($dbh, $user_email_address);
40+
41+
$check_duplicate_email="SELECT * FROM users WHERE user_id != $user_id AND user_email_address LIKE '$user_email_address' LIMIT 1";
42+
$result=mysqli_query($dbh, $check_duplicate_email);
43+
if (mysqli_num_rows($result)==1)
44+
{
45+
die('Error: Email address already used by another user.');
46+
}
47+
48+
49+
$user_name=mysqli_real_escape_string($dbh, trim($_POST['user_name']));
50+
51+
$SQL="UPDATE users SET user_email_address='$user_email_address', user_name='$user_name', status='$status', user_type='$user_type' WHERE user_id=$user_id LIMIT 1";
52+
mysqli_query($dbh,$SQL);
53+
54+
echo 'Changes to '.$_POST['user_email_address'].' updated.';
55+
56+
}
57+
else
58+
{
59+
echo 'Authentication expired. Please reload the page to relogin.';
60+
}
61+
?>

‎tasks_admin/styles.css

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
input[type="time"]
2+
{
3+
4+
min-width:110px;
5+
}

‎tasks_me.php

+177
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
#!/usr/local/bin/php -q
2+
<?
3+
/*
4+
5+
This script processes incoming email from registered accounts line by line into task entries in the database.
6+
7+
Requires https://github.com/soundasleep/html2text
8+
9+
If more robust reply parsing is needed, check
10+
https://github.com/willdurand/EmailReplyParser
11+
https://pastebin.com/J0R5aCR1
12+
*/
13+
14+
15+
require_once('db_config.php');
16+
$dbh = mysqli_connect($conn_hostname, $conn_username, $conn_password,$conn_database);
17+
mysqli_set_charset($dbh,'utf8'); //sets connection charset to utf8 so smart quotes and other chars are stored correctly
18+
19+
20+
// read from stdin
21+
22+
$fd = fopen("php://stdin", "r");
23+
$email = "";
24+
25+
26+
while (!feof($fd)) {
27+
$email .= fread($fd, 1024);
28+
}
29+
30+
31+
fclose($fd);
32+
33+
34+
function is_registered_email($email)
35+
{
36+
/*
37+
38+
looks for registered email address in user table
39+
40+
*/
41+
42+
global $dbh;
43+
$email=mysqli_real_escape_string($dbh,$email);
44+
45+
46+
$SQL="SELECT * FROM users where user_email_address='$email' AND status='active' LIMIT 1";
47+
$result=mysqli_query($dbh,$SQL);
48+
49+
if ($result)
50+
{
51+
52+
return mysqli_fetch_assoc($result);
53+
}
54+
return false;
55+
56+
}
57+
$hit = preg_match('/\AFrom (?P<email_addr>[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4})\b/i',$email,$matches); //find incoming email address
58+
$hit = true;
59+
if(!$hit) {
60+
61+
die();
62+
}
63+
$email_addr=trim($matches['email_addr']);
64+
65+
if (!filter_var($email_addr, FILTER_VALIDATE_EMAIL,FILTER_NULL_ON_FAILURE)) //validate email address
66+
{
67+
die();
68+
}
69+
$user_match=is_registered_email($email_addr); // checks if it's from a registered email address
70+
if (!$user_match)
71+
{
72+
die();
73+
}
74+
75+
76+
$hit = preg_match('/boundary=(?P<boundary>.+)/i',$email,$matches);
77+
if (!$hit) // cannot find boundary. Might be pure plain text?
78+
{
79+
//if ($user_match['user_type']=='script') // removed this condition because iPhone will send text-only non-multipart mail
80+
81+
// Remove all lines that starts with html keywords
82+
$text_content_array=preg_split('/^Date\:.*/mi',$email);
83+
84+
$text_content=$text_content_array[1];
85+
$text_content=quoted_printable_decode($text_content); // decode quoted printable content
86+
$email_keyword_pattern='/^[\w\-]+:.*?(\r\n|\n|\r){2,}/mis';
87+
$text_content=preg_replace($email_keyword_pattern,'',$text_content,1);
88+
89+
if (stristr($text_content_array[0], "Content-Type: text/html") !== false)
90+
{
91+
require_once($html2text_loc.'html2text.php');
92+
$text_content= trim(convert_html_to_text($text_content));
93+
}
94+
95+
$text_content= preg_replace('/(^\w.+:\n)?(^>.*(\n|$)){2,}/mi', '', $text_content); // strip out replies
96+
97+
$quoted_email=preg_quote($from_email,'/');
98+
$text_content= preg_replace('/(\r\n|\n|\r){1}[^\n\r]*' . $quoted_email. '(.|\r\n|\n|\r)*/is', '', $text_content,1); // strip out auto inserted reply text from reminder email
99+
$quoted_email=preg_quote(trim($email_addr),'/');
100+
$text_content= preg_replace('/(\r\n|\n|\r){1}[^\n\r]*' . $quoted_email. '(.|\r\n|\n|\r)*/is', '', $text_content,1); // strip out auto inserted reply text from sender email
101+
102+
103+
}
104+
else
105+
{
106+
$matches['boundary']=trim($matches['boundary'],'"');
107+
$boundary_str='/'.preg_quote('--'.$matches['boundary']).'/';
108+
$content_array=preg_split($boundary_str,$email);
109+
array_shift($content_array);
110+
111+
$text_content=false;
112+
113+
foreach ($content_array as $segment)
114+
{
115+
116+
if (stristr($segment, "Content-Type: text/html") !== false)
117+
{
118+
$text_content = trim(preg_replace('/Content-Type:[^;]+;(\s*charset=.*)?/i', "", $segment));
119+
$text_content = trim(preg_replace('/Content-(Type|ID|Disposition|Transfer-Encoding):.*?(\r\n|\n|\r)/is', "", $text_content));
120+
$text_content=quoted_printable_decode($text_content); // decode quoted printable content
121+
require_once($html2text_loc.'html2text.php');
122+
123+
$text_content= trim(convert_html_to_text($text_content));
124+
125+
$text_content= preg_replace('/(^\w.+:\n)?(^>.*(\n|$)){2,}/mi', '', $text_content); // strip out replies
126+
127+
$quoted_email=preg_quote($from_email,'/');
128+
$text_content= preg_replace('/(\r\n|\n|\r){1}[^\n\r]*' . $quoted_email. '(.|\r\n|\n|\r)*/is', '', $text_content,1); // strip out auto inserted reply text from reminder email
129+
$quoted_email=preg_quote(trim($email_addr),'/');
130+
$text_content= preg_replace('/(\r\n|\n|\r){1}[^\n\r]*' . $quoted_email. '(.|\r\n|\n|\r)*/is', '', $text_content,1); // strip out auto inserted reply text from sender email
131+
132+
133+
break;
134+
}
135+
136+
}
137+
138+
139+
if (!$text_content) // can't find html. Try to look for plain text
140+
{
141+
foreach ($content_array as $segment)
142+
{
143+
if (stristr($segment, "Content-Type: text/plain") !== false)
144+
{
145+
$text_content = trim(preg_replace('/Content-Type:[^;]+;(\s*charset=.*)?/i', "", $segment));
146+
$text_content = trim(preg_replace('/Content-(Type|ID|Disposition|Transfer-Encoding):.*?(\r\n|\n|\r)/is', "", $text_content));
147+
$text_content=quoted_printable_decode($text_content);
148+
149+
$text_content= preg_replace('/(^\w.+:\n)?(^>.*(\n|$)){2,}/mi', '', $text_content); // strip out replies
150+
151+
// need to remove auto inserted texts in reply
152+
$quoted_email=preg_quote('tasks@savetzpublishing.com','/');
153+
$text_content= preg_replace('/(\r\n|\n|\r){1}[^\n\r]*' . $quoted_email. '(.|\r\n|\n|\r)*/is', '', $text_content,1); // strip out auto inserted reply text
154+
$quoted_email=preg_quote(trim($email_addr),'/');
155+
$text_content= preg_replace('/(\r\n|\n|\r){1}[^\n\r]*' . $quoted_email. '(.|\r\n|\n|\r)*/is', '', $text_content,1); // strip out auto inserted reply text
156+
157+
break;
158+
}
159+
}
160+
161+
}
162+
}
163+
$tasks_array=preg_split("/\r\n|\n|\r/", $text_content); // split lines into tasks and record each task
164+
165+
166+
foreach ($tasks_array as $task)
167+
{
168+
if (trim($task)!='')
169+
{
170+
$task=mysqli_real_escape_string($dbh,$task);
171+
$user_id=$user_match['user_id'];
172+
$SQL="INSERT INTO tasks (user_id, body) VALUES ($user_id, '$task')";
173+
174+
mysqli_query($dbh,$SQL);
175+
}
176+
}
177+
?>

0 commit comments

Comments
 (0)
Please sign in to comment.