2023-06-06 16:04:39 +01:00
< ? php
2023-10-18 23:58:21 +01:00
2023-06-08 15:10:27 +01:00
namespace api\blog ;
2023-10-18 23:58:21 +01:00
2023-11-14 01:02:27 +00:00
use api\utils\feedGenerator\FeedWriter ;
2023-06-26 03:54:25 +01:00
use api\utils\imgUtils ;
use DOMDocument ;
2023-06-06 16:04:39 +01:00
use PDO ;
2023-06-26 03:54:25 +01:00
use Psr\Http\Message\UploadedFileInterface ;
2023-11-18 13:06:27 +00:00
use DonatelloZa\RakePlus\RakePlus ;
2023-12-06 00:38:33 +00:00
use PHPMailer\PHPMailer\PHPMailer ;
use PHPMailer\PHPMailer\Exception ;
use function api\utils\dbConn ;
use function api\utils\getEmailPassword ;
2023-11-14 01:02:27 +00:00
use const api\utils\feedGenerator\ATOM ;
use const api\utils\feedGenerator\RSS2 ;
2023-06-06 16:04:39 +01:00
2023-11-18 13:06:27 +00:00
2023-12-06 00:38:33 +00:00
2023-06-08 15:10:27 +01:00
require_once __DIR__ . " /../utils/config.php " ;
2023-06-26 03:54:25 +01:00
require_once __DIR__ . " /../utils/imgUtils.php " ;
2023-11-14 01:02:27 +00:00
require_once __DIR__ . " /../utils/feedGenerator/FeedWriter.php " ;
2023-06-06 16:04:39 +01:00
/**
* Blog Data Class
* Define all functions which either get , update , create or delete posts
*/
class blogData
{
/**
* Get all blog posts
2023-11-14 01:02:27 +00:00
* @ return array < array > - Array of all blog posts or error message
2023-06-06 16:04:39 +01:00
*/
2023-06-26 03:54:25 +01:00
public function getBlogPosts () : array
2023-06-06 16:04:39 +01:00
{
$conn = dbConn ();
2023-11-14 01:02:27 +00:00
$stmt = $conn -> prepare ( " SELECT ID, title, DATE_FORMAT(dateCreated, '%Y-%m-%dT%TZ') AS dateCreated,
DATE_FORMAT ( dateModified , '%Y-%m-%dT%TZ' ) AS dateModified , featured , abstract ,
2023-11-18 13:06:27 +00:00
headerImg , body , bodyText , categories , keywords , folderID FROM blog ORDER BY featured DESC ,
2023-11-14 01:02:27 +00:00
dateCreated DESC ; " );
2023-06-06 16:04:39 +01:00
$stmt -> execute ();
// set the resulting array to associative
$result = $stmt -> fetchAll ( PDO :: FETCH_ASSOC );
if ( $result )
{
return $result ;
}
return array ( " errorMessage " => " Error, blog data not found " );
}
/**
* Get a blog post with the given ID
2023-10-18 00:28:34 +01:00
* @ param string $title - Title of the blog post
2023-11-14 01:02:27 +00:00
* @ return array - Array of blog post or error message
2023-06-06 16:04:39 +01:00
*/
2023-10-18 00:28:34 +01:00
public function getBlogPost ( string $title ) : array
2023-06-06 16:04:39 +01:00
{
$conn = dbConn ();
2023-11-14 01:02:27 +00:00
$stmt = $conn -> prepare ( " SELECT ID, title, DATE_FORMAT(dateCreated, '%Y-%m-%dT%TZ') AS dateCreated,
DATE_FORMAT ( dateModified , '%Y-%m-%dT%TZ' ) AS dateModified , featured , abstract ,
2023-11-18 13:06:27 +00:00
headerImg , body , bodyText , categories , keywords , folderID FROM blog WHERE
2023-11-14 01:02:27 +00:00
title = : title ; " );
2023-10-18 00:28:34 +01:00
$stmt -> bindParam ( " :title " , $title );
2023-06-06 16:04:39 +01:00
$stmt -> execute ();
// set the resulting array to associative
$result = $stmt -> fetch ( PDO :: FETCH_ASSOC );
if ( $result )
{
return $result ;
}
return array ( " errorMessage " => " Error, blog post could not found " );
}
/**
* Get the latest blog post
* @ return array - Array of the latest blog post or error message
*/
2023-06-26 03:54:25 +01:00
public function getLatestBlogPost () : array
2023-06-06 16:04:39 +01:00
{
$conn = dbConn ();
2023-11-14 01:02:27 +00:00
$stmt = $conn -> prepare ( " SELECT ID, title, DATE_FORMAT(dateCreated, '%Y-%m-%dT%TZ') AS dateCreated,
DATE_FORMAT ( dateModified , '%Y-%m-%dT%TZ' ) AS dateModified , featured , abstract ,
2023-11-18 13:06:27 +00:00
headerImg , body , bodyText , categories , keywords , folderID FROM blog ORDER BY
2023-11-14 01:02:27 +00:00
dateCreated DESC LIMIT 1 ; " );
2023-06-06 16:04:39 +01:00
$stmt -> execute ();
// set the resulting array to associative
$result = $stmt -> fetch ( PDO :: FETCH_ASSOC );
if ( $result )
{
return $result ;
}
return array ( " errorMessage " => " Error, blog post could not found " );
}
/**
* Get featured blog post
* @ return array - Array of the featured blog post or error message
*/
2023-06-26 03:54:25 +01:00
public function getFeaturedBlogPost () : array
2023-06-06 16:04:39 +01:00
{
$conn = dbConn ();
2023-11-14 01:02:27 +00:00
$stmt = $conn -> prepare ( " SELECT ID, title, DATE_FORMAT(dateCreated, '%Y-%m-%dT%TZ') AS dateCreated,
DATE_FORMAT ( dateModified , '%Y-%m-%dT%TZ' ) AS dateModified , featured , abstract ,
2023-11-18 13:06:27 +00:00
headerImg , body , bodyText , categories , keywords , folderID FROM blog WHERE featured = 1 ; " );
2023-06-06 16:04:39 +01:00
$stmt -> execute ();
$result = $stmt -> fetch ( PDO :: FETCH_ASSOC );
if ( $result )
{
return $result ;
}
return array ( " errorMessage " => " Error, blog post could not found " );
}
2023-06-26 03:54:25 +01:00
2023-10-18 00:28:34 +01:00
/**
2023-10-31 19:36:51 +00:00
* Get all unique categories
* @ return string [] - Array of all categories or error message
2023-10-18 00:28:34 +01:00
*/
public function getCategories () : array
{
$conn = dbConn ();
2023-10-18 23:58:21 +01:00
$stmt = $conn -> prepare ( " SELECT DISTINCT categories FROM blog; " );
2023-10-18 00:28:34 +01:00
$stmt -> execute ();
// set the resulting array to associative
$result = $stmt -> fetchAll ( PDO :: FETCH_ASSOC );
if ( $result )
{
return $result ;
}
return array ( " errorMessage " => " Error, blog post could not found " );
}
2023-07-12 03:29:56 +01:00
/**
* Delete a blog post with the given ID
* @ param int $ID - ID of the blog post to delete
* @ return string - Success or error message
*/
public function deletePost ( int $ID ) : string
{
$conn = dbConn ();
$stmtCheckPost = $conn -> prepare ( " SELECT * FROM blog WHERE ID = :ID " );
$stmtCheckPost -> bindParam ( " :ID " , $ID );
$stmtCheckPost -> execute ();
$result = $stmtCheckPost -> fetch ( PDO :: FETCH_ASSOC );
if ( ! $result )
{
return " post not found " ;
}
if ( $result [ " featured " ] === 1 )
{
return " cannot delete " ;
}
$stmt = $conn -> prepare ( " DELETE FROM blog WHERE ID = :ID " );
$stmt -> bindParam ( " :ID " , $ID );
if ( $stmt -> execute ())
{
$imagUtils = new imgUtils ();
$imagUtils -> deleteDirectory ( " ../blog/imgs/ " . $result [ " title " ] . " _ " . $result [ " folderID " ] . " / " );
return " success " ;
}
return " error " ;
}
/**
* Update the blog post with the given ID
* @ param int $ID - ID of the blog post to update
* @ param string $title - Title of the blog post
* @ param bool $featured - Whether the blog post is featured or not
2023-10-18 00:28:34 +01:00
* @ param string $abstract - Abstract of the blog post i . e . a short description
2023-07-12 03:29:56 +01:00
* @ param string $body - Body of the blog post
2023-10-31 19:36:51 +00:00
* @ param string $bodyText - Body of the blog post as plain text
2023-07-12 03:29:56 +01:00
* @ param string $dateModified - Date the blog post was modified
* @ param string $categories - Categories of the blog post
* @ return bool | string - Success or error message
*/
2023-10-31 19:36:51 +00:00
public function updatePost ( int $ID , string $title , bool $featured , string $abstract , string $body , string $bodyText , string $dateModified , string $categories ) : bool | string
2023-07-12 03:29:56 +01:00
{
$conn = dbConn ();
$stmtCheckPost = $conn -> prepare ( " SELECT * FROM blog WHERE ID = :ID " );
$stmtCheckPost -> bindParam ( " :ID " , $ID );
$stmtCheckPost -> execute ();
$result = $stmtCheckPost -> fetch ( PDO :: FETCH_ASSOC );
if ( ! $result )
{
return " post not found " ;
}
if ( ! $featured && $result [ " featured " ] === 1 )
{
return " unset feature " ;
}
if ( $featured )
{
$stmtUnsetFeatured = $conn -> prepare ( " UPDATE blog SET featured = 0 WHERE featured = 1; " );
$stmtUnsetFeatured -> execute ();
}
$to = " ../blog/imgs/ " . $title . " _ " . $result [ " folderID " ] . " / " ;
if ( $result [ " title " ] !== $title )
{
$from = " ../blog/imgs/ " . $result [ " title " ] . " _ " . $result [ " folderID " ] . " / " ;
mkdir ( $to , 0777 , true );
rename ( $result [ " headerImg " ], $to . basename ( $result [ " headerImg " ]));
$body = $this -> changeHTMLSrc ( $body , $to , $from );
rmdir ( $from );
}
$from = " ../blog/imgs/tmp/ " ;
$newBody = $this -> changeHTMLSrc ( $body , $to , $from );
2023-11-18 13:06:27 +00:00
$keywords = implode ( " , " , RakePlus :: create ( $bodyText ) -> keywords ());
$stmt = $conn -> prepare ( " UPDATE blog SET title = :title, featured = :featured, abstract = :abstract, body = :body, bodyText = :bodyText, dateModified = :dateModified, categories = :categories, keywords = :keywords WHERE ID = :ID; " );
2023-07-12 03:29:56 +01:00
$stmt -> bindParam ( " :ID " , $ID );
$stmt -> bindParam ( " :title " , $title );
$stmt -> bindParam ( " :featured " , $featured );
2023-10-18 00:28:34 +01:00
$stmt -> bindParam ( " :abstract " , $abstract );
2023-07-12 03:29:56 +01:00
$stmt -> bindParam ( " :body " , $newBody );
2023-10-31 19:36:51 +00:00
$stmt -> bindParam ( " :bodyText " , $bodyText );
2023-07-12 03:29:56 +01:00
$stmt -> bindParam ( " :dateModified " , $dateModified );
$stmt -> bindParam ( " :categories " , $categories );
2023-11-18 13:06:27 +00:00
$stmt -> bindParam ( " :keywords " , $keywords );
2023-07-12 03:29:56 +01:00
return $stmt -> execute ();
}
/**
* Creates a new post di rectory , uploads the header image and moves the images from the
* temp folder to the new folder , then updates the post html to point to the new images , finally
* it creates the post in the database
* @ param string $title - Title of the blog post
2023-10-18 00:28:34 +01:00
* @ param string $abstract - Abstract of the blog post i . e . a short description
2023-07-12 03:29:56 +01:00
* @ param string $body - Body of the blog post
2023-10-31 19:36:51 +00:00
* @ param string $bodyText - Body of the blog post as plain text
2023-07-12 03:29:56 +01:00
* @ param string $dateCreated - Date the blog post was created
* @ param bool $featured - Whether the blog post is featured or not
* @ param string $categories - Categories of the blog post
2023-12-06 00:38:33 +00:00
* @ param UploadedFileInterface | null $headerImg - Header image of the blog post
2023-07-12 03:29:56 +01:00
* @ return int | string - ID of the blog post or error message
*/
2023-12-06 00:38:33 +00:00
public function createPost ( string $title , string $abstract , string $body , string $bodyText , string $dateCreated , bool $featured , string $categories , UploadedFileInterface | null $headerImg ) : int | string
2023-07-12 03:29:56 +01:00
{
$conn = dbConn ();
$folderID = uniqid ();
$targetFile = array ( " imgLocation " => " ../blog/imgs/placeholder.png " );
$targetDir = " ../blog/imgs/ " . $title . " _ " . $folderID . " / " ;
mkdir ( $targetDir , 0777 , true );
if ( $headerImg !== null )
{
$imagUtils = new imgUtils ();
$targetFile = $imagUtils -> uploadFile ( $targetDir , $headerImg );
}
if ( ! is_array ( $targetFile ))
{
return $targetFile ;
}
$newBody = $this -> changeHTMLSrc ( $body , $targetDir , " ../blog/imgs/tmp/ " );
if ( $featured )
{
$stmtMainProject = $conn -> prepare ( " UPDATE blog SET featured = 0 WHERE featured = 1; " );
$stmtMainProject -> execute ();
}
2023-11-18 13:06:27 +00:00
$keywords = implode ( " , " , RakePlus :: create ( $bodyText ) -> keywords ());
2023-12-06 00:38:33 +00:00
$latest = $this -> getLatestBlogPost ();
$prevTitle = $latest [ " title " ];
$prevAbstract = $latest [ " abstract " ];
2023-12-13 03:10:23 +00:00
$prevHeaderImage = substr ( $latest [ " headerImg " ], 10 );
$prevHeaderImage = str_ireplace ( " %2F " , " / " , $prevHeaderImage );
2023-12-06 00:38:33 +00:00
2023-12-13 03:10:23 +00:00
$headerImage = rawurlencode ( " ../ " . $targetFile [ " imgLocation " ]);
2023-12-06 00:38:33 +00:00
2023-11-18 13:06:27 +00:00
$stmt = $conn -> prepare ( " INSERT INTO blog (title, dateCreated, dateModified, featured, headerImg, abstract, body, bodyText, categories, keywords, folderID)
VALUES ( : title , : dateCreated , : dateModified , : featured , : headerImg , : abstract , : body , : bodyText , : categories , : keywords , : folderID ); " );
2023-07-12 03:29:56 +01:00
$stmt -> bindParam ( " :title " , $title );
$stmt -> bindParam ( " :dateCreated " , $dateCreated );
$stmt -> bindParam ( " :dateModified " , $dateCreated );
2023-12-13 03:10:23 +00:00
// $isFeatured = $featured ? 1 : 0;
$stmt -> bindParam ( " :featured " , $featured );
2023-12-06 00:38:33 +00:00
$stmt -> bindParam ( " :headerImg " , $headerImage );
2023-10-18 00:28:34 +01:00
$stmt -> bindParam ( " :abstract " , $abstract );
2023-07-12 03:29:56 +01:00
$stmt -> bindParam ( " :body " , $newBody );
2023-10-31 19:36:51 +00:00
$stmt -> bindParam ( " :bodyText " , $bodyText );
2023-07-12 03:29:56 +01:00
$stmt -> bindParam ( " :categories " , $categories );
2023-11-18 13:06:27 +00:00
$stmt -> bindParam ( " :keywords " , $keywords );
2023-07-12 03:29:56 +01:00
$stmt -> bindParam ( " :folderID " , $folderID );
2023-12-06 00:38:33 +00:00
if ( ! $stmt -> execute ())
2023-07-12 03:29:56 +01:00
{
2023-12-06 00:38:33 +00:00
return " Error, couldn't create post " ;
2023-07-12 03:29:56 +01:00
}
2023-12-06 00:38:33 +00:00
$stmtEmails = $conn -> prepare ( " SELECT email FROM newsletter; " );
$stmtEmails -> execute ();
$emails = $stmtEmails -> fetchAll ( PDO :: FETCH_ASSOC );
2023-12-13 03:10:23 +00:00
$headerImage = substr ( $headerImage , 10 );
$headerImage = str_ireplace ( " %2F " , " / " , $headerImage );
2023-12-06 00:38:33 +00:00
$emailBody = <<< EOD
<! DOCTYPE html >
< html lang = " en " >
< head >
< meta charset = " UTF-8 " >
< title > Rohit Pai ' s blog </ title >
< style >
@ font - face {
font - family : 'Noto Sans' ;
font - style : normal ;
font - weight : 700 ;
font - display : swap ;
src : url ( https :// fonts . gstatic . com / s / notosans / v34 / o - 0 NIpQlx3QUlC5A4PNjXhFVZNyB . woff2 ) format ( 'woff2' );
unicode - range : U + 0000 - 00 FF , U + 0131 , U + 0152 - 0153 , U + 02 BB - 02 BC , U + 02 C6 , U + 02 DA , U + 02 DC , U + 0304 , U + 030 8 , U + 032 9 , U + 2000 - 206 F , U + 2074 , U + 20 AC , U + 2122 , U + 2191 , U + 2193 , U + 2212 , U + 2215 , U + FEFF , U + FFFD ;
}
@ font - face {
font - family : 'Share Tech Mono' ;
font - style : normal ;
font - weight : 400 ;
font - display : swap ;
src : url ( https :// fonts . gstatic . com / s / sharetechmono / v15 / J7aHnp1uDWRBEqV98dVQztYldFcLowEF . woff2 ) format ( 'woff2' );
unicode - range : U + 0000 - 00 FF , U + 0131 , U + 0152 - 0153 , U + 02 BB - 02 BC , U + 02 C6 , U + 02 DA , U + 02 DC , U + 0304 , U + 030 8 , U + 032 9 , U + 2000 - 206 F , U + 2074 , U + 20 AC , U + 2122 , U + 2191 , U + 2193 , U + 2212 , U + 2215 , U + FEFF , U + FFFD ;
}
* {
box - sizing : border - box ;
}
body , html {
margin : 0 ;
padding : 0 ;
}
html {
scroll - behavior : smooth ;
}
body {
font - family : Noto Sans KR , sans - serif ;
font - style : normal ;
font - weight : 500 ;
font - size : 22 px ;
line - height : 1.625 rem ;
min - height : 100 % ;
}
main , header , footer {
max - width : 768 px ;
margin : 0 auto ;
}
a {
text - decoration : none ;
text - transform : lowercase ;
}
a : visited {
color : inherit ;
}
h1 , h2 {
font - family : Share Tech Mono , monospace ;
font - style : normal ;
font - weight : normal ;
line - height : 2.5625 rem ;
text - transform : lowercase ;
}
h1 , nav {
font - size : 40 px ;
}
h2 {
font - size : 28 px ;
}
h3 {
font - size : 22 px ;
}
header {
background : hsla ( 80 , 60 % , 50 % , 1 );
color : #FFFFFF;
border - bottom : 5 px solid hsla ( 0 , 0 % , 75 % , 1 );
}
header div . title , header div . byLine {
padding : 0 3 em 1 em ;
}
header div . title h1 {
margin : 0 ;
padding : 1 em 0 0 ;
font - size : 42 px ;
}
header div . byLine {
border - top : 5 px solid hsla ( 80 , 60 % , 30 % , 1 );
}
a . btn {
background - color : hsla ( 80 , 60 % , 50 % , 1 );
text - decoration : none ;
display : inline - flex ;
padding : 1 em 2 em ;
border - radius : 0.625 em ;
border : 0.3215 em solid hsla ( 80 , 60 % , 50 % , 1 );
color : #FFFFFF;
text - align : center ;
align - items : center ;
max - height : 4 em ;
}
div . postContainer , div . container {
padding : 1 em 2 em 0 ;
margin : 0 auto 1 em ;
max - width : 768 px ;
}
div . postContainer ~ div . postContainer {
border - top : 5 px solid hsla ( 0 , 0 % , 75 % , 1 );
}
div . postContainer > * , div . container > * {
margin : 0 auto ;
max - width : 768 px ;
}
div . postContainer div . post h2 , div . container div . content h2 {
margin : 0 ;
padding : 0 ;
}
div . postContainer div . image , div . container div . image {
width : 50 % ;
max - width : 100 % ;
height : auto ;
}
div . postContainer div . image img {
- webkit - border - radius : 0.5 em ;
- moz - border - radius : 0.5 em ;
border - radius : 0.5 em ;
border : 4 px solid hsla ( 0 , 0 % , 75 % , 1 );
}
div . container div . image img {
- webkit - border - radius : 10 em ;
- moz - border - radius : 10 em ;
border - radius : 10 em ;
border : 4 px solid hsla ( 0 , 0 % , 75 % , 1 );
}
div . postContainer div . image img , div . container div . image img {
min - width : 100 % ;
width : 100 % ;
}
footer {
background - color : hsla ( 80 , 60 % , 50 % , 1 );
margin - top : auto ;
padding : 3 em ;
display : block ;
color : #FFFFFF;
}
footer . nav {
width : 75 % ;
float : left ;
}
footer . nav ul {
list - style : none ;
margin : 0 ;
padding : 0 ;
width : 100 % ;
}
footer . nav ul li {
display : inline - block ;
margin : 0 1 em ;
}
footer . nav ul a {
color : #FFFFFF;
}
footer . date {
width : 25 % ;
float : right ;
}
</ style >
</ head >
< body >
< header >
< div class = " title " >
< h1 > Hey , I ' ve got a new post !</ h1 >
</ div >
< div class = " byLine " >
< p > Hello I ' m Rohit an avid full - stack developer and home lab enthusiast . I love anything and everything
to do with full stack development , home labs , coffee and generally anything to do with tech .</ p >
</ div >
</ header >
< main >
< div class = " postContainer " >
< h2 > latest post </ h2 >
< div class = " image " >
2023-12-13 03:10:23 +00:00
< img src = " https://rohitpai.co.uk/ $headerImage " alt = " header image of the latest post " >
2023-12-06 00:38:33 +00:00
</ div >
< div class = " post " >
< h3 > $title </ h3 >
< p > $abstract </ p >
< a href = " https://rohitpai.co.uk/blog/post/ $title " class = " btn " > See Post </ a >
</ div >
</ div >
< div class = " postContainer " >
< h2 > in case you missed the previous post </ h2 >
< div class = " image " >
2023-12-13 03:10:23 +00:00
< img src = " https://rohitpai.co.uk/ $prevHeaderImage " alt = " header image of the previous post " >
2023-12-06 00:38:33 +00:00
</ div >
< div class = " post " >
< h3 > $prevTitle </ h3 >
< p > $prevAbstract </ p >
< a href = " https://rohitpai.co.uk/blog/post/ $prevTitle " class = " btn " > See Post </ a >
</ div >
</ div >
</ main >
2023-12-13 03:10:23 +00:00
EOD ;
foreach ( $emails as $email )
{
$emailFooter = <<< EOD
2023-12-06 00:38:33 +00:00
< footer >
< div class = " nav " >
< ul >
< li >< a href = " https://rohitpai.co.uk/blog " >& lt ; https :// rohitpai . co . uk / blog & gt ; </ a ></ li >
2023-12-13 03:10:23 +00:00
< li >< a href = " https://rohitpai.co.uk/blog/unsubscribe/ $email " >& lt ; Unsubscribe & gt ; </ a ></ li >
2023-12-06 00:38:33 +00:00
</ ul >
</ div >
< div class = " date " > 2023 </ div >
</ footer >
</ body >
</ html >
2023-12-13 03:10:23 +00:00
EOD ;
$emailBody .= $emailFooter ;
2023-12-06 00:38:33 +00:00
$this -> sendMail ( $email [ " email " ], $emailBody , " Hey, Rohit's blog has a new post! " );
}
return intval ( $conn -> lastInsertId ());
2023-07-12 03:29:56 +01:00
}
2023-06-26 03:54:25 +01:00
/**
* Upload the images in the post to temp folder and return image location
* @ param UploadedFileInterface $img - Image to upload
* @ return string | array - String with error message or array with the location of the uploaded file
*/
public function uploadPostImage ( UploadedFileInterface $img ) : string | array
{
$targetDir = " ../blog/imgs/tmp/ " ;
$imagUtils = new imgUtils ();
$targetFile = $imagUtils -> uploadFile ( $targetDir , $img );
$file = $targetDir . basename ( $img -> getClientFilename ());
if ( file_exists ( $file ))
{
return array ( " url " => $file );
}
if ( ! is_array ( $targetFile ))
{
return $targetFile ;
}
if ( file_exists ( $targetFile [ " imgLocation " ]))
{
return array ( " url " => $targetFile [ " imgLocation " ]);
}
return " Couldn't upload the image " ;
}
/**
2023-07-12 03:29:56 +01:00
* Upload the header image of the post and update the database
* @ param int $ID - ID of the post
* @ param UploadedFileInterface $img - Image to upload
* @ return string | array - String with error message or array with the location of the uploaded file
2023-06-26 03:54:25 +01:00
*/
2023-07-12 03:29:56 +01:00
public function uploadHeaderImage ( int $ID , UploadedFileInterface $img ) : string | array
2023-06-26 03:54:25 +01:00
{
$conn = dbConn ();
2023-07-12 03:29:56 +01:00
$stmt = $conn -> prepare ( " SELECT * FROM blog WHERE ID = :ID; " );
$stmt -> bindParam ( " :ID " , $ID );
$stmt -> execute ();
$result = $stmt -> fetch ( PDO :: FETCH_ASSOC );
if ( ! $result )
2023-06-26 16:52:48 +01:00
{
2023-07-12 03:29:56 +01:00
return " Couldn't find the post " ;
2023-06-26 16:52:48 +01:00
}
2023-07-12 03:29:56 +01:00
$targetDir = " ../blog/imgs/ " . $result [ " title " ] . " _ " . $result [ " folderID " ] . " / " ;
$imagUtils = new imgUtils ();
$targetFile = $imagUtils -> uploadFile ( $targetDir , $img );
2023-06-26 16:52:48 +01:00
2023-06-26 03:54:25 +01:00
if ( ! is_array ( $targetFile ))
{
return $targetFile ;
}
2023-07-12 03:29:56 +01:00
if ( file_exists ( $targetFile [ " imgLocation " ]))
{
unlink ( $result [ " headerImg " ]);
$stmt = $conn -> prepare ( " UPDATE blog SET headerImg = :headerImg WHERE ID = :ID; " );
$stmt -> bindParam ( " :ID " , $ID );
2023-12-13 03:10:23 +00:00
$location = urldecode ( " ../ " . $targetFile [ " imgLocation " ]);
$stmt -> bindParam ( " :headerImg " , $location );
2023-07-12 03:29:56 +01:00
$stmt -> execute ();
if ( $stmt -> rowCount () > 0 )
{
return $targetFile ;
}
return " Couldn't update the post " ;
}
return " Couldn't upload the image " ;
}
/**
* Change the HTML src of the images in the post to point to the new location
* @ param string $body - Body of the post
* @ param string $to - New location of the images
* @ param string $from - Old location of the images
* @ return string - Body of the post with the new image locations
*/
public function changeHTMLSrc ( string $body , string $to , string $from ) : string
{
2023-06-26 03:54:25 +01:00
$htmlDoc = new DOMDocument ();
2024-11-04 22:17:42 +00:00
// Load the raw HTML content into DOMDocument
@ $htmlDoc -> loadHTML ( $body , LIBXML_NOERROR );
// Get the body and process images
2023-06-26 03:54:25 +01:00
$doc = $htmlDoc -> getElementsByTagName ( 'body' ) -> item ( 0 );
$imgs = $doc -> getElementsByTagName ( 'img' );
$srcList = array ();
foreach ( $imgs as $img )
{
$src = $img -> getAttribute ( " src " );
2023-07-12 03:29:56 +01:00
$src = urldecode ( $src );
2023-06-26 03:54:25 +01:00
$srcList [] = $src ;
$fileName = basename ( $src );
2024-11-04 22:17:42 +00:00
// Update the src attribute to the new location
2023-10-18 00:28:34 +01:00
$img -> setAttribute ( " src " , substr ( $to , 2 ) . $fileName );
2023-06-26 03:54:25 +01:00
}
2024-11-04 22:17:42 +00:00
// Rename files and clean up old ones
2023-07-12 03:29:56 +01:00
$files = scandir ( $from );
2023-06-26 03:54:25 +01:00
foreach ( $files as $file )
{
if ( $file != " . " && $file != " .. " )
{
2023-07-12 03:29:56 +01:00
if ( ! in_array ( $from . $file , $srcList ))
2023-06-26 03:54:25 +01:00
{
2023-07-12 03:29:56 +01:00
unlink ( $from . $file );
2023-11-14 01:02:27 +00:00
continue ;
2023-06-26 03:54:25 +01:00
}
2023-11-14 01:02:27 +00:00
rename ( $from . $file , $to . $file );
2023-06-26 03:54:25 +01:00
}
}
2024-11-04 22:17:42 +00:00
// Process the HTML content for output
2023-06-26 03:54:25 +01:00
$newBody = '' ;
foreach ( $doc -> childNodes as $node )
{
2024-11-04 22:17:42 +00:00
// Only convert text nodes to HTML entities
if ( $node -> nodeType === XML_TEXT_NODE )
{
$newBody .= $this -> convertToHtmlEntities ( $node -> nodeValue ); // Convert text nodes
}
else
{
$newBody .= $htmlDoc -> saveHTML ( $node ); // Keep HTML tags intact
}
2023-06-26 03:54:25 +01:00
}
2024-11-04 22:17:42 +00:00
2023-07-12 03:29:56 +01:00
return $newBody ;
2023-06-26 03:54:25 +01:00
}
2023-10-18 00:28:34 +01:00
2024-11-04 22:17:42 +00:00
/**
* Convert all characters in a string to HTML entities while leaving HTML tags intact .
* @ param string $text - The text to convert
* @ return string - The converted text with HTML entities
*/
private function convertToHtmlEntities ( string $text ) : string
{
// Convert characters to HTML entities using mb_encode_numericentity
return htmlentities ( $text , ENT_QUOTES | ENT_HTML5 , 'UTF-8' );
}
2023-10-18 00:28:34 +01:00
/**
* Get all posts with the given category
2023-10-18 23:58:21 +01:00
* @ param string $category - Category of the post
2023-11-14 01:02:27 +00:00
* @ return array < array > - Array of all posts with the given category or error message
2023-10-18 00:28:34 +01:00
*/
2023-10-18 23:58:21 +01:00
public function getPostsByCategory ( string $category ) : array
2023-10-18 00:28:34 +01:00
{
$conn = dbConn ();
2023-10-18 23:58:21 +01:00
$stmt = $conn -> prepare ( " SELECT * FROM blog WHERE LOCATE(:category, categories) > 0; " );
2023-10-18 00:28:34 +01:00
$stmt -> bindParam ( " :category " , $category );
$stmt -> execute ();
2023-10-18 23:58:21 +01:00
return $stmt -> fetchAll ( PDO :: FETCH_ASSOC );
2023-10-18 00:28:34 +01:00
}
2023-10-31 19:36:51 +00:00
/**
* Search for a blog post with the given search term
* @ param string $searchTerm - Search term
2023-11-14 01:02:27 +00:00
* @ return array < array > - Array of all posts with the given search term or error message
2023-10-31 19:36:51 +00:00
*/
public function searchBlog ( string $searchTerm ) : array
{
$conn = dbConn ();
$stmt = $conn -> prepare ( " SELECT * FROM blog WHERE MATCH(title, bodyText) AGAINST(:searchTerm IN NATURAL LANGUAGE MODE); " );
$stmt -> bindParam ( " :searchTerm " , $searchTerm );
$stmt -> execute ();
$result = $stmt -> fetchAll ( PDO :: FETCH_ASSOC );
if ( $result )
{
2023-11-05 17:47:44 +00:00
for ( $i = 0 ; $i < count ( $result ); $i ++ )
2023-10-31 19:36:51 +00:00
{
2023-11-05 17:47:44 +00:00
$result [ $i ][ " abstract " ] = $this -> getShortPost ( $searchTerm , stripcslashes ( $result [ $i ][ " bodyText " ]));
2023-10-31 19:36:51 +00:00
}
return $result ;
}
return array ( " errorMessage " => " Error, could not find posts " );
}
2023-11-05 17:47:44 +00:00
/**
* Get the short post with the search term
* @ param string $searchTerm - Search term
* @ param $text - Body of the post as plain text
* @ return string - Short post with the search term
*/
private function getShortPost ( string $searchTerm , $text ) : string
{
$pattern = '/([,:;!?.-]+)/u' ;
$parts = preg_split ( $pattern , $text , - 1 , PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
$cleanedParts = [];
foreach ( $parts as $part )
{
$part = trim ( $part ); // Remove leading/trailing spaces and newline characters
if ( ! empty ( $part ))
{
$cleanedParts [] = $part ;
}
}
$combinedParts = [];
$currentPart = '' ;
foreach ( $cleanedParts as $part )
{
if ( preg_match ( '/[,:;!?.-]/u' , $part ))
{
$currentPart .= $part ;
}
else
{
if ( ! empty ( $currentPart ))
{
$combinedParts [] = trim ( $currentPart );
}
$currentPart = rtrim ( $part );
}
}
if ( ! empty ( $currentPart ))
{
$combinedParts [] = trim ( $currentPart );
}
$result = " " ;
for ( $i = 0 ; $i < count ( $combinedParts ); $i ++ )
{
$part = $combinedParts [ $i ];
if ( stripos ( $part , $searchTerm ) !== false )
{
$before = ( $i > 0 ) ? $combinedParts [ $i - 1 ] : " " ;
$after = ( $i < count ( $combinedParts ) - 1 ) ? $combinedParts [ $i + 1 ] : " " ;
if ( $before === " " && $i > 0 )
{
$before = $combinedParts [ $i - 1 ];
}
$result = $before . " " . $part . " " . $after ;
// If the search term is found, we don't need to continue checking subsequent parts
break ;
}
}
return $result ;
}
2023-11-14 01:02:27 +00:00
/**
* Generate the XML feed
* @ param mixed $type - Type of feed
* @ return array | string - Error message or the XML feed
*/
private function generateXMLFeed ( mixed $type ) : array | string
{
ob_start ();
$feed = new FeedWriter ( $type );
$feed -> setTitle ( " Rohit Pai's Blog " );
$feed -> setLink ( 'https://rohitpai.co.uk/blog' );
$feed -> setFeedURL ( 'https://rohitpai.co.uk/api/blog/feed/atom' );
$feed -> setChannelElement ( 'updated' , date ( DATE_ATOM , time ()));
$feed -> setChannelElement ( 'author' , [ 'name' => 'Rohit Pai' ]);
$posts = $this -> getBlogPosts ();
if ( isset ( $posts [ " errorMessage " ]))
{
return $posts ;
}
foreach ( $posts as $post )
{
$newItem = $feed -> createNewItem ();
$newItem -> setTitle ( $post [ " title " ]);
$newItem -> setLink ( " https://rohitpai.co.uk/blog/post/ " . rawurlencode ( $post [ " title " ]) . " #disqus_thread " );
$newItem -> setDate ( $post [ " dateModified " ]);
$newItem -> setDescription ( $post [ " body " ]);
$feed -> addItem ( $newItem );
}
$feed -> generateFeed ();
$atom = ob_get_contents ();
ob_end_clean ();
return $atom ;
}
/**
* Generate the JSON feed
* @ return array | array [] - Error message or the JSON feed
*/
private function generateJSONFeed () : array
{
$posts = $this -> getBlogPosts ();
if ( isset ( $posts [ " errorMessage " ]))
{
return $posts ;
}
$json = array ();
$json [ " version " ] = " https://jsonfeed.org/version/1.1 " ;
$json [ " title " ] = " Rohit Pai's Blog " ;
$json [ " home_page_url " ] = " https://rohitpai.co.uk/blog " ;
$json [ " feed_url " ] = " https://rohitpai.co.uk/api/blog/feed/json " ;
$json [ " description " ] = " Rohit Pai's personal blog on all things self hosting and various other tech topics " ;
$json [ " author " ] = array (
" name " => " Rohit Pai " ,
" url " => " https://rohitpai.co.uk " ,
" avatar " => " https://rohitpai.co.uk/imgs/profile.jpg "
);
$items = array ();
foreach ( $posts as $post )
{
$items [] = array (
2023-12-06 00:38:33 +00:00
" id " => strval ( $post [ " ID " ]),
2023-11-14 01:02:27 +00:00
" url " => " https://rohitpai.co.uk/blog/post/ " . rawurlencode ( $post [ " title " ]) . " #disqus_thread " ,
" title " => $post [ " title " ],
" date_published " => date ( $post [ " dateCreated " ]),
" date_modified " => date ( $post [ " dateModified " ]),
2023-12-06 00:38:33 +00:00
" description " => $post [ " abstract " ],
2023-11-14 01:02:27 +00:00
" banner_image " => " https://rohitpai.co.uk/ " . rawurlencode ( $post [ " headerImg " ]),
" content_html " => $post [ " body " ]
);
}
$json [ " items " ] = $items ;
return $json ;
}
/**
* Generate the RSS feed based on type
* @ param string $type - Type of feed
* @ return string | array - RSS feed or an error message
*/
public function getFeed ( string $type ) : string | array
{
$feed = " " ;
if ( $type == " atom " )
{
$feed = $this -> generateXMLFeed ( ATOM );
}
if ( $type == " rss " )
{
$feed = $this -> generateXMLFeed ( RSS2 );
}
if ( $type == " json " )
{
$feed = $this -> generateJSONFeed ();
}
return $feed ;
}
2023-12-06 00:38:33 +00:00
/**
* Add an email to the newsletter and send welcome email
* @ param string $email - Email to add to the newsletter
* @ return string | array - Success or error message
*/
public function addNewsletterEmail ( string $email ) : string | array
{
$conn = dbConn ();
$stmtCheckEmail = $conn -> prepare ( " SELECT * FROM newsletter WHERE email = :email; " );
$stmtCheckEmail -> bindParam ( " :email " , $email );
$stmtCheckEmail -> execute ();
$result = $stmtCheckEmail -> fetch ( PDO :: FETCH_ASSOC );
if ( $result )
{
return " Email already exists " ;
}
$stmt = $conn -> prepare ( " INSERT INTO newsletter (email) VALUES (:email); " );
$stmt -> bindParam ( " :email " , $email );
$stmt -> execute ();
$body = <<< EOD
<! DOCTYPE html >
< html lang = " en " >
< head >
< meta charset = " UTF-8 " >
< title > Rohit Pai ' s blog </ title >
< style >
@ font - face {
font - family : 'Noto Sans' ;
font - style : normal ;
font - weight : 700 ;
font - display : swap ;
src : url ( https :// fonts . gstatic . com / s / notosans / v34 / o - 0 NIpQlx3QUlC5A4PNjXhFVZNyB . woff2 ) format ( 'woff2' );
unicode - range : U + 0000 - 00 FF , U + 0131 , U + 0152 - 0153 , U + 02 BB - 02 BC , U + 02 C6 , U + 02 DA , U + 02 DC , U + 0304 , U + 030 8 , U + 032 9 , U + 2000 - 206 F , U + 2074 , U + 20 AC , U + 2122 , U + 2191 , U + 2193 , U + 2212 , U + 2215 , U + FEFF , U + FFFD ;
}
@ font - face {
font - family : 'Share Tech Mono' ;
font - style : normal ;
font - weight : 400 ;
font - display : swap ;
src : url ( https :// fonts . gstatic . com / s / sharetechmono / v15 / J7aHnp1uDWRBEqV98dVQztYldFcLowEF . woff2 ) format ( 'woff2' );
unicode - range : U + 0000 - 00 FF , U + 0131 , U + 0152 - 0153 , U + 02 BB - 02 BC , U + 02 C6 , U + 02 DA , U + 02 DC , U + 0304 , U + 030 8 , U + 032 9 , U + 2000 - 206 F , U + 2074 , U + 20 AC , U + 2122 , U + 2191 , U + 2193 , U + 2212 , U + 2215 , U + FEFF , U + FFFD ;
}
* {
box - sizing : border - box ;
}
body , html {
margin : 0 ;
padding : 0 ;
}
html {
scroll - behavior : smooth ;
}
body {
font - family : Noto Sans KR , sans - serif ;
font - style : normal ;
font - weight : 500 ;
font - size : 22 px ;
line - height : 1.625 rem ;
min - height : 100 % ;
}
main , header , footer {
max - width : 768 px ;
margin : 0 auto ;
}
a {
text - decoration : none ;
text - transform : lowercase ;
}
a : visited {
color : inherit ;
}
h1 , h2 {
font - family : Share Tech Mono , monospace ;
font - style : normal ;
font - weight : normal ;
line - height : 2.5625 rem ;
text - transform : lowercase ;
}
h1 , nav {
font - size : 40 px ;
}
h2 {
font - size : 28 px ;
}
h3 {
font - size : 22 px ;
}
header {
background : hsla ( 80 , 60 % , 50 % , 1 );
color : #FFFFFF;
border - bottom : 5 px solid hsla ( 0 , 0 % , 75 % , 1 );
}
header div . title , header div . byLine {
padding : 0 3 em 1 em ;
}
header div . title h1 {
margin : 0 ;
padding : 1 em 0 0 ;
font - size : 42 px ;
}
header div . byLine {
border - top : 5 px solid hsla ( 80 , 60 % , 30 % , 1 );
}
a . btn {
background - color : hsla ( 80 , 60 % , 50 % , 1 );
text - decoration : none ;
display : inline - flex ;
padding : 1 em 2 em ;
border - radius : 0.625 em ;
border : 0.3215 em solid hsla ( 80 , 60 % , 50 % , 1 );
color : #FFFFFF;
text - align : center ;
align - items : center ;
max - height : 4 em ;
}
div . postContainer , div . container {
padding : 1 em 2 em 0 ;
margin : 0 auto 1 em ;
max - width : 768 px ;
}
div . postContainer ~ div . postContainer {
border - top : 5 px solid hsla ( 0 , 0 % , 75 % , 1 );
}
div . postContainer > * , div . container > * {
margin : 0 auto ;
max - width : 768 px ;
}
div . postContainer div . post h2 , div . container div . content h2 {
margin : 0 ;
padding : 0 ;
}
div . postContainer div . image , div . container div . image {
width : 50 % ;
max - width : 100 % ;
height : auto ;
}
div . postContainer div . image img {
- webkit - border - radius : 0.5 em ;
- moz - border - radius : 0.5 em ;
border - radius : 0.5 em ;
border : 4 px solid hsla ( 0 , 0 % , 75 % , 1 );
}
div . container div . image img {
- webkit - border - radius : 50 em ;
- moz - border - radius : 50 em ;
border - radius : 50 em ;
border : 4 px solid hsla ( 0 , 0 % , 75 % , 1 );
}
div . postContainer div . image img , div . container div . image img {
min - width : 100 % ;
width : 100 % ;
}
footer {
background - color : hsla ( 80 , 60 % , 50 % , 1 );
margin - top : auto ;
padding : 3 em ;
display : block ;
color : #FFFFFF;
}
footer . nav {
width : 75 % ;
float : left ;
}
footer . nav ul {
list - style : none ;
margin : 0 ;
padding : 0 ;
width : 100 % ;
}
footer . nav ul li {
display : inline - block ;
margin : 0 1 em ;
}
footer . nav ul a {
color : #FFFFFF;
}
footer . date {
width : 25 % ;
float : right ;
}
</ style >
</ head >
< body >
< header >
< div class = " title " >
< h1 > hello from rohit </ h1 >
</ div >
< div class = " byLine " >
< p > Hello I ' m Rohit an avid full - stack developer and home lab enthusiast . I love anything and everything
to do with full stack development , home labs , coffee and generally anything to do with tech .</ p >
</ div >
</ header >
< main >
< div class = " container " >
< h2 > hey there , i ' m rohit !</ h2 >
< div class = " image " >< img src = " https://rohitpai.co.uk/imgs/profile.jpg " alt = " " ></ div >
< div class = " content " >
< h3 > What to Expect </ h3 >
< p > You ' ll get an email from me everytime I make a new post to my blog . Sometimes , you may get a special
email on occasion , where I talk about something interesting that ' s not worth a full on post but I
Still want to tell you about it .</ p >
< p > Don 't worry, I won' t spam you with emails . Well , thanks for signing up to my newsletter , hopefully
you ' ll hear from me soon !</ p >
< p > P . S . Please consider adding this email address rohit @ rohitpai . co . uk to your emails
contact list so that my emails won ' t get sent to span , thanks .</ p >
</ div >
</ div >
</ main >
< footer >
< div class = " nav " >
< ul >
< li >< a href = " https://rohitpai.co.uk/blog " >& lt ; https :// rohitpai . co . uk / blog & gt ; </ a ></ li >
2023-12-13 03:10:23 +00:00
< li >< a href = " https://rohitpai.co.uk/blog/unsubscribe/ $email " >& lt ; Unsubscribe & gt ; </ a ></ li >
2023-12-06 00:38:33 +00:00
</ ul >
</ div >
< div class = " date " > 2023 </ div >
</ footer >
</ body >
</ html >
EOD ;
return $this -> sendMail ( $email , $body , " Hello from Rohit! " );
}
/**
* Send an email to the given email address
* @ param string $email - Email address to send the email to
* @ param string $body - Body of the email
* @ param string $subject - Subject of the email
* @ return string | string []
*/
public function sendMail ( string $email , string $body , string $subject ) : string | array
{
$mail = new PHPMailer ( true );
try
{
$mail -> isSMTP ();
$mail -> Host = " smtp.hostinger.com " ;
$mail -> SMTPAuth = true ;
$mail -> Username = " rohit@rohitpai.co.uk " ;
$mail -> Password = getEmailPassword ();
$mail -> SMTPSecure = " tls " ;
$mail -> Port = 587 ;
$mail -> setFrom ( " rohit@rohitpai.co.uk " , " Rohit Pai " );
$mail -> addAddress ( $email );
$mail -> isHTML ();
$mail -> Subject = $subject ;
$mail -> Body = $body ;
$mail -> send ();
return " success " ;
}
catch ( Exception $e )
{
return array ( " errorMessage " => " Error, couldn't send email because of " . $e );
}
}
/**
* @ param string $email - Email to delete from the newsletter
* @ return string - Success or error message
*/
public function deleteNewsletterEmail ( string $email ) : string
{
$conn = dbConn ();
$stmtCheckEmail = $conn -> prepare ( " SELECT * FROM newsletter WHERE email = :email; " );
$stmtCheckEmail -> bindParam ( " :email " , $email );
$stmtCheckEmail -> execute ();
$result = $stmtCheckEmail -> fetch ( PDO :: FETCH_ASSOC );
if ( ! $result )
{
return " email not found " ;
}
$stmt = $conn -> prepare ( " DELETE FROM newsletter WHERE email = :email; " );
$stmt -> bindParam ( " :email " , $email );
$stmt -> execute ();;
return " success " ;
}
/**
* @ param string $subject - Subject of the newsletter
* @ param string $message - Message content
* @ return array | string - Success or error message
*/
public function sendNewsletter ( string $subject , string $message ) : array | string
{
$conn = dbConn ();
$stmtEmails = $conn -> prepare ( " SELECT email FROM newsletter; " );
$stmtEmails -> execute ();
$emails = $stmtEmails -> fetchAll ( PDO :: FETCH_ASSOC );
$msg = " " ;
$body = <<< EOD
<! DOCTYPE html >
< html lang = " en " >
< head >
< meta charset = " UTF-8 " >
< title > Rohit Pai ' s blog </ title >
< style >
@ font - face {
font - family : 'Noto Sans' ;
font - style : normal ;
font - weight : 700 ;
font - display : swap ;
src : url ( https :// fonts . gstatic . com / s / notosans / v34 / o - 0 NIpQlx3QUlC5A4PNjXhFVZNyB . woff2 ) format ( 'woff2' );
unicode - range : U + 0000 - 00 FF , U + 0131 , U + 0152 - 0153 , U + 02 BB - 02 BC , U + 02 C6 , U + 02 DA , U + 02 DC , U + 0304 , U + 030 8 , U + 032 9 , U + 2000 - 206 F , U + 2074 , U + 20 AC , U + 2122 , U + 2191 , U + 2193 , U + 2212 , U + 2215 , U + FEFF , U + FFFD ;
}
@ font - face {
font - family : 'Share Tech Mono' ;
font - style : normal ;
font - weight : 400 ;
font - display : swap ;
src : url ( https :// fonts . gstatic . com / s / sharetechmono / v15 / J7aHnp1uDWRBEqV98dVQztYldFcLowEF . woff2 ) format ( 'woff2' );
unicode - range : U + 0000 - 00 FF , U + 0131 , U + 0152 - 0153 , U + 02 BB - 02 BC , U + 02 C6 , U + 02 DA , U + 02 DC , U + 0304 , U + 030 8 , U + 032 9 , U + 2000 - 206 F , U + 2074 , U + 20 AC , U + 2122 , U + 2191 , U + 2193 , U + 2212 , U + 2215 , U + FEFF , U + FFFD ;
}
* {
box - sizing : border - box ;
}
body , html {
margin : 0 ;
padding : 0 ;
}
html {
scroll - behavior : smooth ;
}
body {
font - family : Noto Sans KR , sans - serif ;
font - style : normal ;
font - weight : 500 ;
font - size : 22 px ;
line - height : 1.625 rem ;
min - height : 100 % ;
}
main , header , footer {
max - width : 768 px ;
margin : 0 auto ;
}
a {
text - decoration : none ;
text - transform : lowercase ;
}
a : visited {
color : inherit ;
}
h1 , h2 {
font - family : Share Tech Mono , monospace ;
font - style : normal ;
font - weight : normal ;
line - height : 2.5625 rem ;
text - transform : lowercase ;
}
h1 , nav {
font - size : 40 px ;
}
h2 {
font - size : 28 px ;
}
h3 {
font - size : 22 px ;
}
header {
background : hsla ( 80 , 60 % , 50 % , 1 );
color : #FFFFFF;
border - bottom : 5 px solid hsla ( 0 , 0 % , 75 % , 1 );
}
header div . title , header div . byLine {
padding : 0 3 em 1 em ;
}
header div . title h1 {
margin : 0 ;
padding : 1 em 0 0 ;
font - size : 42 px ;
}
header div . byLine {
border - top : 5 px solid hsla ( 80 , 60 % , 30 % , 1 );
}
a . btn {
background - color : hsla ( 80 , 60 % , 50 % , 1 );
text - decoration : none ;
display : inline - flex ;
padding : 1 em 2 em ;
border - radius : 0.625 em ;
border : 0.3215 em solid hsla ( 80 , 60 % , 50 % , 1 );
color : #FFFFFF;
text - align : center ;
align - items : center ;
max - height : 4 em ;
}
div . postContainer , div . container {
padding : 1 em 2 em 0 ;
margin : 0 auto 1 em ;
max - width : 768 px ;
}
div . postContainer ~ div . postContainer {
border - top : 5 px solid hsla ( 0 , 0 % , 75 % , 1 );
}
div . postContainer > * , div . container > * {
margin : 0 auto ;
max - width : 768 px ;
}
div . postContainer div . post h2 , div . container div . content h2 {
margin : 0 ;
padding : 0 ;
}
div . postContainer div . image , div . container div . image {
width : 50 % ;
max - width : 100 % ;
height : auto ;
}
div . postContainer div . image img {
- webkit - border - radius : 0.5 em ;
- moz - border - radius : 0.5 em ;
border - radius : 0.5 em ;
border : 4 px solid hsla ( 0 , 0 % , 75 % , 1 );
}
div . container div . image img {
- webkit - border - radius : 50 em ;
- moz - border - radius : 50 em ;
border - radius : 50 em ;
border : 4 px solid hsla ( 0 , 0 % , 75 % , 1 );
}
div . postContainer div . image img , div . container div . image img {
min - width : 100 % ;
width : 100 % ;
}
footer {
background - color : hsla ( 80 , 60 % , 50 % , 1 );
margin - top : auto ;
padding : 3 em ;
display : block ;
color : #FFFFFF;
}
footer . nav {
width : 75 % ;
float : left ;
}
footer . nav ul {
list - style : none ;
margin : 0 ;
padding : 0 ;
width : 100 % ;
}
footer . nav ul li {
display : inline - block ;
margin : 0 1 em ;
}
footer . nav ul a {
color : #FFFFFF;
}
footer . date {
width : 25 % ;
float : right ;
}
</ style >
</ head >
< body >
< header >
< div class = " title " >
< h1 > a surprise hello from rohit </ h1 >
</ div >
< div class = " byLine " >
< p > Hello I ' m Rohit an avid full - stack developer and home lab enthusiast . I love anything and everything
to do with full stack development , home labs , coffee and generally anything to do with tech .</ p >
</ div >
</ header >
< main >
< div class = " container " >
< h2 > $subject </ h2 >
< div class = " content " >
$message
</ div >
</ div >
</ main >
< footer >
< div class = " nav " >
< ul >
< li >< a href = " https://rohitpai.co.uk/blog " >& lt ; https :// rohitpai . co . uk / blog & gt ; </ a ></ li >
< li >< a href = " https://rohitpai.co.uk/blog/unsubscribe " >& lt ; Unsubscribe & gt ; </ a ></ li >
</ ul >
</ div >
< div class = " date " > 2023 </ div >
</ footer >
</ body >
</ html >
EOD ;
foreach ( $emails as $email )
{
$msg = $this -> sendMail ( $email [ " email " ], $body , $subject );
if ( is_array ( $msg ))
{
return $msg ;
}
}
return $msg ;
}
2023-06-06 16:04:39 +01:00
}