Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • adaures/castopod
  • mkljczk/castopod-host
  • spaetz/castopod-host
  • PatrykMis/castopod
  • jonas/castopod
  • ajeremias/castopod
  • misuzu/castopod
  • KrzysztofDomanczyk/castopod
  • Behel/castopod
  • nebulon/castopod
  • ewen/castopod
  • NeoluxConsulting/castopod
  • nateritter/castopod-og
  • prcutler/castopod
14 results
Select Git revision
Show changes
Showing
with 564 additions and 977 deletions
<?php
/**
* @copyright 2021 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace ActivityPub\Models;
use CodeIgniter\Database\Exceptions\DataException;
use stdClass;
class UuidModel extends \Michalsn\Uuid\UuidModel
{
/**
* This insert overwrite is added as a means to FIX some bugs
* from the extended Uuid package. See: https://github.com/michalsn/codeigniter4-uuid/issues/2
*
* Inserts data into the current table. If an object is provided,
* it will attempt to convert it to an array.
*
* @param array|object $data
* @param boolean $returnID Whether insert ID should be returned or not.
*
* @return BaseResult|integer|string|false
* @throws \ReflectionException
*/
public function insert($data = null, bool $returnID = true)
{
$escape = null;
$this->insertID = 0;
if (empty($data)) {
$data = $this->tempData['data'] ?? null;
$escape = $this->tempData['escape'] ?? null;
$this->tempData = [];
}
if (empty($data)) {
throw DataException::forEmptyDataset('insert');
}
// If $data is using a custom class with public or protected
// properties representing the table elements, we need to grab
// them as an array.
if (is_object($data) && !$data instanceof stdClass) {
$data = static::classToArray(
$data,
$this->primaryKey,
$this->dateFormat,
false,
);
}
// If it's still a stdClass, go ahead and convert to
// an array so doProtectFields and other model methods
// don't have to do special checks.
if (is_object($data)) {
$data = (array) $data;
}
if (empty($data)) {
throw DataException::forEmptyDataset('insert');
}
// Validate data before saving.
if ($this->skipValidation === false) {
if ($this->cleanRules()->validate($data) === false) {
return false;
}
}
// Must be called first so we don't
// strip out created_at values.
$data = $this->doProtectFields($data);
// Set created_at and updated_at with same time
$date = $this->setDate();
if (
$this->useTimestamps &&
!empty($this->createdField) &&
!array_key_exists($this->createdField, $data)
) {
$data[$this->createdField] = $date;
}
if (
$this->useTimestamps &&
!empty($this->updatedField) &&
!array_key_exists($this->updatedField, $data)
) {
$data[$this->updatedField] = $date;
}
$eventData = ['data' => $data];
if ($this->tempAllowCallbacks) {
$eventData = $this->trigger('beforeInsert', $eventData);
}
// Require non empty primaryKey when
// not using auto-increment feature
if (
!$this->useAutoIncrement &&
empty($eventData['data'][$this->primaryKey])
) {
throw DataException::forEmptyPrimaryKey('insert');
}
if (!empty($this->uuidFields)) {
foreach ($this->uuidFields as $field) {
if ($field === $this->primaryKey) {
$this->uuidTempData[
$field
] = $this->uuid->{$this->uuidVersion}();
if ($this->uuidUseBytes === true) {
$this->builder()->set(
$field,
$this->uuidTempData[$field]->getBytes(),
);
} else {
$this->builder()->set(
$field,
$this->uuidTempData[$field]->toString(),
);
}
} else {
if (
$this->uuidUseBytes === true &&
!empty($eventData['data'][$field])
) {
$this->uuidTempData[$field] = $this->uuid->fromString(
$eventData['data'][$field],
);
$this->builder()->set(
$field,
$this->uuidTempData[$field]->getBytes(),
);
unset($eventData['data'][$field]);
}
}
}
}
// Must use the set() method to ensure objects get converted to arrays
$result = $this->builder()
->set($eventData['data'], '', $escape)
->insert();
// If insertion succeeded then save the insert ID
if ($result) {
if (
!$this->useAutoIncrement ||
isset($eventData['data'][$this->primaryKey])
) {
$this->insertID = $eventData['data'][$this->primaryKey];
} else {
if (in_array($this->primaryKey, $this->uuidFields)) {
$this->insertID = $this->uuidTempData[
$this->primaryKey
]->toString();
} else {
$this->insertID = $this->db->insertID();
}
}
}
// Cleanup data before event trigger
if (!empty($this->uuidFields) && $this->uuidUseBytes === true) {
foreach ($this->uuidFields as $field) {
if (
$field === $this->primaryKey ||
empty($this->uuidTempData[$field])
) {
continue;
}
$eventData['data'][$field] = $this->uuidTempData[
$field
]->toString();
}
}
$eventData = [
'id' => $this->insertID,
'data' => $eventData['data'],
'result' => $result,
];
if ($this->tempAllowCallbacks) {
// Trigger afterInsert events with the inserted data and new ID
$this->trigger('afterInsert', $eventData);
}
$this->tempAllowCallbacks = $this->allowCallbacks;
// If insertion failed, get out of here
if (!$result) {
return $result;
}
// otherwise return the insertID, if requested.
return $returnID ? $this->insertID : $result;
}
}
<?php
/**
* @copyright 2021 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace ActivityPub\Objects;
use ActivityPub\Core\ObjectType;
class ActorObject extends ObjectType
{
/**
* @var array|string
*/
protected $context = [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
];
/**
* @var string
*/
protected $type = 'Person';
/**
* @var string
*/
protected $name;
/**
* @var string
*/
protected $preferredUsername;
/**
* @var string
*/
protected $summary;
/**
* @var string
*/
protected $inbox;
/**
* @var string
*/
protected $outbox;
/**
* @var string
*/
protected $followers;
/**
* @var string
*/
protected $url;
/**
* @var array|null
*/
protected $image;
/**
* @var array
*/
protected $icon;
/**
* @var object
*/
protected $publicKey;
/**
* @param \ActivityPub\Entities\Actor $podcast
*/
public function __construct($actor)
{
$this->id = $actor->uri;
$this->name = $actor->display_name;
$this->preferredUsername = $actor->username;
$this->summary = $actor->summary;
$this->url = $actor->uri;
$this->inbox = $actor->inbox_url;
$this->outbox = $actor->outbox_url;
$this->followers = $actor->followers_url;
if ($actor->cover_image_url) {
$this->image = [
'type' => 'Image',
'mediaType' => $actor->cover_image_mimetype,
'url' => $actor->cover_image_url,
];
}
$this->icon = [
'type' => 'Image',
'mediaType' => $actor->avatar_image_mimetype,
'url' => $actor->avatar_image_url,
];
$this->publicKey = [
'id' => $actor->key_id,
'owner' => $actor->uri,
'publicKeyPem' => $actor->public_key,
];
}
}
<?php
/**
* This class defines the Object which is the
* primary base type for the Activity Streams vocabulary.
*
* Object is a reserved word in php, so the class is named ObjectType.
*
* @copyright 2021 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace ActivityPub\Objects;
use ActivityPub\Core\ObjectType;
class NoteObject extends ObjectType
{
/**
* @var string
*/
protected $type = 'Note';
/**
* @var string
*/
protected $attributedTo;
/**
* @var string
*/
protected $inReplyTo;
/**
* @var array
*/
protected $replies;
/**
* @param \ActivityPub\Entities\Note $note
*/
public function __construct($note)
{
$this->id = $note->uri;
$this->content = $note->message_html;
$this->published = $note->published_at->format(DATE_W3C);
$this->attributedTo = $note->actor->uri;
if ($note->is_reply) {
$this->inReplyTo = $note->reply_to_note->uri;
}
$this->replies = base_url(
route_to('note-replies', $note->actor->username, $note->id),
);
$this->cc = [$note->actor->followers_url];
}
}
<?php
/**
* This class defines a Paginated OrderedCollection
* based on CodeIgniter4 Pager to get the pagination metadata
*
* @copyright 2021 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace ActivityPub\Objects;
use ActivityPub\Core\ObjectType;
class OrderedCollectionObject extends ObjectType
{
protected $type = 'OrderedCollection';
/**
* @var integer
*/
protected $totalItems;
/**
* @var integer|null
*/
protected $first;
/**
* @var integer|null
*/
protected $current;
/**
* @var integer|null
*/
protected $last;
/**
* @var array|null
*/
protected $orderedItems;
/**
* @param \ActivityPub\Libraries\ActivityPub\Activity[] $orderedItems
* @param \CodeIgniter\Pager\Pager $pager
*/
public function __construct($orderedItems, $pager = null)
{
$this->id = current_url();
if ($pager) {
$totalItems = $pager->getTotal();
$this->totalItems = $totalItems;
if ($totalItems) {
$this->first = $pager->getPageURI($pager->getFirstPage());
$this->current = $pager->getPageURI();
$this->last = $pager->getPageURI($pager->getLastPage());
}
}
$this->orderedItems = $orderedItems;
}
}
<?php
/**
* This class defines a Paginated OrderedCollection
* based on CodeIgniter4 Pager to get the pagination metadata
*
* @copyright 2021 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace ActivityPub\Objects;
class OrderedCollectionPage extends OrderedCollectionObject
{
/**
* @var string
*/
protected $type = 'OrderedCollectionPage';
/**
* @var string
*/
protected $partOf;
/**
* @var integer
*/
protected $prev;
/**
* @var integer
*/
protected $next;
/**
* @param \CodeIgniter\Pager\Pager $pager
* @param \ActivityPub\Libraries\ActivityPub\Activity[] $orderedItems
*/
public function __construct($pager, $orderedItems)
{
parent::__construct($orderedItems, $pager);
$isFirstPage = $pager->getCurrentPage() === $pager->getFirstPage();
$isLastPage = $pager->getCurrentPage() === $pager->getLastPage();
$isFirstPage && ($this->first = null);
$isLastPage && ($this->last = null);
$this->id = $pager->getPageURI($pager->getCurrentPage());
$this->partOf = $pager->getPageURI();
$this->prev = $pager->getPreviousPageURI();
$this->current = $pager->getPageURI($pager->getCurrentPage());
$this->next = $pager->getNextPageURI();
}
}
<?php
/**
* @copyright 2021 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace ActivityPub\Objects;
use ActivityPub\Core\ObjectType;
class TombstoneObject extends ObjectType
{
/**
* @var string
*/
protected $type = 'Tombstone';
}
<?php
/**
* @copyright 2021 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace ActivityPub;
use Exception;
class WebFinger
{
const RESOURCE_PATTERN = '/^acct:(?P<username>([\w_]+))@(?P<domain>([\w\-\.]+[\w]+)(:[\d]+)?)$/x';
/**
* @var string
*/
protected $username;
/**
* @var string
*/
protected $host;
/**
* @var string
*/
protected $port;
/**
* @var string
*/
protected $subject;
/**
* @var array
*/
protected $aliases;
/**
* @var string
*/
protected $links;
/**
* @param string $resource
*/
public function __construct($resource)
{
$this->subject = $resource;
// Split resource into its parts (username, domain)
$parts = $this->splitResource($resource);
if (!$parts) {
throw new Exception('Wrong WebFinger resource pattern.');
}
extract($parts);
$this->username = $username;
$this->domain = $domain;
$currentUrl = current_url(true);
$currentDomain =
$currentUrl->getHost() .
($currentUrl->getPort() ? ':' . $currentUrl->getPort() : '');
if ($currentDomain !== $domain) {
// TODO: return error code
throw new Exception('Domain does not correspond to Instance.');
}
if (
!($actor = model('ActorModel')->getActorByUsername(
$username,
$domain,
))
) {
throw new Exception('Could not find actor');
}
$this->aliases = [$actor->id];
$this->links = [
[
'rel' => 'self',
'type' => 'application/activity+json',
'href' => $actor->uri,
],
[
'rel' => 'http://webfinger.net/rel/profile-page',
'type' => 'text/html',
'href' => $actor->uri, # TODO: should there be 2 values? @actorUsername
],
];
}
/**
* Split resource into its parts (username, domain)
*
* @param string $resource
* @return bool|array
*/
private function splitResource(string $resource)
{
if (!preg_match(self::RESOURCE_PATTERN, $resource, $matches)) {
// Resource pattern failed
return false;
}
return $matches;
}
/**
* Get WebFinger response as an array
*
* @return array
*/
public function toArray()
{
return [
'subject' => $this->subject,
'aliases' => $this->aliases,
'links' => $this->links,
];
}
}
<?php
declare(strict_types=1);
/**
* Generates and renders a breadcrumb based on the current url segments
*
* @copyright 2021 Podlibre
* @copyright 2021 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
......@@ -15,72 +17,67 @@ class Breadcrumb
/**
* List of breadcrumb links.
*
* @var array
* $links = [
* 'text' => (string) the anchor text,
* 'href' => (string) the anchor href,
* ]
* $links = [ 'text' => 'Example Link', 'href' => 'https://example.com/', ]
*
* @var array<array<string, string>>
*/
protected $links = [];
protected array $links = [];
/**
* Initializes the Breadcrumb object using the segments from
* current_url by populating the $links property with text and href data
* Initializes the Breadcrumb object using the segments from current_url by populating the $links property with text
* and href data
*/
public function __construct()
{
$uri = '';
foreach (current_url(true)->getSegments() as $segment) {
$uri .= '/' . $segment;
array_push($this->links, [
$link = [
'text' => is_numeric($segment)
? $segment
: lang('Breadcrumb.' . $segment),
'href' => base_url($uri),
]);
];
if (is_numeric($segment)) {
$this->links[] = $link;
} else {
$this->links[$segment] = $link;
}
}
}
/**
* Replaces all numeric text in breadcrumb's $link property
* with new params at same position
* Replaces all numeric text in breadcrumb's $link property with new params at same position
*
* Given a breadcrumb with numeric params, this function
* replaces them with the values provided in $newParams
* Given a breadcrumb with numeric params, this function replaces them with the values provided in $newParams
*
* Example with `Home / podcasts / 1 / episodes / 1`
* Example with `Home / podcasts / 1 / episodes / 1 / foo`
*
* $newParams = [
* 0 => 'foo',
* 1 => 'bar'
* ]
* replaceParams($newParams);
* $newParams = [ 0 => 'bar', 1 => 'baz', 'foo' => 'I Pity The Foo' ] replaceParams($newParams);
*
* The breadcrumb is now `Home / podcasts / foo / episodes / bar`
* The breadcrumb is now `Home / podcasts / foo / episodes / bar / I Pity The Foo`
*
* @param array $newParams
* @param array<string|int,string> $newParams
*/
public function replaceParams($newParams)
public function replaceParams(array $newParams): void
{
foreach ($this->links as $key => $link) {
if (is_numeric($link['text'])) {
$this->links[$key]['text'] = $newParams[0];
array_shift($newParams);
foreach ($newParams as $key => $newValue) {
if (array_key_exists($key, $this->links)) {
$this->links[$key]['text'] = $newValue;
}
}
}
/**
* Renders the breadcrumb object as an accessible html breadcrumb nav
*
* @return string
*/
public function render($class = null)
public function render(?string $class = null): string
{
$listItems = '';
$keys = array_keys($this->links);
foreach ($this->links as $key => $link) {
if (end($keys) == $key) {
if (end($keys) === $key) {
$listItem =
'<li class="breadcrumb-item active" aria-current="page">' .
$link['text'] .
......@@ -97,9 +94,7 @@ class Breadcrumb
return '<nav aria-label="' .
lang('Breadcrumb.label') .
'"><ol class="breadcrumb ' .
$class .
'">' .
'" class="' . $class . '"><ol class="breadcrumb">' .
$listItems .
'</ol></nav>';
}
......
<?php
declare(strict_types=1);
/**
* @copyright 2021 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Libraries;
use App\Entities\EpisodeComment;
use Modules\Fediverse\Core\ObjectType;
class CommentObject extends ObjectType
{
protected string $type = 'Note';
protected string $attributedTo;
protected string $inReplyTo;
protected string $replies;
public function __construct(EpisodeComment $comment)
{
$this->id = $comment->uri;
$this->content = $comment->message_html;
$this->published = $comment->created_at->format(DATE_W3C);
$this->attributedTo = $comment->actor->uri;
if ($comment->in_reply_to_id !== null) {
$this->inReplyTo = $comment->reply_to_comment->uri;
}
$this->replies = url_to(
'episode-comment-replies',
esc($comment->actor->username),
$comment->episode->slug,
$comment->id,
);
$this->cc = [$comment->actor->followers_url];
}
}
<?php
declare(strict_types=1);
namespace App\Libraries;
use App\Controllers\WebmanifestController;
use Override;
use Stringable;
/**
* Inspired by https://github.com/melbahja/seo
*/
class HtmlHead implements Stringable
{
protected ?string $title = null;
/**
* @var array{name:string,value:string|null,attributes:array<string,string|null>}[]
*/
protected array $tags = [];
protected string $rawContent = '';
#[Override]
public function __toString(): string
{
helper('misc');
$this
->tag('meta', null, [
'charset' => 'UTF-8',
])
->meta('viewport', 'width=device-width, initial-scale=1.0')
->tag('link', null, [
'rel' => 'icon',
'type' => 'image/x-icon',
'href' => get_site_icon_url('ico'),
])
->tag('link', null, [
'rel' => 'apple-touch-icon',
'href' => get_site_icon_url('180'),
])
->tag('link', null, [
'rel' => 'manifest',
// @phpstan-ignore-next-line
'href' => isset($podcast) ? route_to('podcast-webmanifest', esc($podcast->handle)) : route_to(
'webmanifest',
),
])
->meta(
'theme-color',
WebmanifestController::THEME_COLORS[service('settings')->get('App.theme')]['theme'],
)
->tag('link', null, [
'rel' => 'stylesheet',
'type' => 'text/css',
'href' => route_to('themes-colors-css'),
])
->appendRawContent(<<<HTML
<script>
// Check that service workers are supported
if ('serviceWorker' in navigator) {
// Use the window load event to keep the page load performant
window.addEventListener('load', () => {
navigator.serviceWorker.register('/assets/sw.js');
});
}
</script>
HTML);
if ($this->title) {
$this->tag('title', esc($this->title));
}
if (url_is(route_to('admin') . '*') || url_is(base_url(config('Auth')->gateway) . '*')) {
// restricted admin and auth areas, do not index
$this->meta('robots', 'noindex');
} else {
// public website, set siteHead hook only there
service('plugins')
->siteHead($this);
}
$head = '<head>';
foreach ($this->tags as $tag) {
if ($tag['value'] === null) {
$head .= <<<HTML
<{$tag['name']}{$this->stringify_attributes($tag['attributes'])}/>
HTML;
} else {
$head .= <<<HTML
<{$tag['name']} {$this->stringify_attributes($tag['attributes'])}>{$tag['value']}</{$tag['name']}>
HTML;
}
}
$head .= $this->rawContent . '</head>';
// reset head for next render
$this->title = null;
$this->tags = [];
$this->rawContent = '';
return $head;
}
public function title(string $title): self
{
$this->title = $title;
return $this->meta('title', $title)
->og('title', $title)
->twitter('title', $title);
}
public function description(string $desc): self
{
return $this->meta('description', $desc)
->og('description', $desc)
->twitter('description', $desc);
}
public function image(string $url, string $card = 'summary_large_image'): self
{
return $this->og('image', $url)
->twitter('card', $card)
->twitter('image', $url);
}
public function canonical(string $url): self
{
return $this->tag('link', null, [
'rel' => 'canonical',
'href' => $url,
]);
}
public function twitter(string $name, string $value): self
{
$this->meta("twitter:{$name}", $value);
return $this;
}
/**
* @param array<string,string|null> $attributes
*/
public function tag(string $name, ?string $value = null, array $attributes = []): self
{
$this->tags[] = [
'name' => $name,
'value' => $value,
'attributes' => $attributes,
];
return $this;
}
public function meta(string $name, string $content): self
{
$this->tag('meta', null, [
'name' => $name,
'content' => $content,
]);
return $this;
}
public function og(string $name, string $content): self
{
$this->meta('og:' . $name, $content);
return $this;
}
public function appendRawContent(string $content): self
{
$this->rawContent .= $content;
return $this;
}
/**
* @param array<string, string|null> $attributes
*/
private function stringify_attributes(array $attributes): string
{
return stringify_attributes($attributes);
}
}
<?php
/**
* @copyright 2021 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Libraries;
class Image
{
/**
* @var \Config\Images
*/
protected $config;
/**
* @var string
*/
protected $original_path;
/**
* @var string
*/
public $original_url;
/**
* @var string
*/
protected $thumbnail_path;
/**
* @var string
*/
public $thumbnail_url;
/**
* @var string
*/
protected $medium_path;
/**
* @var string
*/
public $medium_url;
/**
* @var string
*/
protected $large_path;
/**
* @var string
*/
public $large_url;
/**
* @var string
*/
public $feed_path;
/**
* @var string
*/
public $feed_url;
/**
* @var string
*/
public $id3_path;
public function __construct($originalUri, $mimetype)
{
helper('media');
$originalPath = media_path($originalUri);
[
'filename' => $filename,
'dirname' => $dirname,
'extension' => $extension,
] = pathinfo($originalPath);
// load images extensions from config
$this->config = config('Images');
$thumbnailExtension = $this->config->thumbnailExtension;
$mediumExtension = $this->config->mediumExtension;
$largeExtension = $this->config->largeExtension;
$feedExtension = $this->config->feedExtension;
$id3Extension = $this->config->id3Extension;
$thumbnail =
$dirname . '/' . $filename . $thumbnailExtension . '.' . $extension;
$medium =
$dirname . '/' . $filename . $mediumExtension . '.' . $extension;
$large =
$dirname . '/' . $filename . $largeExtension . '.' . $extension;
$feed = $dirname . '/' . $filename . $feedExtension . '.' . $extension;
$id3 = $dirname . '/' . $filename . $id3Extension . '.' . $extension;
$this->original_path = $originalPath;
$this->original_url = media_url($originalUri);
$this->thumbnail_path = $thumbnail;
$this->thumbnail_url = base_url($thumbnail);
$this->medium_path = $medium;
$this->medium_url = base_url($medium);
$this->large_path = $large;
$this->large_url = base_url($large);
$this->feed_path = $feed;
$this->feed_url = base_url($feed);
$this->id3_path = $id3;
$this->mimetype = $mimetype;
}
public function saveSizes()
{
$thumbnailSize = $this->config->thumbnailSize;
$mediumSize = $this->config->mediumSize;
$largeSize = $this->config->largeSize;
$feedSize = $this->config->feedSize;
$id3Size = $this->config->id3Size;
$imageService = \Config\Services::image();
$imageService
->withFile($this->original_path)
->resize($thumbnailSize, $thumbnailSize)
->save($this->thumbnail_path);
$imageService
->withFile($this->original_path)
->resize($mediumSize, $mediumSize)
->save($this->medium_path);
$imageService
->withFile($this->original_path)
->resize($largeSize, $largeSize)
->save($this->large_path);
$imageService
->withFile($this->original_path)
->resize($feedSize, $feedSize)
->save($this->feed_path);
$imageService
->withFile($this->original_path)
->resize($id3Size, $id3Size)
->save($this->id3_path);
}
}
<?php
declare(strict_types=1);
namespace App\Libraries;
class Negotiate extends \CodeIgniter\HTTP\Negotiate
use CodeIgniter\HTTP\Negotiate as CodeIgniterHTTPNegotiate;
class Negotiate extends CodeIgniterHTTPNegotiate
{
public function callMatch(
array $acceptable,
string $supported,
bool $enforceTypes = false
): bool {
/**
* @param mixed[] $acceptable
*/
public function callMatch(array $acceptable, string $supported, bool $enforceTypes = false): bool
{
return $this->match($acceptable, $supported, $enforceTypes);
}
}
<?php
declare(strict_types=1);
/**
* @copyright 2021 Podlibre
* @copyright 2021 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Libraries;
class NoteObject extends \ActivityPub\Objects\NoteObject
use App\Entities\Post;
use Modules\Fediverse\Objects\NoteObject as FediverseNoteObject;
class NoteObject extends FediverseNoteObject
{
/**
* @param \App\Entities\Note $note
* @param Post $post
*/
public function __construct($note)
public function __construct($post)
{
parent::__construct($note);
parent::__construct($post);
if ($note->episode_id) {
if ($post->episode_id) {
$this->content =
'<a href="' .
$note->episode->link .
'" target="_blank" rel="noopener noreferrer">' .
$note->episode->title .
'</a><br/>' .
$note->message_html;
'<a href="' . $post->episode->link . '">' . $post->episode->title . '</a><br/>' . $post->message_html;
}
}
}
<?php
declare(strict_types=1);
/**
* @copyright 2021 Podlibre
* @copyright 2021 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Libraries;
use App\Models\PodcastModel;
use App\Entities\Podcast;
use Modules\Fediverse\Objects\ActorObject;
class PodcastActor extends \ActivityPub\Objects\ActorObject
class PodcastActor extends ActorObject
{
/**
* @var string
*/
protected $rss;
/**
* @param \App\Entities\Actor $actor
*/
public function __construct($actor)
protected string $rssFeed;
protected string $language;
protected string $category;
protected string $episodes;
public function __construct(Podcast $podcast)
{
parent::__construct($actor);
parent::__construct($podcast->actor);
$this->context[] = 'https://github.com/Podcastindex-org/activitypub-spec-work/blob/main/docs/1.0.md';
$this->type = 'Podcast';
$this->rssFeed = $podcast->feed_url;
$this->language = $podcast->language_code;
$category = '';
if ($podcast->category->parent_id !== null) {
$category .= $podcast->category->parent->apple_category . ' › ';
}
$category .= $podcast->category->apple_category;
$podcast = (new PodcastModel())->where('actor_id', $actor->id)->first();
$this->category = $category;
$this->rss = $podcast->feed_url;
$this->episodes = url_to('podcast-episodes', esc($podcast->handle));
}
}
<?php
declare(strict_types=1);
/**
* @copyright 2021 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Libraries;
use App\Entities\Actor;
use App\Entities\Episode;
use CodeIgniter\I18n\Time;
use Modules\Fediverse\Core\ObjectType;
use Modules\Media\Entities\Chapters;
use Modules\Media\Entities\Transcript;
class PodcastEpisode extends ObjectType
{
protected string $type = 'PodcastEpisode';
protected string $attributedTo;
protected string $comments;
/**
* @var array<mixed>
*/
protected array $description = [];
/**
* @var array<string, string>
*/
protected array $image = [];
/**
* @var array<mixed>
*/
protected array $audio = [];
public function __construct(Episode $episode)
{
// TODO: clean things up with specified spec
$this->id = $episode->link;
$this->description = [
'type' => 'Note',
'mediaType' => 'text/markdown',
'content' => $episode->description_markdown,
'contentMap' => [
$episode->podcast->language_code => $episode->description_html,
],
];
$this->image = [
'type' => 'Image',
'mediaType' => $episode->cover->file_mimetype,
'url' => $episode->cover->feed_url,
];
// add audio file
$this->audio = [
'id' => $episode->audio_url,
'type' => 'Audio',
'name' => esc($episode->title),
'size' => $episode->audio->file_size,
'duration' => $episode->audio->duration,
'url' => [
'href' => $episode->audio_url,
'type' => 'Link',
'mediaType' => $episode->audio->file_mimetype,
],
];
if ($episode->transcript instanceof Transcript) {
$this->audio['transcript'] = $episode->transcript->file_url;
}
if ($episode->chapters instanceof Chapters) {
$this->audio['chapters'] = $episode->chapters->file_url;
}
$this->comments = url_to('episode-comments', $episode->podcast->handle, $episode->slug);
if ($episode->published_at instanceof Time) {
$this->published = $episode->published_at->format(DATE_W3C);
}
if ($episode->podcast->actor instanceof Actor) {
$this->attributedTo = $episode->podcast->actor->uri;
if ($episode->podcast->actor->followers_url) {
$this->cc = [$episode->podcast->actor->followers_url];
}
}
}
}
<?php
declare(strict_types=1);
/**
* This file extends the Router class from the CodeIgniter 4 framework.
*
* It introduces the alternate-content option for a route.
*
* @copyright 2021 Podlibre
* @copyright 2021 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Libraries;
use Config\Services;
use CodeIgniter\Router\Exceptions\RedirectException;
use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\HTTP\Exceptions\RedirectException;
use CodeIgniter\Router\Exceptions\RouterException;
use CodeIgniter\Router\Router as CodeIgniterRouter;
use Config\Routing;
use Override;
class Router extends \CodeIgniter\Router\Router
class Router extends CodeIgniterRouter
{
/**
* Compares the uri string against the routes that the
* RouteCollection class defined for us, attempting to find a match.
* This method will modify $this->controller, etal as needed.
* Compares the uri string against the routes that the RouteCollection class defined for us, attempting to find a
* match. This method will modify $this->controller, etal as needed.
*
* @param string $uri The URI path to compare against the routes
*
* @return boolean Whether the route was matched or not.
* @throws RedirectException
*/
#[Override]
protected function checkRoutes(string $uri): bool
{
$routes = $this->collection->getRoutes(
$this->collection->getHTTPVerb(),
);
$routes = $this->collection->getRoutes($this->collection->getHTTPVerb());
// Don't waste any time
if (empty($routes)) {
if ($routes === []) {
return false;
}
$uri = $uri === '/' ? $uri : ltrim($uri, '/ ');
$uri = $uri === '/' ? $uri : trim($uri, '/ ');
// Loop through the route array looking for wildcards
foreach ($routes as $key => $val) {
// Reset localeSegment
$localeSegment = null;
$key = $key === '/' ? $key : ltrim($key, '/ ');
foreach ($routes as $routeKey => $handler) {
$routeKey = $routeKey === '/' ? $routeKey : ltrim($routeKey, '/ ');
$matchedKey = $key;
$matchedKey = $routeKey;
// Are we dealing with a locale?
if (strpos($key, '{locale}') !== false) {
$localeSegment = array_search(
'{locale}',
preg_split(
'/[\/]*((^[a-zA-Z0-9])|\(([^()]*)\))*[\/]+/m',
$key,
),
true,
);
// Replace it with a regex so it
// will actually match.
$key = str_replace('/', '\/', $key);
$key = str_replace('{locale}', '[^\/]+', $key);
if (str_contains($routeKey, '{locale}')) {
$routeKey = str_replace('{locale}', '[^/]+', $routeKey);
}
// Does the RegEx match?
if (preg_match('#^' . $key . '$#u', $uri, $matches)) {
$this->matchedRouteOptions = $this->collection->getRoutesOptions(
$matchedKey,
);
if (preg_match('#^' . $routeKey . '$#u', $uri, $matches)) {
$this->matchedRouteOptions = $this->collection->getRoutesOptions($matchedKey);
// Is this route supposed to redirect to another?
if ($this->collection->isRedirect($key)) {
if ($this->collection->isRedirect($routeKey)) {
// replacing matched route groups with references: post/([0-9]+) -> post/$1
$redirectTo = preg_replace_callback('/(\([^\(]+\))/', static function (): string {
static $i = 1;
return '$' . $i++;
}, (string) (is_array($handler) ? key($handler) : $handler));
throw new RedirectException(
is_array($val) ? key($val) : $val,
$this->collection->getRedirectCode($key),
preg_replace('#^' . $routeKey . '$#u', (string) $redirectTo, $uri),
$this->collection->getRedirectCode($routeKey),
);
}
// Store our locale so CodeIgniter object can
// assign it to the Request.
if (isset($localeSegment)) {
// The following may be inefficient, but doesn't upset NetBeans :-/
$temp = explode('/', $uri);
$this->detectedLocale = $temp[$localeSegment];
if (str_contains($matchedKey, '{locale}')) {
preg_match(
'#^' . str_replace('{locale}', '(?<locale>[^/]+)', $matchedKey) . '$#u',
$uri,
$matched,
);
if ($this->collection->shouldUseSupportedLocalesOnly()
&& ! in_array($matched['locale'], config('App')->supportedLocales, true)) {
// Throw exception to prevent the autorouter, if enabled,
// from trying to find a route
throw PageNotFoundException::forLocaleNotSupported($matched['locale']);
}
$this->detectedLocale = $matched['locale'];
unset($matched);
}
// Are we using Closures? If so, then we need
// to collect the params into an array
// so it can be passed to the controller method later.
if (!is_string($val) && is_callable($val)) {
$this->controller = $val;
if (! is_string($handler) && is_callable($handler)) {
$this->controller = $handler;
// Remove the original string from the matches array
array_shift($matches);
$this->params = $matches;
$this->matchedRoute = [$matchedKey, $val];
$this->setMatchedRoute($matchedKey, $handler);
return true;
}
......@@ -108,91 +114,120 @@ class Router extends \CodeIgniter\Router\Router
// check if the alternate-content has been requested in the accept
// header and overwrite the $val with the matching controller method
if (
array_key_exists(
'alternate-content',
$this->matchedRouteOptions,
) &&
array_key_exists('alternate-content', $this->matchedRouteOptions) &&
is_array($this->matchedRouteOptions['alternate-content'])
) {
$request = Services::request();
$negotiate = Services::negotiator();
$request = service('request');
$negotiate = service('negotiator');
$acceptHeader = $request->getHeader('Accept')->getValue();
// Accept header is mandatory
if ($request->header('Accept') === null) {
break;
}
$acceptHeader = $request->header('Accept')
->getValue();
$parsedHeader = $negotiate->parseHeader($acceptHeader);
$supported = array_keys(
$this->matchedRouteOptions['alternate-content'],
);
$supported = array_keys($this->matchedRouteOptions['alternate-content']);
$expectedContentType = $parsedHeader[0];
foreach ($supported as $available) {
if (
$negotiate->callMatch(
$expectedContentType,
$available,
true,
)
! $negotiate->callMatch($expectedContentType, $available, true)
) {
if (
array_key_exists(
'namespace',
$this->matchedRouteOptions[
'alternate-content'
continue;
}
if (
array_key_exists(
'namespace',
$this->matchedRouteOptions[
'alternate-content'
][$available],
)
) {
$this->collection->setDefaultNamespace(
$this->matchedRouteOptions[
'alternate-content'
)
) {
$this->collection->setDefaultNamespace(
$this->matchedRouteOptions[
'alternate-content'
][$available]['namespace'],
);
}
$val =
$this->collection->getDefaultNamespace() .
$this->directory .
$this->matchedRouteOptions['alternate-content'][
$available
]['controller-method'];
// no need to continue loop as $val has been overwritten
break;
);
}
$handler =
$this->collection->getDefaultNamespace() .
$this->directory .
$this->matchedRouteOptions['alternate-content'][
$available
]['controller-method'];
// no need to continue loop as $handle has been overwritten
break;
}
}
// Are we using the default method for back-references?
// Are we using Closures? If so, then we need
// to collect the params into an array
// so it can be passed to the controller method later.
if (! is_string($handler) && is_callable($handler)) {
$this->controller = $handler;
// Support resource route when function with subdirectory
// ex: $routes->resource('Admin/Admins');
if (
strpos($val, '$') !== false &&
strpos($key, '(') !== false &&
strpos($key, '/') !== false
) {
$replacekey = str_replace('/(.*)', '', $key);
$val = preg_replace('#^' . $key . '$#u', $val, $uri);
$val = str_replace(
$replacekey,
str_replace('/', '\\', $replacekey),
$val,
);
} elseif (
strpos($val, '$') !== false &&
strpos($key, '(') !== false
) {
$val = preg_replace('#^' . $key . '$#u', $val, $uri);
} elseif (strpos($val, '/') !== false) {
[$controller, $method] = explode('::', $val);
// Remove the original string from the matches array
array_shift($matches);
$this->params = $matches;
$this->setMatchedRoute($matchedKey, $handler);
return true;
}
if (str_contains((string) $handler, '::')) {
[$controller, $methodAndParams] = explode('::', (string) $handler);
} else {
$controller = $handler;
$methodAndParams = '';
}
// Only replace slashes in the controller, not in the method.
$controller = str_replace('/', '\\', $controller);
// Checks `/` in controller name
if (str_contains((string) $controller, '/')) {
throw RouterException::forInvalidControllerName($handler);
}
if (str_contains((string) $handler, '$') && str_contains($routeKey, '(')) {
// Checks dynamic controller
if (str_contains((string) $controller, '$')) {
throw RouterException::forDynamicController($handler);
}
$val = $controller . '::' . $method;
if (config(Routing::class)->multipleSegmentsOneParam === false) {
// Using back-references
$segments = explode(
'/',
(string) preg_replace('#\A' . $routeKey . '\z#u', (string) $handler, $uri),
);
} else {
if (str_contains($methodAndParams, '/')) {
[$method, $handlerParams] = explode('/', $methodAndParams, 2);
$params = explode('/', $handlerParams);
$handlerSegments = array_merge([$controller . '::' . $method], $params);
} else {
$handlerSegments = [$handler];
}
$segments = [];
foreach ($handlerSegments as $segment) {
$segments[] = $this->replaceBackReferences($segment, $matches);
}
}
} else {
$segments = explode('/', (string) $handler);
}
$this->setRequest(explode('/', $val));
$this->setRequest($segments);
$this->matchedRoute = [$matchedKey, $val];
$this->setMatchedRoute($matchedKey, $handler);
return true;
}
......