From 1ec8d12a7b125d41ecb723c5d83f2afef95408e9 Mon Sep 17 00:00:00 2001 From: Camil Staps Date: Sun, 31 Jul 2016 09:40:25 +0200 Subject: 0.5.2 emails --- .gitmodules | 3 + README.md | 1 + classes/BusinessAdmin.php | 22 +++++++ classes/Constants.php | 2 +- classes/Contact.php | 11 ++++ classes/Mail.php | 32 ++++++++++ classes/Mailer.php | 100 +++++++++++++++++++++++++++++++ classes/Offer.php | 26 ++++++++ conf.php | 2 + conf.private.example.php | 13 ++++ include/offers-view.php | 148 ++++++++++++++++++++++++++-------------------- include/offers.php | 20 +++++++ install/index.php | 21 ++++++- install/upgrade.php | 21 +++++++ modules/PHPMailer | 1 + 15 files changed, 355 insertions(+), 68 deletions(-) create mode 100644 classes/Mail.php create mode 100644 classes/Mailer.php create mode 160000 modules/PHPMailer diff --git a/.gitmodules b/.gitmodules index 2698b7a..5c4d8bd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "modules/braintree"] path = modules/braintree url = https://github.com/braintree/braintree_php.git +[submodule "modules/PHPMailer"] + path = modules/PHPMailer + url = https://github.com/PHPMailer/PHPMailer diff --git a/README.md b/README.md index 5538798..4b1cd95 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,7 @@ are listed by name and removal time. This way, you never really lose your file. ### 0.5 (Jul 28, 2016) +0.5.2 Emails. 0.5.1 Files are hidden from the world. 0.5 Braintree integration. diff --git a/classes/BusinessAdmin.php b/classes/BusinessAdmin.php index 7c3c76c..83fa4b3 100644 --- a/classes/BusinessAdmin.php +++ b/classes/BusinessAdmin.php @@ -406,6 +406,28 @@ class BusinessAdmin { } } + /** + * Create a new mail + * + * @param PDO $pdo The database connection + * @param int $contactId The contactId for the new mail + * @param int $offerId The offerId for the new mail + * @param string $subject + * + * @throws PDOException If something went wrong with the database + * + * @return Mail|bool A new instance of the Mail class, or false on failure + */ + public static function createMail($pdo, $contactId, $offerId, $subject) { + $stmt = $pdo->prepare("INSERT INTO `".Constants::db_prefix."mail` (`contactId`, `offerId`, `subject`) VALUES (?,?,?)"); + $stmt->execute([$contactId, $offerId, $subject]); + if ($stmt->rowCount() == 1) { + return new Mail($pdo, $pdo->lastInsertId()); + } else { + return false; + } + } + /** * Format a date nicely * diff --git a/classes/Constants.php b/classes/Constants.php index fbac6cf..7528630 100644 --- a/classes/Constants.php +++ b/classes/Constants.php @@ -78,5 +78,5 @@ class Constants { const password_cost = 10; /** @const version Version of BusinessAdmin. Don't change this yourself! */ - const version = '0.5.1'; + const version = '0.5.2'; } diff --git a/classes/Contact.php b/classes/Contact.php index 6ab4803..4a82ade 100644 --- a/classes/Contact.php +++ b/classes/Contact.php @@ -73,4 +73,15 @@ class Contact extends Model { throw new Exception($error[2]); } } + + /** + * Make a mailer to send to this contact + * + * @return Mailer The mailer + */ + public function mailer() { + $mailer = new Mailer($this->pdo); + $mailer->addAddress($this->email, $this->name); + return $mailer; + } } diff --git a/classes/Mail.php b/classes/Mail.php new file mode 100644 index 0000000..dcc5cf5 --- /dev/null +++ b/classes/Mail.php @@ -0,0 +1,32 @@ +. + */ + +/** + * An extension to Model for the mail table + */ +class Mail extends Model { + /** {@inheritDoc} */ + public + $table = 'mail', + $fillable_columns = ['contactId', 'offerId', 'subject']; +} diff --git a/classes/Mailer.php b/classes/Mailer.php new file mode 100644 index 0000000..782afc3 --- /dev/null +++ b/classes/Mailer.php @@ -0,0 +1,100 @@ +. + */ + +/** + * An extension to PHPMailer to set some defaults + */ +class Mailer extends PHPMailer { + /** + * {@inheritDoc} + * + * @param PDO $_pdo A PDO instance for database connection + * @param mixed $exceptions For PHPMailer::__construct() + */ + public function __construct($_pdo, $exceptions = null) { + parent::__construct($exceptions); + + $this->pdo = $_pdo; + + $this->isSMTP(); + $this->Host = SMTP_HOST; + $this->SMTPAuth = SMTP_AUTH; + $this->Username = SMTP_USERNAME; + $this->Password = SMTP_PASSWORD; + $this->SMTPSecure = SMTP_SECURE; + $this->Port = SMTP_PORT; + + if (defined('SMTP_OPTIONS')) + $this->SMTPOptions = json_decode(SMTP_OPTIONS, true); + + $from = explode(';', MAILER_FROM); + $this->setFrom($from[0], $from[1]); + + if (defined('MAILER_REPLY_TO')) { + $replyto = explode(';', MAILER_REPLY_TO); + $this->addReplyTo($replyto[0], $replyto[1]); + } + + if (defined('MAILER_CC')) + foreach (explode(';', MAILER_CC) as $cc) + $this->addCC($cc); + if (defined('MAILER_BCC')) + foreach (explode(';', MAILER_BCC) as $bcc) + $this->addBCC($bcc); + } + + /** + * Set the contact this mail should be sent to + * + * @param Contact $contact The contact + */ + public function setContact($contact) { + $this->addAddress($contact->email, $contact->name); + $this->contactId = $contact->id; + } + + /** + * Set the offer this email is about + * + * @param Offer $offer The offer + */ + public function setOffer($offer) { + $this->setContact($offer->getContact()); + $this->offerId = $offer->id; + } + + /** + * {@inheritDoc} + * + * Then store it in the database. + */ + public function send() { + if (!parent::send()) { + return false; + } + + BusinessAdmin::createMail($this->pdo, $this->contactId, $this->offerId, $this->Subject); + + return true; + } +} diff --git a/classes/Offer.php b/classes/Offer.php index 16e0d20..7b0d2fc 100644 --- a/classes/Offer.php +++ b/classes/Offer.php @@ -293,6 +293,32 @@ class Offer extends Model{ } } + /** + * Make a mailer to send about this offer + * + * @return Mailer The mailer + */ + public function mailer() { + $mailer = new Mailer($this->pdo); + $mailer->setOffer($this); + return $mailer; + } + + /** + * Send the invoice to the contact + * + * @return bool The result of Mailer::send + */ + public function send() { + $mailer = $this->mailer(); + + $mailer->addAttachment($this->getInvoiceFile()->getFilenamePath()); + $mailer->Subject = 'Your invoice'; + $mailer->Body = 'Here is your invoice.'; + + return $mailer->send(); + } + /** * Make a new assignment linked to this order * diff --git a/conf.php b/conf.php index d195a07..9085b1e 100644 --- a/conf.php +++ b/conf.php @@ -56,6 +56,8 @@ if (BRAINTREE_ENABLED) { Braintree_Configuration::merchantId(BRAINTREE_MERCHANT); Braintree_Configuration::publicKey(BRAINTREE_KEY_PUBLIC); Braintree_Configuration::privateKey(BRAINTREE_KEY_PRIVATE); + + require_once('modules/PHPMailer/PHPMailerAutoload.php'); } require_once('classes/Calculatable.php'); // Some definitions that are required diff --git a/conf.private.example.php b/conf.private.example.php index a9275b1..b389c4c 100644 --- a/conf.private.example.php +++ b/conf.private.example.php @@ -12,3 +12,16 @@ define('BRAINTREE_ENVIRONMENT', 'sandbox'); define('BRAINTREE_MERCHANT', ...); define('BRAINTREE_KEY_PUBLIC', ...); define('BRAINTREE_KEY_PRIVATE', ...); + +// Mailer settings +define('SMTP_HOST', ...); +define('SMTP_AUTH', true); +define('SMTP_USERNAME', ...); +define('SMTP_PASSWORD', ...); +define('SMTP_SECURE', 'tls'); +define('SMTP_PORT', 587); +define('SMTP_OPTIONS', ...); // JSON array, can be used to turn off SSL verification (http://stackoverflow.com/a/28759959/1544337) + +define('MAILER_FROM', 'MY_EMAIL;MY_NAME'); +define('MAILER_REPLY_TO', ...); +define('MAILER_BCC', 'MY_EMAIL'); diff --git a/include/offers-view.php b/include/offers-view.php index 802f42f..d96e9ed 100644 --- a/include/offers-view.php +++ b/include/offers-view.php @@ -21,70 +21,6 @@ require_once('./login.php'); $_offer = new Offer($_pdo, $_id); ?> -
-
-
- Timeline -
- -
-
    - $_offer->id, - 'contact' => $_offer->getContact()->name, - 'assignments' => '', - 'assignments_header' => '' - ); - foreach ($_offer->getAssignments() as $assignment) { - $temp['assignments'] .= "{$assignment->title}
    (".Constants::invoice_valuta."{$assignment->calculate(Calculatable::SUBTOTAL)} excl. VAT, ".Constants::invoice_valuta."{$assignment->calculate(Calculatable::TOTAL)} incl. VAT)

    {$assignment->getHTMLDescription()}

    "; - $temp['assignments_header'] .= "{$assignment->title}
    (".Constants::invoice_valuta."{$assignment->calculate(Calculatable::SUBTOTAL)} excl. VAT, ".Constants::invoice_valuta."{$assignment->calculate(Calculatable::TOTAL)} incl. VAT)
    "; - } - $list[] = array_merge($temp, array('type' => 'start', 'time' => $_offer->start_date, 'description' => 'Offer started')); - $sort_list[] = $_offer->start_date . $_offer->id . 0; - $list[] = array_merge($temp, array('type' => 'end', 'time' => $_offer->end_date, 'description' => 'Offer ended')); - $sort_list[] = $_offer->end_date . $_offer->id . 1; - if ($_offer->invoice_date > 0) { - $list[] = array_merge($temp, array('type' => 'invoice', 'time' => $_offer->invoice_date, 'description' => 'Invoice sent')); - $sort_list[] = $_offer->invoice_date . $_offer->id . 2; - if ($_offer->getPaymentReceived() > 0) { - $list[] = array_merge($temp, array('type' => 'payment_received', 'time' => $_offer->getPaymentReceived(), 'description' => 'Payment received')); - $sort_list[] = $_offer->getPaymentReceived() . $_offer->id . 3; - } - } - - array_multisort($sort_list, SORT_DESC, $list); - $i = 0; - foreach ($list as $item) { - if ($item['time'] > time()) { - continue; - } - echo ""; - switch ($item['type']) { - case 'start': echo "
    "; break; - case 'end': echo "
    "; break; - case 'invoice': echo "
    "; break; - case 'payment_received': echo "
    "; break; - } - echo "
    "; - echo "

    #{$item['id']} to {$item['contact']}: {$item['description']}

    ".BusinessAdmin::formatDate($item['time'],false,true,true)."

    "; - switch ($item['type']) { - case 'start': echo "
    {$item['assignments']}
    "; break; - default: echo "
    {$item['assignments_header']}
    "; - } - echo "
    "; - echo ""; - } - ?> -
-
- -
- -
Assignments
@@ -171,3 +107,87 @@ $_offer = new Offer($_pdo, $_id);
+
+
+
+
Tools
+
+ accepted ? + ['Accepted', 'btn-success'] : + ['Not accepted', 'btn-default']; + $eligible = $_offer->getPaymentEligibility() ? + ['Eligible for online payment', 'btn-success'] : + ['Not eligible for online payment', 'btn-default']; + ?> + + + Send invoice to contact + Delete +
+
+
+
+
+
+ Timeline +
+ +
+
    + $_offer->id, + 'contact' => $_offer->getContact()->name, + 'assignments' => '', + 'assignments_header' => '' + ); + foreach ($_offer->getAssignments() as $assignment) { + $temp['assignments'] .= "{$assignment->title}
    (".Constants::invoice_valuta."{$assignment->calculate(Calculatable::SUBTOTAL)} excl. VAT, ".Constants::invoice_valuta."{$assignment->calculate(Calculatable::TOTAL)} incl. VAT)

    {$assignment->getHTMLDescription()}

    "; + $temp['assignments_header'] .= "{$assignment->title}
    (".Constants::invoice_valuta."{$assignment->calculate(Calculatable::SUBTOTAL)} excl. VAT, ".Constants::invoice_valuta."{$assignment->calculate(Calculatable::TOTAL)} incl. VAT)
    "; + } + $list[] = array_merge($temp, array('type' => 'start', 'time' => $_offer->start_date, 'description' => 'Offer started')); + $sort_list[] = $_offer->start_date . $_offer->id . 0; + $list[] = array_merge($temp, array('type' => 'end', 'time' => $_offer->end_date, 'description' => 'Offer ended')); + $sort_list[] = $_offer->end_date . $_offer->id . 1; + if ($_offer->invoice_date > 0) { + $list[] = array_merge($temp, array('type' => 'invoice', 'time' => $_offer->invoice_date, 'description' => 'Invoice sent')); + $sort_list[] = $_offer->invoice_date . $_offer->id . 2; + if ($_offer->getPaymentReceived() > 0) { + $list[] = array_merge($temp, array('type' => 'payment_received', 'time' => $_offer->getPaymentReceived(), 'description' => 'Payment received')); + $sort_list[] = $_offer->getPaymentReceived() . $_offer->id . 3; + } + } + + array_multisort($sort_list, SORT_DESC, $list); + $i = 0; + foreach ($list as $item) { + if ($item['time'] > time()) { + continue; + } + echo ""; + switch ($item['type']) { + case 'start': echo "
    "; break; + case 'end': echo "
    "; break; + case 'invoice': echo "
    "; break; + case 'payment_received': echo "
    "; break; + } + echo "
    "; + echo "

    #{$item['id']} to {$item['contact']}: {$item['description']}

    ".BusinessAdmin::formatDate($item['time'],false,true,true)."

    "; + switch ($item['type']) { + case 'start': echo "
    {$item['assignments']}
    "; break; + default: echo "
    {$item['assignments_header']}
    "; + } + echo "
    "; + echo ""; + } + ?> +
+
+ +
+ +
diff --git a/include/offers.php b/include/offers.php index 8be7530..dbe5df0 100644 --- a/include/offers.php +++ b/include/offers.php @@ -37,6 +37,7 @@ require('./header.php'); // ?toggle_payment_eligibility= Toggle the payment eligibility of the offer with id // ?generate_invoice= Generate an invoice for the offer with id // ?trash_invoice= Trash the invoice file + // ?send_invoice= Send invoice to contact // ?delete= Delete the offer with id //------------------------------------------------------------------------------ @@ -137,6 +138,25 @@ require('./header.php'); echo ""; } + // Send invoice + if (isset($_GET['send_invoice'])) { + echo "
"; + $id = (int) $_GET['send_invoice']; + try { + $offer = new Offer($_pdo, $id); + if ($offer->send()) { + echo "
The invoice for offer {$id} has been sent to {$offer->getContact()->email}.
"; + } else { + echo "
The invoice could not be sent due to an unknown error.
"; + } + } catch (PDOException $e) { + echo "
The invoice could not be sent due to a PDO error.
"; + } catch (Exception $e) { + echo "
The offer with id {$id} could not be found.
"; + } + echo "
"; + } + // Delete offer if (isset($_GET['delete'])) { echo "
"; diff --git a/install/index.php b/install/index.php index 519e3c5..19cd5a4 100644 --- a/install/index.php +++ b/install/index.php @@ -75,6 +75,17 @@ if (isset($_GET['create_tables'])) { UNIQUE KEY `filename` (`filename`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1"); + $_pdo->query("CREATE TABLE IF NOT EXISTS `".Constants::db_prefix."mail` ( + `id` smallint(5) unsigned NOT NULL AUTO_INCREMENT, + `contactId` smallint(5) unsigned NOT NULL, + `offerId` smallint(5) unsigned DEFAULT NULL, + `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `subject` varchar(100) NOT NULL, + PRIMARY KEY (`id`), + KEY `contactId` (`contactId`), + KEY `offerId` (`offerId`) + ) ENGINE=InnoDB DEFAULT CHARSET=latin1;"); + $_pdo->query("CREATE TABLE IF NOT EXISTS `".Constants::db_prefix."offer` ( `id` smallint(5) unsigned NOT NULL AUTO_INCREMENT, `contactId` smallint(5) unsigned NOT NULL, @@ -110,13 +121,17 @@ if (isset($_GET['create_tables'])) { $_pdo->query("ALTER TABLE `".Constants::db_prefix."assignment` ADD CONSTRAINT `assignment_ibfk_1` FOREIGN KEY (`offerId`) REFERENCES `".Constants::db_prefix."offer` (`id`)"); - $_pdo->query("ALTER LE `".Constants::db_prefix."contact` + $_pdo->query("ALTER TABLE `".Constants::db_prefix."contact` ADD CONSTRAINT `contact_ibfk_1` FOREIGN KEY (`clientId`) REFERENCES `".Constants::db_prefix."client` (`id`)"); - $_pdo->query("ALTER LE `".Constants::db_prefix."discount` + $_pdo->query("ALTER TABLE `".Constants::db_prefix."discount` ADD CONSTRAINT `discount_ibfk_1` FOREIGN KEY (`offerId`) REFERENCES `".Constants::db_prefix."offer` (`id`);"); - $_pdo->query("ALTER LE `".Constants::db_prefix."offer` + $_pdo->query("ALTER TABLE `".Constants::db_prefix."mail` + ADD CONSTRAINT `mail_ibfk_1` FOREIGN KEY (`contactId`) REFERENCES `".Constants::db_prefix."contact` (`id`) ON UPDATE CASCADE, + ADD CONSTRAINT `mail_ibfk_2` FOREIGN KEY (`offerId`) REFERENCES `".Constants::db_prefix."offer` (`id`) ON UPDATE CASCADE;"); + + $_pdo->query("ALTER TABLE `".Constants::db_prefix."offer` ADD CONSTRAINT `offer_ibfk_1` FOREIGN KEY (`invoice_fileId`) REFERENCES `".Constants::db_prefix."file` (`id`), ADD CONSTRAINT `offer_ibfk_2` FOREIGN KEY (`contactId`) REFERENCES `".Constants::db_prefix."contact` (`id`)"); diff --git a/install/upgrade.php b/install/upgrade.php index db0c913..c759df0 100644 --- a/install/upgrade.php +++ b/install/upgrade.php @@ -141,6 +141,27 @@ if (isset($_GET['upgrade'])) { } } + if (lower_version($_GET['upgrade'], '0.5.2')) { + try { + $_pdo->query("CREATE TABLE IF NOT EXISTS `".Constants::db_prefix."mail` ( + `id` smallint(5) unsigned NOT NULL AUTO_INCREMENT, + `contactId` smallint(5) unsigned NOT NULL, + `offerId` smallint(5) unsigned DEFAULT NULL, + `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `subject` varchar(100) NOT NULL, + PRIMARY KEY (`id`), + KEY `contactId` (`contactId`), + KEY `offerId` (`offerId`) + ) ENGINE=InnoDB DEFAULT CHARSET=latin1;"); + + $_pdo->query("ALTER TABLE `".Constants::db_prefix."mail` + ADD CONSTRAINT `mail_ibfk_1` FOREIGN KEY (`contactId`) REFERENCES `".Constants::db_prefix."contact` (`id`) ON UPDATE CASCADE, + ADD CONSTRAINT `mail_ibfk_2` FOREIGN KEY (`offerId`) REFERENCES `".Constants::db_prefix."offer` (`id`) ON UPDATE CASCADE;"); + } catch (PDOException $e) { + echo "Altering the database structure failed with a PDOException ({$e->getCode()}): {$e->getMessage()}
" . $e->getTraceAsString(); + } + } + echo "
All done."; } ?> diff --git a/modules/PHPMailer b/modules/PHPMailer new file mode 160000 index 0000000..4f7967a --- /dev/null +++ b/modules/PHPMailer @@ -0,0 +1 @@ +Subproject commit 4f7967ac91a25e8838fdb59df2f45030a1d39bc5 -- cgit v1.2.3