Initial Commit

This commit is contained in:
Netkas 2023-08-13 18:09:44 -04:00
commit 9b588562da
No known key found for this signature in database
GPG key ID: 5DAF58535614062B
27 changed files with 1668 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
build/

46
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,46 @@
image: php:8.1
before_script:
# Install some stuff that the image doesn't come with
- apt update -yqq
- apt install git libpq-dev libzip-dev zip make wget gnupg -yqq
# Install phive
- wget -O phive.phar https://phar.io/releases/phive.phar
- wget -O phive.phar.asc https://phar.io/releases/phive.phar.asc
- gpg --keyserver hkps://keys.openpgp.org --recv-keys 0x9D8A98B29B2D5D79
- gpg --verify phive.phar.asc phive.phar
- chmod +x phive.phar
- mv phive.phar /usr/local/bin/phive
# Install phab
- phive install phpab --global --trust-gpg-keys 0x2A8299CE842DD38C
# Install the latest version of ncc (Nosial Code Compiler)
- git clone https://git.n64.cc/nosial/ncc.git
- cd ncc
- make redist
- php build/src/INSTALL --auto --install-composer
- cd .. && rm -rf ncc
build:
stage: build
script:
- ncc build --config release --log-level debug
artifacts:
paths:
- build/
rules:
- if: $CI_COMMIT_BRANCH
release:
stage: deploy
script:
- ncc build --config release --log-level debug
- >
curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file build/release/net.nosial.rsslib.ncc "$CI_API_V4_URL/projects/$CI_PROJECT_ID/packages/generic/net.nosial.rsslib.ncc/$CI_COMMIT_REF_NAME/net.nosial.rsslib.ncc"
artifacts:
paths:
- build/
rules:
- if: $CI_COMMIT_TAG

8
.idea/.gitignore generated vendored Normal file
View file

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

11
.idea/RssLib.iml generated Normal file
View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View file

@ -0,0 +1,29 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="IncorrectHttpHeaderInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="customHeaders">
<set>
<option value="Subject" />
<option value="Reply-To" />
<option value="X-JSON-Schema" />
<option value="X-JSON-Type" />
<option value="X-JSON-Path" />
<option value="X-Java-Type" />
<option value="X-Region-Id" />
<option value="X-GraphQL-Variables" />
<option value="X-SSH-Private-Key" />
<option value="X-Temperature" />
<option value="X-Model" />
<option value="X-OPENAI-API-KEY" />
<option value="X-Args-0" />
<option value="X-Args-1" />
<option value="X-Args-2" />
<option value="X-Args-3" />
<option value="X-Args-4" />
<option value="X-Args-5" />
</set>
</option>
</inspection_tool>
</profile>
</component>

8
.idea/modules.xml generated Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/RssLib.iml" filepath="$PROJECT_DIR$/.idea/RssLib.iml" />
</modules>
</component>
</project>

20
.idea/php.xml generated Normal file
View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MessDetectorOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCSFixerOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCodeSnifferOptionsConfiguration">
<option name="highlightLevel" value="WARNING" />
<option name="transferred" value="true" />
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="8.2" />
<component name="PhpStanOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PsalmOptionsConfiguration">
<option name="transferred" value="true" />
</component>
</project>

10
.idea/runConfigurations/Build.xml generated Normal file
View file

@ -0,0 +1,10 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Build" type="MAKEFILE_TARGET_RUN_CONFIGURATION" factoryName="Makefile" activateToolWindowBeforeRun="false">
<makefile filename="$PROJECT_DIR$/Makefile" target="build" workingDirectory="" arguments="">
<envs />
</makefile>
<method v="2">
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Clean" run_configuration_type="MAKEFILE_TARGET_RUN_CONFIGURATION" />
</method>
</configuration>
</component>

8
.idea/runConfigurations/Clean.xml generated Normal file
View file

@ -0,0 +1,8 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Clean" type="MAKEFILE_TARGET_RUN_CONFIGURATION" factoryName="Makefile" activateToolWindowBeforeRun="false">
<makefile filename="$PROJECT_DIR$/Makefile" target="clean" workingDirectory="" arguments="">
<envs />
</makefile>
<method v="2" />
</configuration>
</component>

10
.idea/runConfigurations/Install.xml generated Normal file
View file

@ -0,0 +1,10 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Install" type="MAKEFILE_TARGET_RUN_CONFIGURATION" factoryName="Makefile" activateToolWindowBeforeRun="false">
<makefile filename="$PROJECT_DIR$/Makefile" target="install" workingDirectory="" arguments="">
<envs />
</makefile>
<method v="2">
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Build" run_configuration_type="MAKEFILE_TARGET_RUN_CONFIGURATION" />
</method>
</configuration>
</component>

6
.idea/vcs.xml generated Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

14
LICENSE Normal file
View file

@ -0,0 +1,14 @@
Copyright 2022-2023 Nosial All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

8
Makefile Normal file
View file

@ -0,0 +1,8 @@
build:
ncc build --config="release" --log-level debug
install:
sudo ncc package install --package="build/release/net.nosial.rsslib.ncc" --skip-dependencies --reinstall -y --log-level debug
clean:
rm -rf build

82
README.md Normal file
View file

@ -0,0 +1,82 @@
# RssLib
RssLib is a library used to parse RSS feeds and return the data in a usable format.
## Table of contents
<!-- TOC -->
* [RssLib](#rsslib)
* [Table of contents](#table-of-contents)
* [Installation](#installation)
* [Compile from source](#compile-from-source)
* [Requirements](#requirements)
* [Documentation](#documentation)
* [License](#license)
<!-- TOC -->
## Installation
The library can be installed using ncc:
```bash
ncc install -p "nosial/libs.rss=latest@n64"
```
or by adding the following to your project.json file under the `build.dependencies` section:
```json
{
"name": "net.nosial.rsslib",
"version": "latest",
"source_type": "remote",
"source": "nosial/libs.rsslib=latest@n64"
}
```
If you don't have the n64 source configured, you can add it by running the following command:
```bash
ncc source add --name n64 --type gitlab --host git.n64.cc
```
## Compile from source
To compile the library from source, you need to have [ncc](https://git.n64.cc/nosial/ncc) installed, then run the
following command:
```bash
ncc build
```
## Requirements
The library requires PHP 8.0 or higher.
## Documentation
The library is designed to be used as a dependency in other projects, and as such, does not have a command-line
interface. The library is designed to be used as follows:
```php
try
{
$feed = \RssLib\RssLib::getFeed('https://rss.nytimes.com/services/xml/rss/nyt/World.xml');
}
catch(\RssLib\Exceptions\RssFeedException $e)
{
echo $e->getMessage();
exit(1);
}
print("Title: {$feed->getTitle()}\n");
print("Description: {$feed->getDescription()}\n");
foreach($feed->getItems() as $item)
{
print("Item: {$item->getTitle()}\n");
print("Description: {$item->getDescription()}\n");
print("Link: {$item->getLink()}\n");
}
```
## License
Distributed under the MIT License. See `LICENSE` for more information.

29
project.json Normal file
View file

@ -0,0 +1,29 @@
{
"project": {
"compiler": {
"extension": "php",
"minimum_version": "8.0",
"maximum_version": "8.2"
},
"options": []
},
"assembly": {
"name": "RssLib",
"package": "net.nosial.rsslib",
"description": "A library used to parse RSS feeds",
"company": "Nosial",
"copyright": "Copyright (c) 2022-2023 Nosial",
"version": "1.0.0",
"uuid": "f24416e8-39f0-11ee-8dcf-f362baa8b68c"
},
"build": {
"source_path": "src",
"default_configuration": "release",
"configurations": [
{
"name": "release",
"output_path": "build/release"
}
]
}
}

View file

@ -0,0 +1,85 @@
<?php
namespace RssLib\Classes;
use InvalidArgumentException;
use SimpleXMLElement;
class Utilities
{
/**
* Attempts to parse a string into a timestamp
*
* @param string $input
* @return int
*/
public static function parseTimestamp(string $input): int
{
$timestamp = strtotime($input);
if($timestamp === false)
{
throw new InvalidArgumentException(sprintf('Invalid timestamp %s', $input));
}
return $timestamp;
}
/**
* Converts a SimpleXMLElement to an array representation
*
* @param SimpleXMLElement $element
* @return array
* @noinspection UnknownInspectionInspection
*/
public static function xmlToArray(SimpleXMLElement $element): array
{
$array = [];
if ($element->attributes())
{
foreach ($element->attributes() as $name => $value)
{
$array[$element->getName() . '_' . $name] = (string)$value;
}
if (trim((string)$element) !== '')
{
$array[$element->getName()] = trim((string)$element);
}
}
// Handle child elements
$items = [];
foreach ($element->children() as $child)
{
$value = ($child->count() > 0 || $child->attributes()) ? self::xmlToArray($child) : (string)$child;
if ($child->getName() === 'item')
{
$items[] = $value;
}
else if (isset($array[$child->getName()]))
{
if (!is_array($array[$child->getName()]))
{
$array[$child->getName()] = [$array[$child->getName()]];
}
/** @noinspection UnsupportedStringOffsetOperationsInspection */
$array[$child->getName()][] = $value;
}
else
{
$array[$child->getName()] = $value;
}
}
if (!empty($items))
{
$array['item'] = $items;
}
return $array;
}
}

View file

@ -0,0 +1,10 @@
<?php
namespace RssLib\Exceptions;
use Exception;
class RssFeedException extends Exception
{
// This class can be empty :)
}

View file

@ -0,0 +1,21 @@
<?php
namespace RssLib\Interfaces;
interface SerializableObjectInterface
{
/**
* Returns an array representation of the object
*
* @return array
*/
public function toArray(): array;
/**
* Constructs an object from an array representation
*
* @param array $array
* @return SerializableObjectInterface
*/
public static function fromArray(array $array): SerializableObjectInterface;
}

View file

@ -0,0 +1,400 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace RssLib\Objects;
use InvalidArgumentException;
use RssLib\Classes\Utilities;
use RssLib\Interfaces\SerializableObjectInterface;
use RssLib\Objects\RssChannel\Image;
use RssLib\Objects\RssChannel\TextInput;
class RssChannel implements SerializableObjectInterface
{
/**
* @var string
*/
private $title;
/**
* @var string
*/
private $link;
/**
* @var string
*/
private $description;
/**
* @var string|null
*/
private $language;
/**
* @var string|null
*/
private $copyright;
/**
* @var string|null
*/
private $managing_editor;
/**
* @var string|null
*/
private $web_master;
/**
* @var int|null
*/
private $publish_date;
/**
* @var int|null
*/
private $last_build_date;
/**
* @var string[]|null
*/
private $categories;
/**
* @var string|null
*/
private $generator;
/**
* @var string|null
*/
private $docs;
/**
* @var string|null
*/
private $cloud;
/**
* @var int|null
*/
private $ttl;
/**
* @var Image|null
*/
private $image;
/**
* @var TextInput|null
*/
private $text_input;
/**
* @var RssItem[]
*/
private $items;
/**
* @param array $data
*/
public function __construct(array $data)
{
foreach(['title', 'link', 'description'] as $key)
{
if(!array_key_exists($key, $data))
{
throw new InvalidArgumentException(sprintf('Missing required key %s for channel', $key));
}
}
$this->title = (string)$data['title'];
$this->link = (string)$data['link'];
$this->description = (string)$data['description'];
if(array_key_exists('language', $data))
{
$this->language = (string)$data['language'];
}
if(array_key_exists('copyright', $data))
{
$this->copyright = (string)$data['copyright'];
}
if(array_key_exists('managingEditor', $data))
{
$this->managing_editor = (string)$data['managingEditor'];
}
if(array_key_exists('webMaster', $data))
{
$this->web_master = (string)$data['webMaster'];
}
if(array_key_exists('pubDate', $data))
{
$this->publish_date = Utilities::parseTimestamp((string)$data['pubDate']);
}
if(array_key_exists('lastBuildDate', $data))
{
$this->last_build_date = Utilities::parseTimestamp((string)$data['lastBuildDate']);
}
if(array_key_exists('category', $data))
{
$this->categories = (array)$data['category'];
}
if(array_key_exists('generator', $data))
{
$this->generator = (string)$data['generator'];
}
if(array_key_exists('docs', $data))
{
$this->docs = (string)$data['docs'];
}
if(array_key_exists('cloud', $data))
{
$this->cloud = (string)$data['cloud'];
}
if(array_key_exists('ttl', $data))
{
$this->ttl = (int)$data['ttl'];
}
if(array_key_exists('image', $data))
{
$this->image = new Image($data['image']);
}
if(array_key_exists('textInput', $data))
{
$this->text_input = new TextInput($data['textInput']);
}
$this->items = [];
if(array_key_exists('item', $data))
{
foreach($data['item'] as $item)
{
$this->items[] = new RssItem($item);
}
}
}
/**
* The name of the channel. It's how people refer to your service. If you have an HTML website that contains
* the same information as your RSS file, the title of your channel should be the same as the title of your
* website.
*
* @return string
*/
public function getTitle(): string
{
return $this->title;
}
/**
* The URL to the HTML website corresponding to the channel.
*
* @return string
*/
public function getLink(): string
{
return $this->link;
}
/**
* Phrase or sentence describing the channel.
*
* @return string
*/
public function getDescription(): string
{
return $this->description;
}
/**
* The language the channel is written in
*
* @return string|null
*/
public function getLanguage(): ?string
{
return $this->language;
}
/**
* Copyright notice for content in the channel.
*
* @return string|null
*/
public function getCopyright(): ?string
{
return $this->copyright;
}
/**
* Email address for person responsible for editorial content.
*
* @return string|null
*/
public function getManagingEditor(): ?string
{
return $this->managing_editor;
}
/**
* Email address for person responsible for technical issues relating to channel.
*
* @return string|null
*/
public function getWebMaster(): ?string
{
return $this->web_master;
}
/**
* The publication date for the content in the channel
*
* @return int|null
*/
public function getPublishDate(): ?int
{
return $this->publish_date;
}
/**
* The last time the content of the channel changed.
*
* @return int|null
*/
public function getLastBuildDate(): ?int
{
return $this->last_build_date;
}
/**
* Specify one or more categories that the channel belongs to
*
* @return string[]|null
*/
public function getCategories(): ?array
{
return $this->categories;
}
/**
* A string indicating the program used to generate the channel.
*
* @return string|null
*/
public function getGenerator(): ?string
{
return $this->generator;
}
/**
* A URL that points to the documentation for the format used in the RSS file
*
* @return string|null
*/
public function getDocs(): ?string
{
return $this->docs;
}
/**
* Allows processes to register with a cloud to be notified of updates to the channel, implementing a
* lightweight publish-subscribe protocol for RSS feeds
*
* @return string|null
*/
public function getCloud(): ?string
{
return $this->cloud;
}
/**
* ttl stands for time to live. It's a number of minutes that indicates how long a channel can be cached before
* refreshing from the source
*
* @return int|null
*/
public function getTtl(): ?int
{
return $this->ttl;
}
/**
* Specifies a GIF, JPEG or PNG image that can be displayed with the channel
*
* @return Image|null
*/
public function getImage(): ?Image
{
return $this->image;
}
/**
* Specifies a text input box that can be displayed with the channel. More info
*
* @return TextInput|null
*/
public function getTextInput(): ?TextInput
{
return $this->text_input;
}
/**
* The items in the channel
*
* @return RssItem[]
*/
public function getItems(): array
{
return $this->items;
}
/**
* @inheritDoc
*/
public function toArray(): array
{
return [
'title' => $this->title,
'link' => $this->link,
'description' => $this->description,
'language' => $this->language,
'managingEditor' => $this->managing_editor,
'webMaster' => $this->web_master,
'pubDate' => $this->publish_date,
'lastBuildDate' => $this->last_build_date,
'category' => $this->categories,
'generator' => $this->generator,
'docs' => $this->docs,
'cloud' => $this->cloud,
'ttl' => $this->ttl,
'image' => ($this->image) ? $this->image->toArray() : null,
'textInput' => ($this->text_input) ? $this->text_input->toArray() : null,
'item' => array_map(static function(RssItem $item) {
return $item->toArray();
}, $this->items)
];
}
/**
* @inheritDoc
*/
public static function fromArray(array $array): SerializableObjectInterface
{
return new self($array);
}
}

View file

@ -0,0 +1,159 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace RssLib\Objects\RssChannel;
use InvalidArgumentException;
use RssLib\Interfaces\SerializableObjectInterface;
class Image implements SerializableObjectInterface
{
/**
* @var string
*/
private $url;
/**
* @var string
*/
private $title;
/**
* @var string
*/
private $link;
/**
* @var int|null
*/
private $width;
/**
* @var int|null
*/
private $height;
/**
* @var string|null
*/
private $description;
/**
* Public Constructor
*
* @param array $data
*/
public function __construct(array $data)
{
foreach(['url', 'title', 'link'] as $key)
{
if(!array_key_exists($key, $data))
{
throw new InvalidArgumentException(sprintf('Missing required key %s for image', $key));
}
}
$this->url = (string)$data['url'];
$this->title = (string)$data['title'];
$this->link = (string)$data['link'];
if(array_key_exists('width', $data))
{
$this->width = (int)$data['width'];
}
if(array_key_exists('height', $data))
{
$this->height = (int)$data['height'];
}
if(array_key_exists('description', $data))
{
$this->description = (string)$data['description'];
}
}
/**
* the URL of a GIF, JPEG or PNG image that represents the channel.
*
* @return string
*/
public function getUrl(): string
{
return $this->url;
}
/**
* describes the image, it's used in the ALT attribute of the HTML <img> tag when the channel is rendered in HTML.
*
* @return string
*/
public function getTitle(): string
{
return $this->title;
}
/**
* the URL of the site, when the channel is rendered, the image is a link to the site.
*
* @return string
*/
public function getLink(): string
{
return $this->link;
}
/**
* Optional. The width of the image in pixels
*
* @return int|null
*/
public function getWidth(): ?int
{
return $this->width;
}
/**
* Optional. The height of the image in pixels
*
* @return int|null
*/
public function getHeight(): ?int
{
return $this->height;
}
/**
* Optional. Specifies the text in the HTML TITLE attribute of the link around the image
*
* @return string|null
*/
public function getDescription(): ?string
{
return $this->description;
}
/**
* @inheritDoc
*/
public function toArray(): array
{
return [
'url' => $this->url,
'title' => $this->title,
'link' => $this->link,
'width' => $this->width,
'height' => $this->height,
'description' => $this->description
];
}
/**
* @inheritDoc
*/
public static function fromArray(array $array): Image
{
return new self($array);
}
}

View file

@ -0,0 +1,113 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace RssLib\Objects\RssChannel;
use InvalidArgumentException;
use RssLib\Interfaces\SerializableObjectInterface;
class TextInput implements SerializableObjectInterface
{
/**
* @var string
*/
private $title;
/**
* @var string
*/
private $description;
/**
* @var string
*/
private $name;
/**
* @var string
*/
private $link;
/**
* Public Constructor
*
* @param array $data
*/
public function __construct(array $data)
{
foreach(['title', 'description', 'name', 'link'] as $key)
{
if(!array_key_exists($key, $data))
{
throw new InvalidArgumentException(sprintf('Missing required key %s for text input', $key));
}
}
$this->title = (string)$data['title'];
$this->description = (string)$data['description'];
$this->name = (string)$data['name'];
$this->link = (string)$data['link'];
}
/**
* The label of the Submit button in the text input area.
*
* @return string
*/
public function getTitle(): string
{
return $this->title;
}
/**
* Explains the text input area.
*
* @return string
*/
public function getDescription(): string
{
return $this->description;
}
/**
* The name of the text object in the text input area.
*
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* The URL of the CGI script that processes text input requests.
*
* @return string
*/
public function getLink(): string
{
return $this->link;
}
/**
* @inheritDoc
*/
public function toArray(): array
{
return [
'title' => $this->title,
'description' => $this->description,
'name' => $this->name,
'link' => $this->link
];
}
/**
* @inheritDoc
*/
public static function fromArray(array $array): TextInput
{
return new self($array);
}
}

View file

@ -0,0 +1,96 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace RssLib\Objects\RssFeed;
use InvalidArgumentException;
use RssLib\Interfaces\SerializableObjectInterface;
class Enclosure implements SerializableObjectInterface
{
/**
* @var string
*/
private $url;
/**
* @var int
*/
private $length;
/**
* @var string
*/
private $type;
/**
* Public Constructor
*
* @param array $data
*/
public function __construct(array $data)
{
foreach(['enclosure_url', 'enclosure_length', 'enclosure_type'] as $key)
{
if(!array_key_exists($key, $data))
{
throw new InvalidArgumentException(sprintf('Missing required key %s for enclosure', $key));
}
}
$this->url = (string)$data['enclosure_url'];
$this->length = (int)$data['enclosure_length'];
$this->type = (string)$data['enclosure_type'];
}
/**
* Required. Defines the URL to the media file
*
* @return string
*/
public function getUrl(): string
{
return $this->url;
}
/**
* Required. Defines the length of the media file in bytes
*
* @return string
*/
public function getLength(): string
{
return $this->length;
}
/**
* Required. Defines the MIME type of the media file
*
* @return string
*/
public function getType(): string
{
return $this->type;
}
/**
* @inheritDoc
*/
public function toArray(): array
{
return [
'enclosure_url' => $this->url,
'enclosure_length' => $this->length,
'enclosure_type' => $this->type
];
}
/**
* @inheritDoc
*/
public static function fromArray(array $array): Enclosure
{
return new self($array);
}
}

View file

@ -0,0 +1,84 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace RssLib\Objects\RssFeed;
use InvalidArgumentException;
use RssLib\Interfaces\SerializableObjectInterface;
class Guid implements SerializableObjectInterface
{
/**
* @var string
*/
private $guid;
/**
* @var bool
*/
private $is_permalink;
/**
* Public Constructor
*
* @param array $data
*/
public function __construct(array $data)
{
if(!array_key_exists('guid', $data))
{
throw new InvalidArgumentException('Missing required key guid for guid');
}
$this->guid = (string)$data['guid'];
if(array_key_exists('guid_isPermalink', $data))
{
$this->is_permalink = (bool)$data['guid_isPermalink'];
}
else
{
$this->is_permalink = false;
}
}
/**
* Returns the guid
*
* @return string
*/
public function getGuid(): string
{
return $this->guid;
}
/**
* Returns True if the guid is a permalink, False otherwise
*
* @return bool
*/
public function isPermalink(): bool
{
return $this->is_permalink;
}
/**
* @inheritDoc
*/
public function toArray(): array
{
return [
'guid' => $this->guid,
'guid_isPermalink' => $this->is_permalink
];
}
/**
* @inheritDoc
*/
public static function fromArray(array $array): Guid
{
return new Guid($array);
}
}

View file

@ -0,0 +1,79 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace RssLib\Objects\RssFeed;
use InvalidArgumentException;
use RssLib\Interfaces\SerializableObjectInterface;
class Source implements SerializableObjectInterface
{
/**
* @var string
*/
private $url;
/**
* @var string
*/
private $source;
/**
* Public Constructor
*
* @param array $data
*/
public function __construct(array $data)
{
foreach(['source', 'source_url'] as $key)
{
if(!array_key_exists($key, $data))
{
throw new InvalidArgumentException(sprintf('Missing required key %s for source', $key));
}
}
$this->url = (string)$data['source_url'];
$this->source = (string)$data['source'];
}
/**
* Returns the url
*
* @return string
*/
public function getUrl(): string
{
return $this->url;
}
/**
* Returns the source
*
* @return string
*/
public function getSource(): string
{
return $this->source;
}
/**
* @inheritDoc
*/
public function toArray(): array
{
return [
'source_url' => $this->url,
'source' => $this->source
];
}
/**
* @inheritDoc
*/
public static function fromArray(array $array): Source
{
return new static($array);
}
}

View file

@ -0,0 +1,253 @@
<?php /** @noinspection PhpMissingFieldTypeInspection */
namespace RssLib\Objects;
use InvalidArgumentException;
use RssLib\Classes\Utilities;
use RssLib\Interfaces\SerializableObjectInterface;
use RssLib\Objects\RssFeed\Enclosure;
use RssLib\Objects\RssFeed\Guid;
use RssLib\Objects\RssFeed\Source;
class RssItem implements SerializableObjectInterface
{
/**
* @var string|null
*/
private $title;
/**
* @var string|null
*/
private $link;
/**
* @var string|null
*/
private $description;
/**
* @var string|null
*/
private $author;
/**
* @var string[]|null
*/
private $categories;
/**
* @var string|null
*/
private $comments;
/**
* @var Enclosure|null
*/
private $enclosure;
/**
* @var Guid|null
*/
private $guid;
/**
* @var int|null
*/
private $publish_date;
/**
* @var Source|null
*/
private $source;
/**
* RssFeed constructor.
*
* @param array $data
* @return void
*/
public function __construct(array $data)
{
foreach(['title', 'link', 'description'] as $key)
{
if(!array_key_exists($key, $data))
{
throw new InvalidArgumentException(sprintf('Missing required key %s for RssFeed', $key));
}
}
$this->title = (string)$data['title'];
$this->link = (string)$data['link'];
$this->description = (string)$data['description'];
if(array_key_exists('author', $data))
{
$this->author = (string)$data['author'];
}
if(array_key_exists('categories', $data))
{
if(is_string($data['categories']))
{
$this->categories = [$data['categories']];
}
else
{
$this->categories = (array)$data['categories'];
}
}
if(array_key_exists('comments', $data))
{
$this->comments = (string)$data['comments'];
}
if(array_key_exists('enclosure', $data))
{
$this->enclosure = new Enclosure($data['enclosure']);
}
if(array_key_exists('guid', $data))
{
$this->guid = new Guid($data['guid']);
}
if(array_key_exists('pubDate', $data))
{
$this->publish_date = Utilities::parseTimestamp($data['pubDate']);
}
if(array_key_exists('source', $data))
{
$this->source = new Source($data['source']);
}
}
/**
* Returns the title of the item.
*
* @return string|null
*/
public function getTitle(): ?string
{
return $this->title;
}
/**
* Returns the link of the item.
*
* @return string|null
*/
public function getLink(): ?string
{
return $this->link;
}
/**
* Returns the description of the item.
*
* @return string|null
*/
public function getDescription(): ?string
{
return $this->description;
}
/**
* Returns the author of the item.
*
* @return string|null
*/
public function getAuthor(): ?string
{
return $this->author;
}
/**
* Returns the categories of the item.
*
* @return string[]|null
*/
public function getCategories(): array|null
{
return $this->categories;
}
/**
* Returns the comments of the item.
*
* @return string|null
*/
public function getComments(): ?string
{
return $this->comments;
}
/**
* Returns the enclosure of the item.
*
* @return Enclosure|null
*/
public function getEnclosure(): ?Enclosure
{
return $this->enclosure;
}
/**
* Returns the GUID of the item.
*
* @return Guid|null
*/
public function getGuid(): ?Guid
{
return $this->guid;
}
/**
* Returns the publication date of the item.
*
* @return int|null
*/
public function getPublishDate(): ?int
{
return $this->publish_date;
}
/**
* Returns the source of the item.
*
* @return Source|null
*/
public function getSource(): ?Source
{
return $this->source;
}
/**
* @inheritDoc
*/
public function toArray(): array
{
return [
'title' => $this->title,
'link' => $this->link,
'description' => $this->description,
'author' => $this->author,
'categories' => $this->categories,
'comments' => $this->comments,
'enclosure' => $this->enclosure?->toArray(),
'guid' => $this->guid?->toArray(),
'pubDate' => $this->publish_date,
'source' => $this->source?->toArray(),
];
}
/**
* @inheritDoc
*/
public static function fromArray(array $array): SerializableObjectInterface
{
return new self($array);
}
}

70
src/RssLib/RssLib.php Normal file
View file

@ -0,0 +1,70 @@
<?php
namespace RssLib;
use Exception;
use RssLib\Classes\Utilities;
use RssLib\Exceptions\RssFeedException;
use RssLib\Objects\RssChannel;
class RssLib
{
/**
* @param string $url
* @param int $timeout
* @return RssChannel
* @throws RssFeedException
*/
public static function getFeed(string $url, int $timeout=120): RssChannel
{
return self::parseRssContent(self::fetchRssContent($url, $timeout));
}
/**
* Fetches the RSS feed from the given URL and returns the content.
*
* @param string $url
* @param int $timeout
* @return string
* @throws RssFeedException
*/
public static function fetchRssContent(string $url, int $timeout=120): string
{
try
{
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
$content = curl_exec($ch);
if(curl_errno($ch))
{
throw new RssFeedException(sprintf('Failed to fetch RSS feed from %s: %s', $url, curl_error($ch)));
}
}
catch(Exception $e)
{
throw new RssFeedException(sprintf('Failed to fetch RSS feed from %s', $url), 0, $e);
}
finally
{
curl_close($ch);
unset($ch);
}
return $content;
}
/**
* @param string $content
* @return RssChannel
*/
public static function parseRssContent(string $content): RssChannel
{
return new RssChannel(Utilities::xmlToArray(simplexml_load_string($content))['channel']);
}
}

8
tests/parse_test.php Normal file
View file

@ -0,0 +1,8 @@
<?php
require 'ncc';
import('net.nosial.rsslib');
$feed = \RssLib\RssLib::getFeed('https://rss.nytimes.com/services/xml/rss/nyt/World.xml');
var_dump($feed);