Merge pull request #112 from ivandiazwm/master

Prepare 4.1.1
This commit is contained in:
Ivan Diaz 2017-12-13 19:28:33 -03:00 committed by GitHub
commit 8e6d28fff8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 671 additions and 16 deletions

View File

@ -1,6 +1,6 @@
![OpenSupports](http://www.opensupports.com/logo.png) ![OpenSupports](http://www.opensupports.com/logo.png)
[![Build Status](https://travis-ci.org/opensupports/opensupports.svg?branch=master)](https://travis-ci.org/opensupports/opensupports) v4.1.0 [![Build Status](https://travis-ci.org/opensupports/opensupports.svg?branch=master)](https://travis-ci.org/opensupports/opensupports) v4.1.1
OpenSupports is an open source ticket system built primarly with PHP and ReactJS. OpenSupports is an open source ticket system built primarly with PHP and ReactJS.
Please, visit our website for more information: [http://www.opensupports.com/](http://www.opensupports.com/) Please, visit our website for more information: [http://www.opensupports.com/](http://www.opensupports.com/)
@ -77,7 +77,7 @@ Just as there is the `gulp dev` task for development, there is also a `gulp prod
4. Run the MySQL server 4. Run the MySQL server
`sudo /etc/init.d/mysql start` `sudo /etc/init.d/mysql start`
##### BACKEND API RUBY TESTING ##### BACKEND API RUBY TESTING
1. Install ruby `sudo apt-get install ruby-full` 1. Install ruby `sudo apt-get install ruby-full`

View File

@ -1,6 +1,6 @@
{ {
"name": "OpenSupports", "name": "OpenSupports",
"version": "4.1.0", "version": "4.1.1",
"author": "Ivan Diaz <contact@opensupports.com>", "author": "Ivan Diaz <contact@opensupports.com>",
"description": "Open source ticket system made with PHP and ReactJS", "description": "Open source ticket system made with PHP and ReactJS",
"repository": { "repository": {

View File

@ -83,9 +83,10 @@ class InitSettingsController extends Controller {
'registration' => !!Controller::request('registration'), 'registration' => !!Controller::request('registration'),
'user-system-enabled' => !!Controller::request('user-system-enabled'), 'user-system-enabled' => !!Controller::request('user-system-enabled'),
'last-stat-day' => date('YmdHi', strtotime(' -12 day ')), 'last-stat-day' => date('YmdHi', strtotime(' -12 day ')),
'ticket-gap' => Hashing::generateRandomPrime(1000000, 9999999), 'ticket-gap' => Hashing::generateRandomPrime(100000, 999999),
'file-gap' => Hashing::generateRandomPrime(1000000, 9999999), 'ticket-first-number' => Hashing::generateRandomNumber(100000, 999999),
'file-first-number' => Hashing::generateRandomNumber(1000000, 9999999), 'file-gap' => Hashing::generateRandomPrime(100000, 999999),
'file-first-number' => Hashing::generateRandomNumber(100000, 999999),
'file-quantity' => 0, 'file-quantity' => 0,
'session-prefix' => 'opensupports-'.Hashing::generateRandomToken().'_' 'session-prefix' => 'opensupports-'.Hashing::generateRandomToken().'_'
]); ]);

View File

@ -26,8 +26,4 @@ class LinearCongruentialGenerator {
return ($this->first - $this->min + $offset * $this->gap) % ($this->max - $this->min + 1) + $this->min; return ($this->first - $this->min + $offset * $this->gap) % ($this->max - $this->min + 1) + $this->min;
} }
public function generateFirst() {
return Hashing::generateRandomNumber($this->min, $this->max);
}
} }

View File

@ -36,10 +36,10 @@ class MailSender {
public function setTemplate($type, $config) { public function setTemplate($type, $config) {
$mailTemplate = MailTemplate::getTemplate($type); $mailTemplate = MailTemplate::getTemplate($type);
$compiledMailContent = $mailTemplate->compile($config); $compiledMailContent = $mailTemplate->compile($config);
$this->mailOptions = array_merge($this->mailOptions, $compiledMailContent); $this->mailOptions = array_merge($this->mailOptions, $compiledMailContent);
} }
public function send() { public function send() {
$mailerInstance = $this->getMailerInstance(); $mailerInstance = $this->getMailerInstance();
@ -49,6 +49,7 @@ class MailSender {
throw new Exception('Mail sending data not available'); throw new Exception('Mail sending data not available');
} }
$mailerInstance->ClearAllRecipients();
$mailerInstance->addAddress($this->mailOptions['to']); $mailerInstance->addAddress($this->mailOptions['to']);
$mailerInstance->Subject = $this->mailOptions['subject']; $mailerInstance->Subject = $this->mailOptions['subject'];
$mailerInstance->Body = $this->mailOptions['body']; $mailerInstance->Body = $this->mailOptions['body'];
@ -88,4 +89,4 @@ class MailSender {
return $this->mailerInstance; return $this->mailerInstance;
} }
} }

View File

@ -78,10 +78,10 @@ class Ticket extends DataStore {
$ticketQuantity = Ticket::count(); $ticketQuantity = Ticket::count();
if ($ticketQuantity === 0) { if ($ticketQuantity === 0) {
$ticketNumber = $linearCongruentialGenerator->generateFirst(); $ticketNumber = Setting::getSetting('ticket-first-number')->value;
} else { } else {
$linearCongruentialGenerator->setGap(Setting::getSetting('ticket-gap')->value); $linearCongruentialGenerator->setGap(Setting::getSetting('ticket-gap')->value);
$linearCongruentialGenerator->setFirst(Ticket::getTicket(1)->ticketNumber); $linearCongruentialGenerator->setFirst(Setting::getSetting('ticket-first-number')->value);
$ticketNumber = $linearCongruentialGenerator->generate($ticketQuantity); $ticketNumber = $linearCongruentialGenerator->generate($ticketQuantity);
} }

View File

@ -16,7 +16,7 @@ class LinearCongruentialGeneratorTest extends TestCase {
$linearCongruentialGenerator = new LinearCongruentialGenerator(); $linearCongruentialGenerator = new LinearCongruentialGenerator();
$linearCongruentialGenerator->setRange($min, $max); $linearCongruentialGenerator->setRange($min, $max);
$linearCongruentialGenerator->setGap(Hashing::generateRandomPrime($min, $max)); $linearCongruentialGenerator->setGap(Hashing::generateRandomPrime($min, $max));
$linearCongruentialGenerator->setFirst($linearCongruentialGenerator->generateFirst()); $linearCongruentialGenerator->setFirst(Hashing::generateRandomNumber($min, $max));
$used = []; $used = [];

View File

@ -0,0 +1,4 @@
<?php
require_once '../mysql_connect.php';
$mysql->query(file_get_contents('./4.1.0.sql'));

View File

@ -0,0 +1,100 @@
<?php
require_once '../mysql_connect.php';
require_once './libs/Hashing.php';
require_once './libs/LinearCongruentialGenerator.php';
require_once './libs/MailSender.php';
$ticketGap = Hashing::generateRandomPrime(1000000, 9999999);
$fileGap = Hashing::generateRandomPrime(1000000, 9999999);
$ticketFirstNumber = Hashing::generateRandomNumber(1000000, 9999999);
$fileFirstNumber = Hashing::generateRandomNumber(1000000, 9999999);
$mysql->query("UPDATE setting SET value='$ticketGap' WHERE name='ticket-gap'");
$mysql->query("UPDATE setting SET value='$fileGap' WHERE name='file-gap'");
$mysql->query("UPDATE setting SET value='$ticketFirstNumber' WHERE name='ticket-first-number'");
$mysql->query("UPDATE setting SET value='$fileFirstNumber' WHERE name='file-first-number'");
$smtpHost = $mysql->query("SELECT value FROM setting WHERE name='smtp-host'")->fetch_array(MYSQLI_ASSOC)['value'];
$smtpPort = $mysql->query("SELECT value FROM setting WHERE name='smtp-port'")->fetch_array(MYSQLI_ASSOC)['value'];
$smtpUser = $mysql->query("SELECT value FROM setting WHERE name='smtp-user'")->fetch_array(MYSQLI_ASSOC)['value'];
$smtpPassword = $mysql->query("SELECT value FROM setting WHERE name='smtp-pass'")->fetch_array(MYSQLI_ASSOC)['value'];
$noReplyEmail = $mysql->query("SELECT value FROM setting WHERE name='no-reply-email'")->fetch_array(MYSQLI_ASSOC)['value'];
$userSystemEnabled = $mysql->query("SELECT value FROM setting WHERE name='user-system-enabled'")->fetch_array(MYSQLI_ASSOC)['value'];
$url = $mysql->query("SELECT value FROM setting WHERE name='url'")->fetch_array(MYSQLI_ASSOC)['value'];
$mailSender = MailSender::getInstance();
$mailSender->setConnectionSettings(
$smtpHost, $smtpPort, $smtpUser, $smtpPassword, $noReplyEmail
);
function compileString($string, $config) {
$compiledString = $string;
foreach ($config as $configName => $configValue) {
$compiledString = str_replace("{{{$configName}}}", $configValue, $compiledString);
}
return $compiledString;
}
$migrationMail = file_get_contents('./libs/migration-mail.html');
if($tickets = $mysql->query("SELECT * FROM ticket ORDER BY id ASC")) {
$linearCongruentialGenerator = new LinearCongruentialGenerator();
$linearCongruentialGenerator->setGap($ticketGap);
$linearCongruentialGenerator->setFirst($ticketFirstNumber);
$offset = 0;
$emails = [];
while($ticket = $tickets->fetch_assoc()) {
$ticketId = $ticket['id'];
$ticketNumber = $linearCongruentialGenerator->generate($offset);
$mysql->query("UPDATE ticket SET ticket_number='$ticketNumber' WHERE id='$ticketId'");
if(array_key_exists('author_email', $ticket) && $ticket['author_email']) {
if(!array_key_exists($ticket['author_email'], $emails)) {
$emails[$ticket['author_email']] = [];
}
array_push(
$emails[$ticket['author_email']],
[
'old_number' => $ticket['ticket_number'],
'new_number' => $ticketNumber,
'title' => $ticket['title']
]
);
}
$offset++;
}
foreach($emails as $email => $emailTickets) {
$ticketString = '';
foreach($emailTickets as $ticket) {
$ticketString .= '<p>';
$ticketString .= $ticket['old_number'];
$ticketString .= ' => ';
$ticketString .= $ticket['new_number'];
$ticketString .= ' ';
$ticketString .= htmlentities($ticket['title']);
$ticketString .= '</p>';
$ticketString .= PHP_EOL;
}
$mailSender->setMailContent([
'to' => $email,
'subject' => 'Tickets have been updated',
'body' => compileString($migrationMail, [
'url' => $url,
'email' => $email,
'tickets' => $ticketString,
])
]);
$mailSender->send();
}
}

View File

@ -0,0 +1,44 @@
<?php
class Hashing {
public static function hashPassword($password) {
return password_hash($password, PASSWORD_DEFAULT);
}
public static function verifyPassword($password, $hash) {
return password_verify($password, $hash);
}
public static function generateRandomToken() {
return md5(uniqid(rand()));
}
public static function generateRandomNumber($min, $max) {
return rand($min, $max);
}
public static function generateRandomPrime($min, $max) {
$number = Hashing::generateRandomNumber($min, $max);
while(!Hashing::isPrime($number)) {
$number = Hashing::generateRandomNumber($min, $max);
}
return $number;
}
public static function isPrime($number) {
$sqrt = sqrt($number);
$prime = true;
if($number <= 1) return false;
for($i = 2; $i <= $sqrt; $i++) {
if($number % $i === 0) {
$prime = false;
break;
}
}
return $prime;
}
}

View File

@ -0,0 +1,29 @@
<?php
class LinearCongruentialGenerator {
private $gap;
private $first;
private $min = 100000;
private $max = 999999;
public function setRange($min, $max) {
$this->min = $min;
$this->max = $max;
}
public function setGap($gap) {
if(!Hashing::isPrime($gap)) throw new Exception('LinearCongruentialGenerator: gap must be prime');
$this->gap = $gap;
}
public function setFirst($first) {
$this->first = $first;
}
public function generate($offset) {
if(!$this->first) throw new Exception('LinearCongruentialGenerator: first is not set');
if(!$this->gap) throw new Exception('LinearCongruentialGenerator: gap is not set');
return ($this->first - $this->min + $offset * $this->gap) % ($this->max - $this->min + 1) + $this->min;
}
}

View File

@ -0,0 +1,83 @@
<?php
require_once '../../server/vendor/autoload.php';
class MailSender {
private $mailOptions = [];
private $mailerInstance;
private static $instance = NULL;
public static function getInstance() {
if(MailSender::$instance === NULL) {
MailSender::$instance = new MailSender();
}
return MailSender::$instance;
}
private function __construct() {}
public function setConnectionSettings($host, $port, $user, $pass, $noReplyEmail) {
$this->mailOptions['from'] = $noReplyEmail;
$this->mailOptions['fromName'] = 'OpenSupports';
$this->mailOptions['smtp-host'] = $host;
$this->mailOptions['smtp-port'] = $port;
$this->mailOptions['smtp-user'] = $user;
$this->mailOptions['smtp-pass'] = $pass;
}
public function setMailContent($mailContent) {
$this->mailOptions = array_merge($this->mailOptions, $mailContent);
}
public function send() {
$mailerInstance = $this->getMailerInstance();
if( !array_key_exists('to', $this->mailOptions) ||
!array_key_exists('subject', $this->mailOptions) ||
!array_key_exists('body', $this->mailOptions) ) {
throw new Exception('Mail sending data not available');
}
$mailerInstance->ClearAllRecipients();
$mailerInstance->addAddress($this->mailOptions['to']);
$mailerInstance->Subject = $this->mailOptions['subject'];
$mailerInstance->Body = $this->mailOptions['body'];
$mailerInstance->isHTML(true);
if ($this->isConnected()) {
$mailerInstance->send();
}
}
public function isConnected() {
return $this->getMailerInstance()->smtpConnect();
}
private function getMailerInstance() {
if(!($this->mailerInstance instanceof PHPMailer)) {
$this->mailerInstance = new PHPMailer();
$this->mailerInstance->From = $this->mailOptions['from'];
$this->mailerInstance->FromName = $this->mailOptions['fromName'];
$this->mailerInstance->isSMTP();
$this->mailerInstance->SMTPAuth = true;
$this->mailerInstance->Host = $this->mailOptions['smtp-host'];
$this->mailerInstance->Port = $this->mailOptions['smtp-port'];
$this->mailerInstance->Username = $this->mailOptions['smtp-user'];
$this->mailerInstance->Password = $this->mailOptions['smtp-pass'];
$this->mailerInstance->Timeout = 1000;
$this->mailerInstance->SMTPOptions = [
'ssl' => [
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true
]
];
}
return $this->mailerInstance;
}
}

View File

@ -0,0 +1,384 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Support Center</title>
<style type="text/css">
/* Take care of image borders and formatting, client hacks */
img { max-width: 600px; outline: none; text-decoration: none; -ms-interpolation-mode: bicubic;}
a img { border: none; }
table { border-collapse: collapse !important;}
#outlook a { padding:0; }
.ReadMsgBody { width: 100%; }
.ExternalClass { width: 100%; }
.backgroundTable { margin: 0 auto; padding: 0; width: 100% !important; }
table td { border-collapse: collapse; }
.ExternalClass * { line-height: 115%; }
.container-for-gmail-android { min-width: 600px; }
/* General styling */
* {
font-family: Helvetica, Arial, sans-serif;
}
body {
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: none;
width: 100% !important;
margin: 0 !important;
height: 100%;
color: #676767;
}
td {
font-family: Helvetica, Arial, sans-serif;
font-size: 14px;
color: #777777;
text-align: center;
line-height: 21px;
}
a {
color: #676767;
text-decoration: none !important;
}
.pull-left {
text-align: left;
}
.pull-right {
text-align: right;
}
.header-lg,
.header-md,
.header-sm {
font-size: 32px;
font-weight: 700;
line-height: normal;
padding: 35px 0 0;
color: #4d4d4d;
}
.header-md {
font-size: 24px;
}
.header-sm {
padding: 5px 0;
font-size: 18px;
line-height: 1.3;
}
.content-padding {
padding: 20px 0 30px;
}
.mobile-header-padding-right {
width: 290px;
text-align: right;
padding-left: 10px;
}
.mobile-header-padding-left {
width: 290px;
text-align: left;
padding-left: 10px;
}
.free-text {
width: 100% !important;
padding: 10px 60px 0px;
}
.block-rounded {
border-radius: 5px;
border: 1px solid #e5e5e5;
vertical-align: top;
}
.button {
padding: 55px 0 0;
}
.info-block {
padding: 0 20px;
width: 260px;
}
.mini-block-container {
padding: 30px 50px;
width: 500px;
}
.mini-block {
background-color: #ffffff;
width: 498px;
border: 1px solid #cccccc;
border-radius: 5px;
padding: 60px 75px;
}
.block-rounded {
width: 260px;
}
.info-img {
width: 258px;
border-radius: 5px 5px 0 0;
}
.force-width-img {
width: 480px;
height: 1px !important;
}
.force-width-full {
width: 600px;
height: 1px !important;
}
.user-img img {
width: 82px;
border-radius: 5px;
border: 1px solid #cccccc;
}
.user-img {
width: 92px;
text-align: left;
}
.user-msg {
width: 236px;
font-size: 14px;
text-align: left;
font-style: italic;
}
.code-block {
padding: 10px 0;
border: 1px solid #cccccc;
color: #4d4d4d;
font-weight: bold;
font-size: 17px;
text-align: center;
}
.force-width-gmail {
min-width:600px;
height: 0px !important;
line-height: 1px !important;
font-size: 1px !important;
}
.button-width {
width: 228px;
}
</style>
<style type="text/css" media="screen">
@import url(http://fonts.googleapis.com/css?family=Oxygen:400,700);
</style>
<style type="text/css" media="screen">
@media screen {
/* Thanks Outlook 2013! */
* {
font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important;
}
}
</style>
<style type="text/css" media="only screen and (max-width: 480px)">
/* Mobile styles */
@media only screen and (max-width: 480px) {
table[class*="container-for-gmail-android"] {
min-width: 290px !important;
width: 100% !important;
}
table[class="w320"] {
width: 320px !important;
}
img[class="force-width-gmail"] {
display: none !important;
width: 0 !important;
height: 0 !important;
}
a[class="button-width"],
a[class="button-mobile"] {
width: 248px !important;
}
td[class*="mobile-header-padding-left"] {
width: 160px !important;
padding-left: 0 !important;
}
td[class*="mobile-header-padding-right"] {
width: 160px !important;
padding-right: 0 !important;
}
td[class="header-lg"] {
font-size: 24px !important;
padding-bottom: 5px !important;
}
td[class="header-md"] {
font-size: 18px !important;
padding-bottom: 5px !important;
}
td[class="content-padding"] {
padding: 5px 0 30px !important;
}
td[class="button"] {
padding: 15px 0 5px !important;
}
td[class*="free-text"] {
padding: 10px 18px 30px !important;
}
img[class="force-width-img"],
img[class="force-width-full"] {
display: none !important;
}
td[class="info-block"] {
display: block !important;
width: 280px !important;
padding-bottom: 40px !important;
}
td[class="info-img"],
img[class="info-img"] {
width: 278px !important;
}
td[class="mini-block-container"] {
padding: 8px 20px !important;
width: 280px !important;
}
td[class="mini-block"] {
padding: 20px !important;
}
td[class="user-img"] {
display: block !important;
text-align: center !important;
width: 100% !important;
padding-bottom: 10px;
}
td[class="user-msg"] {
display: block !important;
padding-bottom: 20px !important;
}
}
</style>
</head>
<body bgcolor="#f7f7f7">
<table align="center" cellpadding="0" cellspacing="0" class="container-for-gmail-android" width="100%">
<tr>
<td align="left" valign="top" width="100%" style="background-color: #ffffff;">
<center>
<table cellspacing="0" cellpadding="0" width="100%" bgcolor="#ffffff" style="border-bottom: 1px solid #cccccc">
<tr>
<td width="100%" height="80" valign="top" style="text-align: center; vertical-align:middle;">
<center>
<table cellpadding="0" cellspacing="0" width="600" class="w320">
<tr>
<td style="vertical-align: middle;">
<a href="http://www.opensupports.com/" target="_blank"><img height="47" src="http://opensupports.com/logo.png" alt="logo"></a>
</td>
</tr>
</table>
</center>
<!--[if gte mso 9]>
</v:textbox>
</v:rect>
<![endif]-->
</td>
</tr>
</table>
</center>
</td>
</tr>
<tr>
<td align="center" valign="top" width="100%" style="background-color: #f7f7f7;" class="content-padding">
<center>
<table cellspacing="0" cellpadding="0" width="600" class="w320">
<tr>
<td class="header-lg">
Ticket numbers updated
</td>
</tr>
<tr>
<td class="free-text">
Hello, {{email}}. Due maintenance reasons, the following tickets have changed its number.
</td>
</tr>
<tr>
<td class="mini-block-container">
<table cellspacing="0" cellpadding="0" width="100%" style="border-collapse:separate !important;">
<tr>
<td class="mini-block">
<table cellpadding="0" cellspacing="0" width="100%">
<tr>
<td style="padding-bottom: 30px;">
{{tickets}}
</td>
</tr>
<tr>
<td class="button">
<div><!--[if mso]>
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="{{url}}/check-ticket/" style="height:45px;v-text-anchor:middle;width:155px;" arcsize="15%" strokecolor="#ffffff" fillcolor="#ff6f6f">
<w:anchorlock/>
<center style="color:#ffffff;font-family:Helvetica, Arial, sans-serif;font-size:14px;font-weight:regular;">Check Ticket</center>
</v:roundrect>
<![endif]--><a class="button-mobile" target="_blank" href="{{url}}/check-ticket/"
style="background-color:#ff6f6f;border-radius:5px;color:#ffffff;display:inline-block;font-family:'Cabin', Helvetica, Arial, sans-serif;font-size:14px;font-weight:regular;line-height:45px;text-align:center;text-decoration:none;width:155px;-webkit-text-size-adjust:none;mso-hide:all;">Check Tickets</a></div>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</center>
</td>
</tr>
<tr>
<td align="center" valign="top" width="100%" style="background-color: #ffffff; border-top: 1px solid #e5e5e5; border-bottom: 1px solid #e5e5e5; height: 100px;">
<center>
<table cellspacing="0" cellpadding="0" width="600" class="w320">
<tr>
<td style="padding: 25px 0 25px">
<strong>OpenSupports</strong><br />
Open source ticket system<br />
www.opensupports.com<br /><br />
</td>
</tr>
</table>
</center>
</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,13 @@
<?php
$mysql_host = getenv('MYSQL_HOST');
$mysql_user = getenv('MYSQL_USER');
$mysql_password = getenv('MYSQL_PASSWORD');
$mysql_db = getenv('MYSQL_DB');
$mysql = new mysqli($mysql_host, $mysql_user, $mysql_password, $mysql_db);
if (mysqli_connect_errno()) {
printf("Connect failed: %s\n", mysqli_connect_error());
exit();
}