email-import.php 9.9 KB

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