email-import.php 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. <?php
  2. /**
  3. * Insipid
  4. * Personal web-bookmark-system
  5. *
  6. * Copyright 2016-2023 Johannes Keßler
  7. *
  8. * Development starting from 2011: Johannes Keßler
  9. * https://www.bananas-playground.net/projekt/insipid/
  10. *
  11. * creator:
  12. * Luke Reeves <luke@neuro-tech.net>
  13. *
  14. * This program is free software: you can redistribute it and/or modify
  15. * it under the terms of the GNU General Public License as published by
  16. * the Free Software Foundation, either version 3 of the License, or
  17. * (at your option) any later version.
  18. *
  19. * This program is distributed in the hope that it will be useful,
  20. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  21. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  22. * GNU General Public License for more details.
  23. *
  24. * You should have received a copy of the GNU General Public License
  25. * along with this program. If not, see http://www.gnu.org/licenses/gpl-3.0.
  26. *
  27. */
  28. mb_http_output('UTF-8');
  29. mb_internal_encoding('UTF-8');
  30. error_reporting(-1); // E_ALL & E_STRICT
  31. require('../config.php');
  32. date_default_timezone_set(TIMEZONE);
  33. ## set the error reporting
  34. ini_set('log_errors',true);;
  35. if(DEBUG === true) {
  36. ini_set('display_errors',true);
  37. }
  38. else {
  39. ini_set('display_errors',false);
  40. }
  41. // if the file needs to be in a web accessible folder
  42. // you can either use the provided htaccess file
  43. // or active the "protection" with a secret given by URL / cli param
  44. if(defined('EMAIL_JOB_PROTECT') && EMAIL_JOB_PROTECT === true
  45. && defined('EMAIL_JOB_PROTECT_SECRET')) {
  46. $_hiddenSouce = false;
  47. $cliOptions = getopt("",array("hiddenSouce::"));
  48. if(!empty($cliOptions)) {
  49. $_hiddenSouce = trim($cliOptions['hiddenSouce']);
  50. }
  51. elseif(isset($_GET['hiddenSouce']) && !empty($_GET['hiddenSouce'])) {
  52. $_hiddenSouce = trim($_GET['hiddenSouce']);
  53. }
  54. if($_hiddenSouce !== EMAIL_JOB_PROTECT_SECRET) {
  55. error_log('ERROR Required param wrong.');
  56. exit("401\n");
  57. }
  58. }
  59. require('../lib/summoner.class.php');
  60. require('../lib/tag.class.php');
  61. require('../lib/category.class.php');
  62. require('../lib/link.class.php');
  63. require('../lib/simple-imap.class.php');
  64. require('../lib/email-import-helper.class.php');
  65. # load only if needed
  66. use PHPMailer\PHPMailer\PHPMailer;
  67. use PHPMailer\PHPMailer\Exception;
  68. use PHPMailer\PHPMailer\SMTP;
  69. if(EMAIL_REPORT_BACK === true) {
  70. require('../lib/phpmailer/Exception.php');
  71. require('../lib/phpmailer/PHPMailer.php');
  72. require('../lib/phpmailer/SMTP.php');
  73. $phpmailer = new PHPMailer();
  74. if(DEBUG === true) $phpmailer->SMTPDebug = SMTP::DEBUG_SERVER;
  75. $phpmailer->isSMTP();
  76. $phpmailer->Host = EMAIL_SERVER;
  77. $phpmailer->SMTPAuth = true;
  78. $phpmailer->SMTPSecure = $phpmailer::ENCRYPTION_SMTPS;
  79. $phpmailer->Username = EMAIL_SERVER_USER;
  80. $phpmailer->Password = EMAIL_SERVER_PASS;
  81. $phpmailer->Port = EMAIL_SERVER_PORT_SMTP;
  82. $phpmailer->setFrom(EMAIL_REPLY_BACK_ADDRESS);
  83. $phpmailer->Subject = EMAIL_REPLY_BACK_SUBJECT;
  84. $phpmailer->Timeout = 3;
  85. if(DEBUG === true) $phpmailer->SMTPDebug = SMTP::DEBUG_SERVER;
  86. $phpmailer->SMTPOptions = array(
  87. 'ssl' => [
  88. 'verify_peer' => false,
  89. 'verify_peer_name' => false,
  90. 'allow_self_signed' => true
  91. ],
  92. );
  93. }
  94. ## DB connection
  95. $DB = new mysqli(DB_HOST, DB_USERNAME,DB_PASSWORD, DB_NAME);
  96. if ($DB->connect_errno) exit('Can not connect to MySQL Server');
  97. $DB->set_charset("utf8mb4");
  98. $DB->query("SET collation_connection = 'utf8mb4_bin'");
  99. $driver = new mysqli_driver();
  100. $driver->report_mode = MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT;;
  101. # the email reader
  102. $EmailReader = new SimpleImap();
  103. $emails = array();
  104. try {
  105. $EmailReader->connect();
  106. if(DEBUG === true) $EmailReader->mailboxStatus();
  107. }
  108. catch (Exception $e) {
  109. Summoner::sysLog('ERROR Email server connection failed: '.$e->getMessage());
  110. exit();
  111. }
  112. try {
  113. // emailid => info of the mail as an array
  114. // this is not the message-id
  115. $emails = $EmailReader->messageWithValidSubject(EMAIL_MARKER);
  116. }
  117. catch (Exception $e) {
  118. Summoner::sysLog('ERROR Can not process email messages: '.$e->getMessage());
  119. exit();
  120. }
  121. # process the emails and then move the emails
  122. $invalidProcessedEmails = array();
  123. $validProcessedEmails = array();
  124. if(!empty($emails)) {
  125. foreach($emails as $emailId=>$emailData) {
  126. $links = EmailImportHelper::extractEmailLinks($emailData['body']);
  127. if(!empty($links)) {
  128. if(DEBUG === true) Summoner::sysLog("Links in email: ".Summoner::cleanForLog($links));
  129. foreach($links as $linkstring) {
  130. # defaults
  131. $newdata['link'] = $linkstring;
  132. $newdata['description'] = '';
  133. $newdata['title'] = '';
  134. $newdata['image'] = '';
  135. $newdata['status'] = '3'; # moderation required
  136. $newdata['tagArr'] = array();
  137. $newdata['catArr'] = array();
  138. $newdata['hash'] = '';
  139. if(strstr($linkstring, "|")) {
  140. $_t = explode("|", $linkstring);
  141. $newdata['link'] = $_t[0];
  142. $newdata['catArr'] = Summoner::prepareTagOrCategoryStr($_t[1]);
  143. if(isset($_t[2])) {
  144. $newdata['tagArr'] = Summoner::prepareTagOrCategoryStr($_t[2]);
  145. }
  146. }
  147. $newdata['link'] = filter_var($newdata['link'], FILTER_SANITIZE_URL);
  148. $newdata['link'] = Summoner::addSchemeToURL($newdata['link']);
  149. if (!filter_var($newdata['link'], FILTER_VALIDATE_URL)) {
  150. error_log("ERROR Invalid URL: ".Summoner::cleanForLog($newdata['link']));
  151. if(DEBUG === true) Summoner::sysLog("Invalid URL: ".Summoner::cleanForLog($newdata['link']));
  152. continue;
  153. }
  154. $newdata['hash'] = md5($newdata['link']);
  155. $linkInfo = Summoner::gatherInfoFromURL($newdata['link']);
  156. if(!empty($linkInfo) && !empty($linkInfo['title'])) {
  157. $newdata['title'] = $linkInfo['title'];
  158. if(isset($linkInfo['description'])) {
  159. $newdata['description'] = $linkInfo['description'];
  160. }
  161. if(isset($linkInfo['image'])) {
  162. $newdata['image'] = $linkInfo['image'];
  163. }
  164. }
  165. else {
  166. error_log("WARN No valid title for link found: ".$newdata['link']);
  167. if(DEBUG === true) Summoner::sysLog("[WARN] No valid title for link found: ".Summoner::cleanForLog($newdata));
  168. array_push($invalidProcessedEmails, $emailData);
  169. continue;
  170. }
  171. if(DEBUG === true) Summoner::sysLog("New data ".Summoner::cleanForLog($newdata));
  172. $linkObj = new Link($DB);
  173. $linkID = false;
  174. # check for duplicate
  175. $existing = $linkObj->load($newdata['hash']);
  176. $DB->begin_transaction(MYSQLI_TRANS_START_READ_WRITE);
  177. if(!empty($existing) && isset($existing['id'])) {
  178. $linkID = $existing['id'];
  179. Summoner::sysLog('[INFO] Updating existing link with tag or category '.Summoner::cleanForLog($newdata['link']));
  180. }
  181. else {
  182. $linkObj = new Link($DB);
  183. try {
  184. $linkID = $linkObj->create(array(
  185. 'hash' => $newdata['hash'],
  186. 'link' => $newdata['link'],
  187. 'status' => $newdata['status'],
  188. 'description' => $newdata['description'],
  189. 'title' => $newdata['title'],
  190. 'image' => $newdata['image'],
  191. 'tagArr' => $newdata['tagArr'],
  192. 'catArr' => $newdata['catArr']
  193. ), true);
  194. } catch (Exception $e) {
  195. $_m = "[WARN] Can not create new link into DB." . $e->getMessage();
  196. Summoner::sysLog($_m);
  197. $emailData['importmessage'] = $_m;
  198. array_push($invalidProcessedEmails, $emailData);
  199. if (DEBUG === true) Summoner::sysLog($_m);
  200. if (DEBUG === true) Summoner::sysLog(Summoner::cleanForLog($newdata));
  201. continue;
  202. }
  203. }
  204. if(!empty($linkID)) {
  205. if(!empty($newdata['catArr'])) {
  206. foreach($newdata['catArr'] as $c) {
  207. $catObj = new Category($DB);
  208. $catObj->initbystring($c);
  209. $catObj->setRelation($linkID);
  210. unset($catObj);
  211. }
  212. }
  213. if(!empty($newdata['tagArr'])) {
  214. foreach($newdata['tagArr'] as $t) {
  215. $tagObj = new Tag($DB);
  216. $tagObj->initbystring($t);
  217. $tagObj->setRelation($linkID);
  218. unset($tagObj);
  219. }
  220. }
  221. $DB->commit();
  222. Summoner::sysLog("[INFO] Link successfully added/updated: ".$newdata['link']);
  223. array_push($validProcessedEmails,$emailData);
  224. }
  225. else {
  226. $DB->rollback();
  227. Summoner::sysLog("ERROR Link could not be added. SQL problem? ".$newdata['link']);
  228. $emailData['importmessage'] = "Link could not be added";
  229. array_push($invalidProcessedEmails,$emailData);
  230. }
  231. }
  232. }
  233. }
  234. }
  235. # if we have invalid import mails, ignore them, just log em
  236. # if EMAIL_REPORT_BACK is true then report back with errors if EMAIL_REPLY_BACK_VALID
  237. if(!empty($invalidProcessedEmails)) {
  238. Summoner::sysLog("[INFO] We have invalid import messages.");
  239. foreach ($invalidProcessedEmails as $invalidMail) {
  240. if(EmailImportHelper::canSendReplyTo($invalidMail['header_rfc822']->reply_toaddress)
  241. && !EmailImportHelper::isAutoReplyMessage($invalidMail['header_array'])) {
  242. $_address = PHPMailer::parseAddresses($invalidMail['header_rfc822']->reply_toaddress);
  243. $phpmailer->Body = $invalidMail['importmessage']."\n\n";
  244. $phpmailer->Body .= $invalidMail['body'];
  245. $phpmailer->addAddress($_address[0]['address']);
  246. $phpmailer->send();
  247. Summoner::sysLog("[INFO] Report back email to: ".$_address[0]['address']);
  248. }
  249. else {
  250. Summoner::sysLog("[WARN] Invalid message: ".$invalidMail['header_rfc822']->subject);
  251. }
  252. }
  253. }
  254. # move them to the processed / archive folder
  255. if(!empty($validProcessedEmails)) {
  256. Summoner::sysLog("[INFO] We have valid import messages.");
  257. foreach ($validProcessedEmails as $validMail) {
  258. $EmailReader->moveMessage($validMail['uid']);
  259. Summoner::sysLog("[INFO] Mail moved to archive ".$validMail['header_rfc822']->subject);
  260. }
  261. }
  262. $DB->close();
  263. $EmailReader->close();