email-import.php 9.7 KB

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