email-import.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. <?php
  2. /**
  3. * Insipid
  4. * Personal web-bookmark-system
  5. *
  6. * Copyright 2016-2020 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. define('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);
  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['search'] = '';
  139. $newdata['tagArr'] = array();
  140. $newdata['catArr'] = array();
  141. $newdata['hash'] = '';
  142. if(strstr($linkstring, "|")) {
  143. $_t = explode("|", $linkstring);
  144. $newdata['link'] = $_t[0];
  145. $newdata['catArr'] = Summoner::prepareTagOrCategoryStr($_t[1]);
  146. if(isset($_t[2])) {
  147. $newdata['tagArr'] = Summoner::prepareTagOrCategoryStr($_t[2]);
  148. }
  149. }
  150. $newdata['link'] = filter_var($newdata['link'], FILTER_SANITIZE_URL);
  151. $newdata['link'] = Summoner::addSchemeToURL($newdata['link']);
  152. if (!filter_var($newdata['link'], FILTER_VALIDATE_URL)) {
  153. error_log("ERROR Invalid URL: ".var_export($newdata['link'],true));
  154. if(DEBUG === true) var_dump($newdata['link']);
  155. continue;
  156. }
  157. $newdata['hash'] = md5($newdata['link']);
  158. $linkInfo = Summoner::gatherInfoFromURL($newdata['link']);
  159. if(!empty($linkInfo) && !empty($linkInfo['title'])) {
  160. $newdata['title'] = $linkInfo['title'];
  161. if(isset($linkInfo['description'])) {
  162. $newdata['description'] = $linkInfo['description'];
  163. }
  164. if(isset($linkInfo['image'])) {
  165. $newdata['image'] = $linkInfo['image'];
  166. }
  167. }
  168. else {
  169. error_log("WARN No valid title for link found: ".$newdata['link']);
  170. if(DEBUG === true) var_dump("WARN No valid title for link found: ".var_export($newdata,true));
  171. array_push($invalidProcessedEmails, $emailData);
  172. continue;
  173. }
  174. $newdata['search'] = $newdata['title'];
  175. $newdata['search'] .= ' '.$newdata['description'];
  176. $newdata['search'] .= ' '.implode(" ",$newdata['tagArr']);
  177. $newdata['search'] .= ' '.implode(" ",$newdata['catArr']);
  178. $newdata['search'] = trim($newdata['search']);
  179. $newdata['search'] = strtolower($newdata['search']);
  180. if(DEBUG === true) var_dump($newdata);
  181. $linkObj = new Link($DB);
  182. $linkID = false;
  183. # check for duplicate
  184. $existing = $linkObj->load($newdata['hash']);
  185. $DB->begin_transaction(MYSQLI_TRANS_START_READ_WRITE);
  186. if(!empty($existing) && isset($existing['id'])) {
  187. $linkID = $existing['id'];
  188. error_log('INFO Updating existing link with tag or category '.$newdata['link']);
  189. }
  190. else {
  191. $linkObj = new Link($DB);
  192. try {
  193. $linkID = $linkObj->create(array(
  194. 'hash' => $newdata['hash'],
  195. 'search' => $newdata['search'],
  196. 'link' => $newdata['link'],
  197. 'status' => $newdata['status'],
  198. 'description' => $newdata['description'],
  199. 'title' => $newdata['title'],
  200. 'image' => $newdata['image']
  201. ), true);
  202. } catch (Exception $e) {
  203. $_m = "WARN Can not create new link into DB." . $e->getMessage();
  204. error_log($_m);
  205. $emailData['importmessage'] = $_m;
  206. array_push($invalidProcessedEmails, $emailData);
  207. if (DEBUG === true) var_dump($_m);
  208. if (DEBUG === true) var_dump($newdata);
  209. continue;
  210. }
  211. }
  212. if(!empty($linkID)) {
  213. if(!empty($newdata['catArr'])) {
  214. foreach($newdata['catArr'] as $c) {
  215. $catObj = new Category($DB);
  216. $catObj->initbystring($c);
  217. $catObj->setRelation($linkID);
  218. unset($catObj);
  219. }
  220. }
  221. if(!empty($newdata['tagArr'])) {
  222. foreach($newdata['tagArr'] as $t) {
  223. $tagObj = new Tag($DB);
  224. $tagObj->initbystring($t);
  225. $tagObj->setRelation($linkID);
  226. unset($tagObj);
  227. }
  228. }
  229. $DB->commit();
  230. error_log("INFO Link successfully added/updated: ".$newdata['link']);
  231. array_push($validProcessedEmails,$emailData);
  232. }
  233. else {
  234. $DB->rollback();
  235. error_log("ERROR Link could not be added. SQL problem? ".$newdata['link']);
  236. $emailData['importmessage'] = "Link could not be added";
  237. array_push($invalidProcessedEmails,$emailData);
  238. }
  239. }
  240. }
  241. }
  242. }
  243. # if we have invalid import mails, ignore them, just log em
  244. # if EMAIL_REPORT_BACK is true then report back with errors if EMAIL_REPLY_BACK_VALID
  245. if(!empty($invalidProcessedEmails)) {
  246. error_log("INFO We have invalid import messages.");
  247. foreach ($invalidProcessedEmails as $invalidMail) {
  248. if(EmailImportHelper::canSendReplyTo($invalidMail['header_rfc822']->reply_toaddress)
  249. && !EmailImportHelper::isAutoReplyMessage($invalidMail['header_array'])) {
  250. $_address = PHPMailer::parseAddresses($invalidMail['header_rfc822']->reply_toaddress);
  251. $phpmailer->Body = $invalidMail['importmessage']."\n\n";
  252. $phpmailer->Body .= $invalidMail['body'];
  253. $phpmailer->addAddress($_address[0]['address']);
  254. $phpmailer->send();
  255. error_log("INFO Report back email to: ".$_address[0]['address']);
  256. }
  257. else {
  258. error_log("WARN Invalid message: ".$invalidMail['header_rfc822']->subject);
  259. }
  260. }
  261. }
  262. # move them to the processed / archive folder
  263. if(!empty($validProcessedEmails)) {
  264. error_log("INFO We have valid import messages.");
  265. foreach ($validProcessedEmails as $validMail) {
  266. $EmailReader->moveMessage($validMail['uid']);
  267. error_log("INFO Mail moved to archive ".$validMail['header_rfc822']->subject);
  268. }
  269. }
  270. $DB->close();
  271. $EmailReader->close();