function init($cfg) { //! defaults if (empty($cfg["sqldir"])) { $cfg["sqldir"] = "data/temp"; } if (empty($cfg["pagesdir"])) { $cfg["pagesdir"] = "pages"; } $this->pagesdir = $cfg["pagesdir"]; //! if it's a special sql refresh request if (Core::$core->url == "sqlrefresh" || isset($_REQUEST["sqlrefresh"])) { //look data source if (!DS::db()) { die("ERROR: no db"); } //read the changes $sqlFiles = @glob($cfg["sqldir"] . "/sqlchanges-*.sql"); foreach ($sqlFiles as $sf) { //get sql commands from file $sqls = str_getcsv(@file_get_contents($sf), ";"); @unlink($sf); //execute one by one foreach ($sqls as $query) { DS::exec($query); } } die("OK"); } //! check if there's a CMS generated file for the url //! if so, add route for it $c = @explode("/", Core::$core->url); while (!empty($c)) { $f = implode("-SLASH-", $c); if (file_exists($cfg["pagesdir"] . "/" . $f . ".php")) { self::$page = $cfg["pagesdir"] . "/" . $f . ".php"; Http::route(Core::$core->url, "\\PHPPE\\EplosCMS"); break; } //if not found, put last part in parameters array self::$params[] = array_pop($c); } //reverse the parameters as they were popped in reverse order self::$params = @array_reverse(self::$params); return true; }
/** * construct mime email and send it out. * * @param string transport backend */ public function send($via = '') { //! allow temporarly override backend. Only url allowed, not array if (!empty($via)) { $this->getBackEnd(@parse_url($via)); } //! sanity checks if (empty($this->via)) { throw new EmailException(L('Mailer backend not configured!')); } if (empty($this->message)) { throw new EmailException(L('Empty message!')); } if (empty($this->header['Subject'])) { throw new EmailException(L('No subject given!')); } if (empty($this->header['To'])) { throw new EmailException(L('No recipient given!')); } if (count($this->header['To']) > 64) { // @codeCoverageIgnoreStart throw new EmailException(L('Too many recipients!')); } // @codeCoverageIgnoreEnd $this->address(self::$sender, 'From'); $local = @explode('@', array_keys($this->header['From'])[0])[1]; if (empty($local)) { $local = 'localhost'; } $id = sha1(uniqid()) . '_' . microtime(true) . '@' . $local; //! message type $isHtml = preg_match('/<html/i', $this->message); //! *** handle transport backends that does not require mime message *** if ($this->via == 'db') { //! mail queue in database if (empty(DS::db())) { throw new EmailException(L('DB queue backend without configured datasource!')); } return DS::exec('INSERT INTO email_queue (data,created) VALUES (?,?);', [$this->get(), Core::$core->now]) > 0 ? true : false; } elseif ($this->via == 'phpmailer') { //! PHP Mailer if (!ClassMap::has('PHPMailer')) { throw new EmailException(L('PHPMailer not installed!')); } // @codeCoverageIgnoreStart $mail = new \PHPMailer(); $mail->Subject = $this->header['Subject']; $mail->SetFrom(implode(', ', $this->header['From'])); if (!empty($this->header['Reply-To'])) { $mail->AddReplyTo($this->header['Reply-To']); } foreach (['To', 'Cc', 'Bcc'] as $type) { foreach ($this->header[$type] as $rcpt => $full) { list($name) = explode('<', $full); $mail->SetAddress(self::$forge ? self::$forge : $rcpt, trim($name)); } } foreach ($this->attach as $attach) { $mail->AddAttachment($attach['file']); } $mail->MsgHTML($this->message); return $mail->Send(); // @codeCoverageIgnoreEnd } //! *** build mime message *** //! mime headers $headers['MIME-Version'] = '1.0'; $headers['Content-Class'] = 'urn:content-classes:message'; $headers['Content-Type'] = 'text/plain;charset=utf-8'; $headers['Content-Transfer-Encoding'] = '8bit'; $headers['Sender'] = implode(', ', $this->header['From']); $headers['Message-ID'] = '<' . $id . '>'; $headers['Date'] = date('r', Core::$core->now); $headers['X-Mailer'] = 'PHPPE ' . VERSION; foreach ($this->header as $k => $v) { $headers[$k] = is_array($v) ? implode(', ', $v) : $v; } //! mime body if (!$isHtml) { //! plain text email $message = wordwrap($this->message, 78); } else { $boundary = uniqid(); //! html email with a plain text alternative $headers['Content-Type'] = "multipart/alternative;\n boundary=\"" . $boundary . '"'; $message = "This is a multi-part message in MIME format.\r\n"; $message .= '--' . $boundary . "\n" . "Content-type: text/plain;charset=utf-8\n" . "Content-Transfer-Encoding: 8bit\n\n" . wordwrap(preg_replace("/\\<.*?\\>/m", '', strtr($this->message, ['</h1>' => "\n\n", '</h2>' => "\n\n", '</h3>' => "\n\n", '</h4>' => "\n\n", '</h5>' => "\n\n", '</h6>' => "\n\n", '</p>' => "\n\n", '</td>' => "\t", '</tr>' => "\n", '</table>' => "\n", '<br>' => "\n", '<br/>' => "\n"])), 78) . "\r\n"; //! look for images in html, if found, we have to create a multipart/related block if (preg_match_all("/(http|images\\/|data\\/).*?\\.(gif|png|jpe?g)/mis", $this->message, $m, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) { $boundary2 = uniqid(); $diff = 0; foreach ($m as $k => $c) { //if it's a absolute url, don't replace it if ($c[1][0] == 'http') { unset($m[$k]); continue; } //generate a cid $m[$k][3] = uniqid(); //get local path for filename if ($c[1][0] == 'data/' && file_exists($c[0][0])) { $m[$k][4] = $c[0][0]; } elseif (file_exists('public/' . $c[0][0])) { $m[$k][4] = 'public/' . $c[0][0]; } else { foreach (['vendor/phppe/*/', 'vendor/*/', 'vendor/*/*/'] as $d) { if ($m[$k][4] = @glob($d . $c[0][0])[0]) { break; } } } //replace image url in message $new = 'cid:' . $m[$k][3]; $this->message = substr($this->message, 0, $c[0][1] + $diff) . $new . substr($this->message, $c[0][1] + $diff + strlen($c[0][0])); $diff -= strlen($c[0][0]) - strlen($new); } } if (!empty($m)) { //! add the html part as related $message .= '--' . $boundary . "\n" . "Content-type: multipart/related;\n boundary=\"" . $boundary2 . "\"\n\n" . "This is a multi-part message in MIME format.\r\n--" . $boundary2 . "\n" . "Content-Type: text/html;charset=utf-8\n" . "Content-Transfer-Encoding: 8bit\n\n" . wordwrap($this->message, 78) . "\r\n"; foreach ($m as $c) { $data = empty($c[4]) ? '' : (substr($c[4], 0, 4) == 'http' ? Core::get($c[4]) : file_get_contents($c[4])); if (!$data) { continue; } //get content $message .= '--' . $boundary2 . "\n" . 'Content-Type: image/' . ($c[2][0] == 'jpg' ? 'jpeg' : $c[2][0]) . "\n" . "Content-Transfer-Encoding: base64\n" . "Content-Disposition: inline\n" . 'Content-ID: <' . $c[3] . ">\n\n" . chunk_split(base64_encode($data), 78, "\n"); } $message .= '--' . $boundary2 . "--\n"; } else { $message .= '--' . $boundary . "\n" . "Content-type: text/html;charset=utf-8\n" . "Content-Transfer-Encoding: 8bit\n\n" . wordwrap($this->message, 78) . "\r\n"; } $message .= '--' . $boundary . "--\n"; } if (!empty($this->attach)) { $boundary = uniqid(); $headers['Content-Type'] = "multipart/mixed;\n boundary=\"" . $boundary . '"'; $message = "This is a multi-part message in MIME format.\r\n--" . $boundary . "\n" . $message; foreach ($this->attach as $attach) { $data = !empty($attach['data']) ? $attach['data'] : (substr($attach['file'], 0, 4) == 'http' ? Core::get($attach['file']) : file_get_contents(substr($attach['file'], 0, 6) == 'images' ? @glob('vendor/phppe/*/' . $attach['file'])[0] : $attach['file'])); if (!$data) { continue; } $message .= '--' . $boundary . "\n" . 'Content-type: ' . (!empty($attach['mime']) ? $attach['mime'] : 'application-octet-stream') . "\n" . 'Content-Disposition: attachment' . (!empty($attach['file']) ? ";\n filename=\"" . basename($attach['file']) . '"' : '') . "\n" . "Content-Transfer-Encoding: base64\n\n" . chunk_split(base64_encode($data), 78, "\n"); } $message .= '--' . $boundary . "--\n"; } //! flat headers $header = ''; //! redirect message to a specific address (for testing) if (!empty(self::$forge)) { // @codeCoverageIgnoreStart $headers['To'] = self::$forge; $headers['Cc'] = ''; $headers['Bcc'] = ''; // @codeCoverageIgnoreEnd } foreach ($headers as $k => $v) { $header .= $k . ': ' . $v . "\r\n"; } //! log that we are sending a mail Core::log('I', 'To: ' . $headers['To'] . ', Subject: ' . $headers['Subject'] . ', ID: ' . $id, 'email'); //if email directory exists, save the full mime message as well for debug @file_put_contents('data/log/email/' . $id, 'Backend: ' . $this->via . ' ' . self::$user . ':' . self::$pass . '@' . self::$host . ':' . self::$port . "\r\n\r\n" . $header . "\r\n" . $message); //! *** handle transport backends *** switch ($this->via) { //! only log and possibly save message in file, do not send for real. Nothing left to do case 'log': break; //! return constructed mime message //! return constructed mime message case 'mime': return $header . "\r\n" . $message; break; //! use php's mail() //! use php's mail() case 'mail': $to = $headers['To']; $subj = $headers['Subject']; unset($headers['To']); unset($headers['Subject']); $header = ''; foreach ($headers as $k => $v) { $header .= $k . ': ' . $v . "\r\n"; } if (!mail($to, $subj, $message, $header)) { Core::log('E', 'mail() failed, To: ' . $to . ', Subject: ' . $subj . ', ID: ' . $id, 'email'); return false; } // @codeCoverageIgnoreStart break; //! sendmail through pipe //! sendmail through pipe case 'sendmail': $f = @popen('/usr/sbin/sendmail -t -i', 'w'); if ($f) { fputs($f, $header . "\r\n" . $message); pclose($f); } else { Core::log('E', 'mail() failed, To: ' . $headers['To'] . ', Subject: ' . $headers['Subject'] . ', ID: ' . $id, 'email'); return false; } break; //! this is how real programmers do it, let's speak smtp directly! //! this is how real programmers do it, let's speak smtp directly! default: //open socket $s = @fsockopen(self::$host, self::$port, $en, $es, 5); $l = ''; //get welcome message if ($s) { stream_set_timeout($s, 5); $l = fgets($s, 1024); } if (!$s || substr($l, 0, 3) != '220') { Core::log('E', 'connection error to ' . self::$host . ':' . self::$port . ', ' . trim($l), 'email'); return false; } //we silently assume we got 8BITMIME here, it's a safe assumption as of 2016 while ($l[3] == '-') { $l = fgets($s, 1024); } //greet remote fputs($s, 'EHLO ' . $local . "\r\n"); $l = fgets($s, 1024); while ($l[3] == '-') { $l = fgets($s, 1024); } //tell who are sending fputs($s, 'MAIL FROM: <' . array_keys($this->header['From'])[0] . ">\r\n"); $l = fgets($s, 1024); if (substr($l, 0, 3) != '250') { PPHPE3::log('E', 'from error: ' . trim($l), 'email'); return false; } //to whom $addresses = array_merge(array_keys($this->header['To']), array_keys($this->header['Cc']), array_keys($this->header['Bcc'])); foreach ($addresses as $a) { fputs($s, 'RCPT TO: <' . $a . ">\r\n"); $l = fgets($s, 1024); if (substr($l, 0, 3) != '250') { Core::log('E', 'recipient error: ' . trim($l), 'email'); } } //the message fputs($s, "DATA\r\n"); $l = fgets($s, 1024); if (substr($l, 0, 3) != '250') { Core::log('E', 'data error: ' . trim($l), 'email'); return false; } fputs($header . "\r\n" . str_replace(array("\n.\n", "\n.\r"), array("\n..\n", "\n..\r"), $message) . "\r\n.\r\n"); $l = fgets($s, 1024); if (substr($l, 0, 3) != '250') { Core::log('E', 'data send error: ' . trim($l), 'email'); return false; } //say bye fputs($s, "QUIT\r\n"); fclose($s); // @codeCoverageIgnoreEnd } return true; }
public static function arr2str($o, $s = '', $c = ' ') { if (!is_array($s)) { $s = str_getcsv($s, ','); } $r = ''; $d = DS::db(); if (is_string($o)) { $o = [$o]; } foreach ($o as $k => $v) { if (!in_array($k, $s)) { $r .= ($r ? $c : '') . $k . '=' . ($c == ',' && !empty($d) ? $d->quote($v) : "'" . str_replace(["\r", "\n", "\t", ""], ['\\r', '\\n', '\\t', '\\x1a'], addslashes($v)) . "'"); } } return $r; }
/** * Save a page * * @param force use of insert instead of update * * @return boolean success */ function save($force = false) { //! check input if (!empty($this->id) && $this->id[0] == "/") { $this->id = substr($this->id, 1); } $this->id = strtr($this->id, ["\\'" => "", "\"" => "", "#" => "", "?" => ""]); if (empty($this->id)) { throw new \Exception(L('No page id')); } if (empty(Core::$user->id) || !Core::$user->has("siteadm|webadm")) { throw new \Exception(L('No user id')); } $d = DS::db(); if (empty($d)) { throw new \Exception('no ds'); } //! set up properties $this->ownerid = 0; $this->lockd = ""; $this->modifyd = date("Y-m-d H:i:s", Core::$core->now); $this->modifyid = Core::$user->id; if (static::$_history || $force) { $this->created = $this->modifyd; } //! build the arguments array $a = []; foreach ($this as $k => $v) { if ($k[0] != '_' && $k != "created") { $a[$k] = is_scalar($v) ? $v : json_encode($v); } } $a['publishid'] = static::$_history ? 0 : Core::$user->id; //! write audit log Core::log('A', sprintf("Page %s saved by %s", $this->id, Core::$user->name), "cmsaudit"); //! save page if (!DS::exec(!static::$_history && !$force ? 'UPDATE ' . static::$_table . ' SET ' . implode('=?,', array_keys($a)) . '=? WHERE id=' . $d->quote($this->id) . ' AND lang=' . $d->quote($this->lang) : 'INSERT INTO ' . static::$_table . ' (' . implode(',', array_keys($a)) . ') VALUES (?' . str_repeat(',?', count($a) - 1) . ')', array_values($a))) { return false; } //! purge old records DS::exec("DELETE FROM " . static::$_table . " WHERE id=? AND lang=? AND publishid!=0 AND created not in (SELECT created FROM " . static::$_table . " WHERE id=? AND lang=? ORDER BY created desc limit " . (static::$_history ? max([intval(Core::lib("CMS")->purge), 1]) : 1) . ")", [$this->id, $this->lang, $this->id, $this->lang]); return true; }