Created feeds and UI for feeds and newsletter
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 19s

Signed-off-by: rodude123 <rodude123@gmail.com>
This commit is contained in:
2023-11-14 01:02:27 +00:00
parent 6cfea3fc98
commit f27a5113b1
32 changed files with 2729 additions and 671 deletions
+132 -12
View File
@@ -2,13 +2,18 @@
namespace api\blog;
use api\utils\feedGenerator\FeedWriter;
use api\utils\imgUtils;
use DOMDocument;
use PDO;
use Psr\Http\Message\UploadedFileInterface;
use function DI\string;
use const api\utils\feedGenerator\ATOM;
use const api\utils\feedGenerator\RSS2;
require_once __DIR__ . "/../utils/config.php";
require_once __DIR__ . "/../utils/imgUtils.php";
require_once __DIR__ . "/../utils/feedGenerator/FeedWriter.php";
/**
* Blog Data Class
@@ -18,12 +23,15 @@ class blogData
{
/**
* Get all blog posts
* @return array - Array of all blog posts or error message
* @return array<array> - Array of all blog posts or error message
*/
public function getBlogPosts(): array
{
$conn = dbConn();
$stmt = $conn->prepare("SELECT * FROM blog ORDER BY featured DESC, dateCreated DESC;");
$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,
headerImg, body, bodyText, categories, folderID FROM blog ORDER BY featured DESC,
dateCreated DESC;");
$stmt->execute();
// set the resulting array to associative
@@ -40,12 +48,15 @@ class blogData
/**
* Get a blog post with the given ID
* @param string $title - Title of the blog post
* @return array - Array of all blog posts or error message
* @return array - Array of blog post or error message
*/
public function getBlogPost(string $title): array
{
$conn = dbConn();
$stmt = $conn->prepare("SELECT * FROM blog WHERE title = :title;");
$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,
headerImg, body, bodyText, categories, folderID FROM blog WHERE
title = :title;");
$stmt->bindParam(":title", $title);
$stmt->execute();
@@ -67,7 +78,10 @@ class blogData
public function getLatestBlogPost(): array
{
$conn = dbConn();
$stmt = $conn->prepare("SELECT * FROM blog ORDER BY dateCreated DESC LIMIT 1;");
$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,
headerImg, body, bodyText, categories, folderID FROM blog ORDER BY
dateCreated DESC LIMIT 1;");
$stmt->execute();
// set the resulting array to associative
@@ -88,7 +102,9 @@ class blogData
public function getFeaturedBlogPost(): array
{
$conn = dbConn();
$stmt = $conn->prepare("SELECT * FROM blog WHERE featured = 1;");
$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,
headerImg, body, bodyText, categories, folderID FROM blog WHERE featured = 1;");
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
@@ -399,11 +415,10 @@ class blogData
if (!in_array($from . $file, $srcList))
{
unlink($from . $file);
continue;
}
else
{
rename($from . $file, $to . $file);
}
rename($from . $file, $to . $file);
}
}
@@ -418,7 +433,7 @@ class blogData
/**
* Get all posts with the given category
* @param string $category - Category of the post
* @return array - Array of all posts with the given category or error message
* @return array<array> - Array of all posts with the given category or error message
*/
public function getPostsByCategory(string $category): array
{
@@ -432,7 +447,7 @@ class blogData
/**
* Search for a blog post with the given search term
* @param string $searchTerm - Search term
* @return array - Array of all posts with the given search term or error message
* @return array<array> - Array of all posts with the given search term or error message
*/
public function searchBlog(string $searchTerm): array
{
@@ -527,4 +542,109 @@ class blogData
return $result;
}
/**
* 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(
"id" => string($post["ID"]),
"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"]),
// "description" => $post["abstract"],
"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;
}
}
+171 -131
View File
@@ -4,6 +4,7 @@ namespace api\blog;
require_once __DIR__ . "/../utils/routesInterface.php";
require_once "blogData.php";
use api\utils\routesInterface;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
@@ -80,35 +81,15 @@ class blogRoutes implements routesInterface
$app->get("/blog/post/{type}", function (Request $request, Response $response, $args)
{
if ($args["type"] != null)
if ($args["type"] == null)
{
if ($args["type"] == "latest")
{
$post = $this->blogData->getLatestBlogPost();
if (array_key_exists("errorMessage", $post))
{
$response->getBody()->write(json_encode($post));
return $response->withStatus(404);
}
$response->getBody()->write(json_encode(array("error" => "Please provide a title")));
return $response->withStatus(400);
}
$response->getBody()->write(json_encode($post));
return $response;
}
if ($args["type"] == "featured")
{
$post = $this->blogData->getFeaturedBlogPost();
if (array_key_exists("errorMessage", $post))
{
$response->getBody()->write(json_encode($post));
return $response->withStatus(404);
}
$response->getBody()->write(json_encode($post));
return $response;
}
$post = $this->blogData->getBlogPost($args["type"]);
if ($args["type"] == "latest")
{
$post = $this->blogData->getLatestBlogPost();
if (array_key_exists("errorMessage", $post))
{
$response->getBody()->write(json_encode($post));
@@ -119,114 +100,173 @@ class blogRoutes implements routesInterface
return $response;
}
$response->getBody()->write(json_encode(array("error" => "Please provide a title")));
if ($args["type"] == "featured")
{
$post = $this->blogData->getFeaturedBlogPost();
if (array_key_exists("errorMessage", $post))
{
$response->getBody()->write(json_encode($post));
return $response->withStatus(404);
}
$response->getBody()->write(json_encode($post));
return $response;
}
$post = $this->blogData->getBlogPost($args["type"]);
if (array_key_exists("errorMessage", $post))
{
$response->getBody()->write(json_encode($post));
return $response->withStatus(404);
}
$response->getBody()->write(json_encode($post));
return $response;
});
$app->get("/blog/feed/{type}", function (Request $request, Response $response, $args)
{
if ($args["type"] == null)
{
$response->getBody()->write(json_encode(array("error" => "Please provide a title")));
return $response->withStatus(400);
}
$feed = $this->blogData->getFeed($args["type"]);
if (is_array($feed))
{
$response->getBody()->write(json_encode($feed));
return $response->withStatus(404);
}
if ($args["type"] == "atom")
{
$response->getBody()->write($feed);
return $response->withHeader("Content-Type", "application/atom+xml");
}
if ($args["type"] == "rss")
{
$response->getBody()->write($feed);
return $response->withHeader("Content-Type", "application/rss+xml");
}
if ($args["type"] == "json")
{
$response->getBody()->write(json_encode($feed));
return $response->withHeader("Content-Type", "application/feed+json");
}
$response->getBody()->write(json_encode(array("error" => "Invalid feed type")));
return $response->withStatus(400);
});
$app->get("/blog/search/{searchTerm}", function (Request $request, $response, $args)
{
if ($args["searchTerm"] != null)
if ($args["searchTerm"] == null)
{
$posts = $this->blogData->searchBlog($args["searchTerm"]);
$json = json_encode($posts);
$response->getBody()->write($json);
if (array_key_exists("errorMessage", $posts))
{
$response->withStatus(404);
}
return $response;
$response->getBody()->write(json_encode(array("error" => "Please provide a search term")));
return $response->withStatus(400);
}
$response->getBody()->write(json_encode(array("error" => "Please provide a search term")));
return $response->withStatus(400);
$posts = $this->blogData->searchBlog($args["searchTerm"]);
$json = json_encode($posts);
$response->getBody()->write($json);
if (array_key_exists("errorMessage", $posts))
{
$response->withStatus(404);
}
return $response;
});
$app->patch("/blog/post/{id}", function (Request $request, Response $response, $args)
{
$data = $request->getParsedBody();
if ($args["id"] != null)
if ($args["id"] == null)
{
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["bodyText"]) || empty($data["dateModified"]) || empty($data["categories"]))
{
// uh oh sent some empty data
$response->getBody()->write(json_encode(array("error" => "Only some of the data was sent")));
return $response->withStatus(400);
}
if (!preg_match('/[a-zA-Z0-9 ]+, |\w+/mx', $data["categories"]))
{
// uh oh sent some empty data
$response->getBody()->write(json_encode(array("error" => "Categories must be in a CSV format")));
return $response->withStatus(400);
}
$message = $this->blogData->updatePost($args["id"], $data["title"], intval($data["featured"]), $data["abstract"], $data["body"], $data["bodyText"], $data["dateModified"], $data["categories"]);
if ($message === "post not found")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Error, post not found")));
return $response->withStatus(404);
}
if ($message === "unset featured")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Error, cannot unset featured post, try updating another post to be featured first")));
return $response->withStatus(409);
}
if (!is_bool($message) || $message === false)
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => $message)));
return $response->withStatus(500);
}
$response->withStatus(201);
return $response;
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
}
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["bodyText"]) || empty($data["dateModified"]) || empty($data["categories"]))
{
// uh oh sent some empty data
$response->getBody()->write(json_encode(array("error" => "Only some of the data was sent")));
return $response->withStatus(400);
}
if (!preg_match('/[a-zA-Z0-9 ]+, |\w+/mx', $data["categories"]))
{
// uh oh sent some empty data
$response->getBody()->write(json_encode(array("error" => "Categories must be in a CSV format")));
return $response->withStatus(400);
}
$message = $this->blogData->updatePost($args["id"], $data["title"], intval($data["featured"]), $data["abstract"], $data["body"], $data["bodyText"], $data["dateModified"], $data["categories"]);
if ($message === "post not found")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Error, post not found")));
return $response->withStatus(404);
}
if ($message === "unset featured")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Error, cannot unset featured post, try updating another post to be featured first")));
return $response->withStatus(409);
}
if (!is_bool($message) || $message === false)
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => $message)));
return $response->withStatus(500);
}
$response->withStatus(201);
return $response;
});
$app->delete("/blog/post/{id}", function (Request $request, Response $response, $args)
{
if ($args["id"] != null)
if ($args["id"] == null)
{
$message = $this->blogData->deletePost($args["id"]);
if ($message === "post not found")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Error, post not found")));
return $response->withStatus(404);
}
if ($message === "error")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Error, something went wrong")));
return $response->withStatus(500);
}
if ($message === "cannot delete")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Error, cannot delete featured post")));
return $response->withStatus(409);
}
return $response;
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
}
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
$message = $this->blogData->deletePost($args["id"]);
if ($message === "post not found")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Error, post not found")));
return $response->withStatus(404);
}
if ($message === "error")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Error, something went wrong")));
return $response->withStatus(500);
}
if ($message === "cannot delete")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Error, cannot delete featured post")));
return $response->withStatus(409);
}
return $response;
});
$app->post("/blog/post", function (Request $request, Response $response)
@@ -290,28 +330,28 @@ class blogRoutes implements routesInterface
{
$files = $request->getUploadedFiles();
if ($args["id"] != null)
if ($args["id"] == null)
{
if (empty($files))
{
// uh oh sent some empty data
$response->getBody()->write(json_encode(array("error" => array("message" => "Error, empty data sent"))));
return $response->withStatus(400);
}
$message = $this->blogData->uploadHeaderImage($args["id"], $files["headerImg"]);
if (!is_array($message))
{
$response->getBody()->write(json_encode(array("error" => array("message" => $message))));
return $response->withStatus(500);
}
$response->getBody()->write(json_encode($message));
return $response->withStatus(201);
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
}
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
if (empty($files))
{
// uh oh sent some empty data
$response->getBody()->write(json_encode(array("error" => array("message" => "Error, empty data sent"))));
return $response->withStatus(400);
}
$message = $this->blogData->uploadHeaderImage($args["id"], $files["headerImg"]);
if (!is_array($message))
{
$response->getBody()->write(json_encode(array("error" => array("message" => $message))));
return $response->withStatus(500);
}
$response->getBody()->write(json_encode($message));
return $response->withStatus(201);
});
}
}
+1 -1
View File
@@ -17,7 +17,7 @@ class projectData
{
/**
* Get all project data
* @return array - Array of all project data or error message
* @return array<array> - Array of all project data or error message
*/
public function getProjectData(): array
{
+62 -62
View File
@@ -50,78 +50,78 @@ class projectRoutes implements routesInterface
$app->patch("/projectData/{id}", function (Request $request, Response $response, array $args)
{
$data = $request->getParsedBody();
if ($args["id"] != null)
if ($args["id"] == null)
{
if (empty($data["title"]) || empty($data["isMainProject"]) || empty($data["information"]) || empty($data["gitLink"]))
{
// uh oh sent some empty data
$response->getBody()->write(json_encode(array("error" => "Only some of the data was sent")));
return $response->withStatus(400);
}
$isMainProject = $data["isMainProject"] === "true";
$update = $this->projectData->updateProjectData($args["id"], $data["title"], $isMainProject, $data["information"], $data["projectLink"], $data["gitLink"]);
if ($update === "project not found")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Project with ID " . $args["id"] . " not found")));
return $response->withStatus(404);
}
if ($update === "unset main project")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Can't unset project as main project, try updating another project as the main project")));
return $response->withStatus(400);
}
if (!$update)
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Something went wrong")));
return $response->withStatus(500);
}
return $response;
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
}
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
if (empty($data["title"]) || empty($data["isMainProject"]) || empty($data["information"]) || empty($data["gitLink"]))
{
// uh oh sent some empty data
$response->getBody()->write(json_encode(array("error" => "Only some of the data was sent")));
return $response->withStatus(400);
}
$isMainProject = $data["isMainProject"] === "true";
$update = $this->projectData->updateProjectData($args["id"], $data["title"], $isMainProject, $data["information"], $data["projectLink"], $data["gitLink"]);
if ($update === "project not found")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Project with ID " . $args["id"] . " not found")));
return $response->withStatus(404);
}
if ($update === "unset main project")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Can't unset project as main project, try updating another project as the main project")));
return $response->withStatus(400);
}
if (!$update)
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Something went wrong")));
return $response->withStatus(500);
}
return $response;
});
$app->delete("/projectData/{id}", function (Request $request, Response $response, array $args)
{
if ($args["id"] != null)
if ($args["id"] == null)
{
$message = $this->projectData->deleteProjectData($args["id"]);
if ($message === "project not found")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Project with ID " . $args["id"] . " not found")));
return $response->withStatus(404);
}
if ($message === "cannot delete")
{
//uh oh cannot delete the main project
$response->getBody()->write(json_encode(array("error" => "Cannot delete the main project")));
return $response->withStatus(409);
}
if ($message === "error")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Something went wrong")));
return $response->withStatus(500);
}
return $response;
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
}
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
$message = $this->projectData->deleteProjectData($args["id"]);
if ($message === "project not found")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Project with ID " . $args["id"] . " not found")));
return $response->withStatus(404);
}
if ($message === "cannot delete")
{
//uh oh cannot delete the main project
$response->getBody()->write(json_encode(array("error" => "Cannot delete the main project")));
return $response->withStatus(409);
}
if ($message === "error")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Something went wrong")));
return $response->withStatus(500);
}
return $response;
});
$app->post("/projectData", function (Request $request, Response $response)
+1 -1
View File
@@ -14,7 +14,7 @@ class timelineData
{
/**
* Get all education data
* @return array - Array of all education data or error message
* @return array<array> - Array of all education data or error message
*/
public function getEduData(): array
{
+152
View File
@@ -0,0 +1,152 @@
<?php
namespace api\utils\feedGenerator;
/**
* Universal Feed Writer
*
* FeedItem class - Used as a feed element in FeedWriter class
*
* @package UniversalFeedWriter
* @author Anis uddin Ahmad <anisniit@gmail.com>
* @link http://www.ajaxray.com/projects/rss
*/
class FeedItem
{
private array $elements = []; // Collection of feed elements
private string $version;
/**
* Constructor
*
* @param string $version (RSS1/RSS2/ATOM) RSS2 is the default.
*/
public function __construct(string $version = RSS2)
{
$this->version = $version;
}
/**
* Add an element to elements array
*
* @param string $elementName The tag name of an element
* @param string $content The content of the tag
* @param array|null $attributes Attributes (if any) in 'attrName' => 'attrValue' format
*/
public function addElement(string $elementName, string $content, ?array $attributes = null): void
{
$this->elements[$elementName]['name'] = $elementName;
$this->elements[$elementName]['content'] = $content;
$this->elements[$elementName]['attributes'] = $attributes;
}
/**
* Set multiple feed elements from an array.
* Elements that have attributes cannot be added by this method
*
* @param array $elementArray Array of elements in 'tagName' => 'tagContent' format.
*/
public function addElementArray(array $elementArray): void
{
foreach ($elementArray as $elementName => $content)
{
$this->addElement($elementName, $content);
}
}
/**
* Return the collection of elements in this feed item
*
* @return array
*/
public function getElements(): array
{
return $this->elements;
}
// Wrapper functions ------------------------------------------------------
/**
* Set the 'description' element of the feed item
*
* @param string $description The content of the 'description' element
*/
public function setDescription(string $description): void
{
$tag = ($this->version === ATOM) ? 'summary' : 'description';
$this->addElement($tag, $description);
}
/**
* Set the 'title' element of the feed item
*
* @param string $title The content of the 'title' element
*/
public function setTitle(string $title): void
{
$this->addElement('title', $title);
}
/**
* Set the 'date' element of the feed item
*
* @param string|int $date The content of the 'date' element
*/
public function setDate(string|int $date): void
{
if (!is_numeric($date))
{
$date = strtotime($date);
}
if ($this->version === ATOM)
{
$tag = 'updated';
$value = date(DATE_ATOM, $date);
}
elseif ($this->version === RSS2)
{
$tag = 'pubDate';
$value = date(DATE_RSS, $date);
}
else
{
$tag = 'dc:date';
$value = date("Y-m-d", $date);
}
$this->addElement($tag, $value);
}
/**
* Set the 'link' element of the feed item
*
* @param string $link The content of the 'link' element
*/
public function setLink(string $link): void
{
if ($this->version === RSS2 || $this->version === RSS1)
{
$this->addElement('link', $link);
}
else
{
$this->addElement('link', '', ['href' => $link]);
$this->addElement('id', FeedWriter::uuid($link, 'urn:uuid:'));
}
}
/**
* Set the 'encloser' element of the feed item
* For RSS 2.0 only
*
* @param string $url The url attribute of the encloser tag
* @param string $length The length attribute of the encloser tag
* @param string $type The type attribute of the encloser tag
*/
public function setEncloser(string $url, string $length, string $type): void
{
$attributes = ['url' => $url, 'length' => $length, 'type' => $type];
$this->addElement('enclosure', '', $attributes);
}
}
+386
View File
@@ -0,0 +1,386 @@
<?php
namespace api\utils\feedGenerator;
require_once "FeedItem.php";
/**
* Universal Feed Writer class
*
* Generate RSS 1.0, RSS 2.0, and Atom Feed
*
* @package UniversalFeedWriter
* @link http://www.ajaxray.com/projects/rss
*/
class FeedWriter
{
private array $channels = []; // Collection of channel elements
private array $items = []; // Collection of items as objects of FeedItem class
private array $data = []; // Store some other version-wise data
private array $CDATAEncoding = []; // The tag names that need to be encoded as CDATA
private string $version;
/**
* Constructor
*
* @param string $version The version (RSS1, RSS2, ATOM).
*/
public function __construct(string $version = RSS2)
{
$this->version = $version;
// Setting default values for essential channel elements
$this->channels['title'] = $version . ' Feed';
$this->channels['link'] = 'http://www.ajaxray.com/blog';
$this->channels["feedUrl"] = "http://example.com/feed";
// Tag names to encode in CDATA
$this->CDATAEncoding = ['description', 'content:encoded', 'summary'];
}
// Public functions
/**
* Set a channel element
*
* @param string $elementName Name of the channel tag
* @param string $content Content of the channel tag
*/
public function setChannelElement(string $elementName, string|array $content): void
{
$this->channels[$elementName] = $content;
}
/**
* Generate the actual RSS/Atom file
*/
public function generateFeed(): void
{
$this->printHead();
$this->printChannels();
$this->printItems();
$this->printTail();
}
/**
* Create a new FeedItem.
*
* @return FeedItem An instance of FeedItem class
*/
public function createNewItem(): FeedItem
{
$item = new FeedItem($this->version);
return $item;
}
/**
* Add a FeedItem to the main class
*
* @param FeedItem $feedItem An instance of FeedItem class
*/
public function addItem(FeedItem $feedItem): void
{
$this->items[] = $feedItem;
}
// Wrapper functions
/**
* Set the 'title' channel element
*
* @param string $title Value of 'title' channel tag
*/
public function setTitle(string $title): void
{
$this->setChannelElement('title', $title);
}
/**
* Set the 'description' channel element
*
* @param string $description Value of 'description' channel tag
*/
public function setDescription(string $description): void
{
$this->setChannelElement('description', $description);
}
/**
* Set the 'link' channel element
*
* @param string $link Value of 'link' channel tag
*/
public function setLink(string $link): void
{
$this->setChannelElement('link', $link);
}
/**
* Set the 'image' channel element
*
* @param string $title Title of the image
* @param string $link Link URL of the image
* @param string $url Path URL of the image
*/
public function setImage(string $title, string $link, string $url): void
{
$this->setChannelElement('image', ['title' => $title, 'link' => $link, 'url' => $url]);
}
/**
* Set the 'about' channel element. Only for RSS 1.0
*
* @param string $url Value of 'about' channel tag
*/
public function setChannelAbout(string $url): void
{
$this->data['ChannelAbout'] = $url;
}
// Other functions
/**
* Generates a UUID
*
* @param string $key An optional prefix
* @param string $prefix A prefix
* @return string The formatted UUID
*/
public static function uuid(?string $key = null, string $prefix = ''): string
{
$key = $key ?? uniqid((string)rand());
$chars = md5($key);
$uuid = substr($chars, 0, 8) . '-';
$uuid .= substr($chars, 8, 4) . '-';
$uuid .= substr($chars, 12, 4) . '-';
$uuid .= substr($chars, 16, 4) . '-';
$uuid .= substr($chars, 20, 12);
return $prefix . $uuid;
}
// Private functions
/**
* Prints the XML and RSS namespace
*/
private function printHead(): void
{
$out = '<?xml version="1.0" encoding="utf-8"?>' . "\n";
if ($this->version == RSS2)
{
$out .= '<rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
>' . PHP_EOL;
}
elseif ($this->version == RSS1)
{
$out .= '<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://purl.org/rss/1.0/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
>' . PHP_EOL;
}
elseif ($this->version == ATOM)
{
$out .= '<feed xmlns="http://www.w3.org/2005/Atom">' . PHP_EOL;
}
echo $out;
}
/**
* Closes the open tags at the end of the file
*/
private function printTail(): void
{
if ($this->version == RSS2)
{
echo '</channel>' . PHP_EOL . '</rss>';
}
elseif ($this->version == RSS1)
{
echo '</rdf:RDF>';
}
elseif ($this->version == ATOM)
{
echo '</feed>';
}
}
/**
* Creates a single node in XML format
*
* @param string $tagName Name of the tag
* @param mixed $tagContent Tag value as a string or an array of nested tags in 'tagName' => 'tagValue' format
* @param array|null $attributes Attributes (if any) in 'attrName' => 'attrValue' format
* @return string Formatted XML tag
*/
private function makeNode(string $tagName, $tagContent, ?array $attributes = null): string
{
$nodeText = '';
$attrText = '';
if (is_array($attributes))
{
foreach ($attributes as $key => $value)
{
$attrText .= " $key=\"$value\"";
}
}
if (is_array($tagContent) && $this->version == RSS1)
{
$attrText = ' rdf:parseType="Resource"';
}
$attrText .= (in_array($tagName, $this->CDATAEncoding) && $this->version == ATOM) ? ' type="html" ' : '';
$nodeText .= (in_array($tagName, $this->CDATAEncoding)) ? "<{$tagName}{$attrText}><![CDATA[" : "<{$tagName}{$attrText}>";
if (is_array($tagContent))
{
foreach ($tagContent as $key => $value)
{
$nodeText .= $this->makeNode($key, $value);
}
}
else
{
$nodeText .= (in_array($tagName, $this->CDATAEncoding)) ? $tagContent : htmlentities($tagContent);
}
$nodeText .= (in_array($tagName, $this->CDATAEncoding)) ? "]]></$tagName>" : "</$tagName>";
return $nodeText . PHP_EOL;
}
/**
* Prints channel elements
*/
private function printChannels(): void
{
// Start channel tag
switch ($this->version)
{
case RSS2:
echo '<channel>' . PHP_EOL;
break;
case RSS1:
echo (isset($this->data['ChannelAbout'])) ? "<channel rdf:about=\"{$this->data['ChannelAbout']}\">" : "<channel rdf:about=\"{$this->channels['link']}\">";
break;
}
// Print items of channel
foreach ($this->channels as $key => $value)
{
if ($this->version == ATOM && $key == 'link')
{
// ATOM prints the link element as an href attribute
echo $this->makeNode($key, '', ['href' => $value]);
// Add the id for ATOM
echo $this->makeNode('id', $this->uuid($value, 'urn:uuid:'));
}
else if ($this->version == ATOM && $key == 'feedUrl')
{
echo $this->makeNode('link', '', ['rel' => 'self', 'href' => $value]);
}
else
{
echo $this->makeNode($key, $value);
}
}
// RSS 1.0 has a special tag <rdf:Seq> with channel
if ($this->version == RSS1)
{
echo "<items>" . PHP_EOL . "<rdf:Seq>" . PHP_EOL;
foreach ($this->items as $item)
{
$thisItems = $item->getElements();
echo "<rdf:li resource=\"{$thisItems['link']['content']}\"/>" . PHP_EOL;
}
echo "</rdf:Seq>" . PHP_EOL . "</items>" . PHP_EOL . "</channel>" . PHP_EOL;
}
}
/**
* Prints formatted feed items
*/
private function printItems(): void
{
foreach ($this->items as $item)
{
$thisItems = $item->getElements();
// The argument is printed as rdf:about attribute of item in RSS 1.0
echo $this->startItem($thisItems['link']['content']);
foreach ($thisItems as $feedItem)
{
echo $this->makeNode($feedItem['name'], $feedItem['content'], $feedItem['attributes']);
}
echo $this->endItem();
}
}
/**
* Makes the starting tag of items
*
* @param string|false $about The value of about tag, which is used only for RSS 1.0
*/
private function startItem($about = false): void
{
if ($this->version == RSS2)
{
echo '<item>' . PHP_EOL;
}
elseif ($this->version == RSS1)
{
if ($about)
{
echo "<item rdf:about=\"$about\">" . PHP_EOL;
}
else
{
die("link element is not set.\n It's required for RSS 1.0 to be used as the about attribute of the item");
}
}
elseif ($this->version == ATOM)
{
echo "<entry>" . PHP_EOL;
}
}
/**
* Closes the feed item tag
*/
private function endItem(): void
{
if ($this->version == RSS2 || $this->version == RSS1)
{
echo '</item>' . PHP_EOL;
}
elseif ($this->version == ATOM)
{
echo "</entry>" . PHP_EOL;
}
}
/**
* Set the Feed URL
* @param string $string - The URL of the feed
* @return void
*/
public function setFeedURL(string $string): void
{
$this->setChannelElement("feedUrl", $string);
}
}
// Define constants for RSS 1.0, RSS 2.0, and Atom
const RSS1 = 'RSS 1.0';
const RSS2 = 'RSS 2.0';
const ATOM = 'ATOM';
+6 -1
View File
@@ -65,7 +65,12 @@ class middleware
$app->add(function ($request, $handler)
{
$response = $handler->handle($request);
return $response->withHeader("Content-Type", "application/json");
$contentType = $response->getHeaderLine("Content-Type");
if (empty($contentType) || $contentType === "text/html")
{
return $response->withHeader("Content-Type", "application/json");
}
return $response;
});
}