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
Commits on Source (9)
Showing
with 788 additions and 329 deletions
# [1.0.0-beta.6](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-beta.5...v1.0.0-beta.6) (2022-02-03)
### Bug Fixes
- **activitypub:** add conditions for possibly missing actor properties + add
user-agent to requests
([8fbf948](https://code.podlibre.org/podlibre/castopod-host/commit/8fbf948fbba22ffd33966a1b2ccd42e8f7c1f8a2))
- **activitypub:** add target actor id to like / announce activities to send
directly to note's actor
([962dd30](https://code.podlibre.org/podlibre/castopod-host/commit/962dd305f5d3f6eadc68f400e0e8f953827fe20d))
- **activitypub:** add target_actor_id for create activity to broadcast post
reply
([0128a21](https://code.podlibre.org/podlibre/castopod-host/commit/0128a21ec55dcc0a2fbf4081dadb4c4737735ba1))
- **http-signature:** update SIGNATURE_PATTERN allowing signature keys to be
sent in any order
([b7f285e](https://code.podlibre.org/podlibre/castopod-host/commit/b7f285e4e24247fedb94f030356fa6f291f525cc))
- **install:** set message block on forms to show error messages
([3a0a20d](https://code.podlibre.org/podlibre/castopod-host/commit/3a0a20d59cdae7f166325efb750eaa6e9800ba6e)),
closes [#157](https://code.podlibre.org/podlibre/castopod-host/issues/157)
- **markdown-editor:** remove unnecessary buttons for podcast and episode
editors + add extensions
([9c4f60e](https://code.podlibre.org/podlibre/castopod-host/commit/9c4f60e00bcbd4f784f12d2a6fed357ad402ee2e))
- **podcast-activity:** check if transcript and chapters are set before
including them in audio
([5855a25](https://code.podlibre.org/podlibre/castopod-host/commit/5855a250936f91641efef77650890a18d8e9917f))
- **podcast:** use markdown description value for editor + set prose class to
about description
([f304d97](https://code.podlibre.org/podlibre/castopod-host/commit/f304d97b14e0ef383509cb3bba50beb55bf701ba)),
closes [#156](https://code.podlibre.org/podlibre/castopod-host/issues/156)
# [1.0.0-beta.5](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-beta.4...v1.0.0-beta.5) (2022-01-31) # [1.0.0-beta.5](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-beta.4...v1.0.0-beta.5) (2022-01-31)
### Bug Fixes ### Bug Fixes
......
...@@ -11,7 +11,7 @@ declare(strict_types=1); ...@@ -11,7 +11,7 @@ declare(strict_types=1);
| |
| NOTE: this constant is updated upon release with Continuous Integration. | NOTE: this constant is updated upon release with Continuous Integration.
*/ */
defined('CP_VERSION') || define('CP_VERSION', '1.0.0-beta.5'); defined('CP_VERSION') || define('CP_VERSION', '1.0.0-beta.6');
/* /*
| -------------------------------------------------------------------- | --------------------------------------------------------------------
......
...@@ -26,7 +26,12 @@ use CodeIgniter\Entity\Entity; ...@@ -26,7 +26,12 @@ use CodeIgniter\Entity\Entity;
use CodeIgniter\Files\File; use CodeIgniter\Files\File;
use CodeIgniter\HTTP\Files\UploadedFile; use CodeIgniter\HTTP\Files\UploadedFile;
use CodeIgniter\I18n\Time; use CodeIgniter\I18n\Time;
use League\CommonMark\CommonMarkConverter; use League\CommonMark\Environment\Environment;
use League\CommonMark\Extension\Autolink\AutolinkExtension;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
use League\CommonMark\MarkdownConverter;
use RuntimeException; use RuntimeException;
/** /**
...@@ -473,13 +478,21 @@ class Episode extends Entity ...@@ -473,13 +478,21 @@ class Episode extends Entity
public function setDescriptionMarkdown(string $descriptionMarkdown): static public function setDescriptionMarkdown(string $descriptionMarkdown): static
{ {
$converter = new CommonMarkConverter([ $config = [
'html_input' => 'strip', 'html_input' => 'escape',
'allow_unsafe_links' => false, 'allow_unsafe_links' => false,
]); ];
$environment = new Environment($config);
$environment->addExtension(new CommonMarkCoreExtension());
$environment->addExtension(new AutolinkExtension());
$environment->addExtension(new SmartPunctExtension());
$environment->addExtension(new DisallowedRawHtmlExtension());
$converter = new MarkdownConverter($environment);
$this->attributes['description_markdown'] = $descriptionMarkdown; $this->attributes['description_markdown'] = $descriptionMarkdown;
$this->attributes['description_html'] = $converter->convertToHtml($descriptionMarkdown); $this->attributes['description_html'] = $converter->convert($descriptionMarkdown);
return $this; return $this;
} }
......
...@@ -12,7 +12,12 @@ namespace App\Entities; ...@@ -12,7 +12,12 @@ namespace App\Entities;
use CodeIgniter\Entity\Entity; use CodeIgniter\Entity\Entity;
use CodeIgniter\I18n\Time; use CodeIgniter\I18n\Time;
use League\CommonMark\CommonMarkConverter; use League\CommonMark\Environment\Environment;
use League\CommonMark\Extension\Autolink\AutolinkExtension;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
use League\CommonMark\MarkdownConverter;
/** /**
* @property int $id * @property int $id
...@@ -49,13 +54,20 @@ class Page extends Entity ...@@ -49,13 +54,20 @@ class Page extends Entity
public function setContentMarkdown(string $contentMarkdown): static public function setContentMarkdown(string $contentMarkdown): static
{ {
$converter = new CommonMarkConverter([ $config = [
'html_input' => 'strip',
'allow_unsafe_links' => false, 'allow_unsafe_links' => false,
]); ];
$environment = new Environment($config);
$environment->addExtension(new CommonMarkCoreExtension());
$environment->addExtension(new AutolinkExtension());
$environment->addExtension(new SmartPunctExtension());
$environment->addExtension(new DisallowedRawHtmlExtension());
$converter = new MarkdownConverter($environment);
$this->attributes['content_markdown'] = $contentMarkdown; $this->attributes['content_markdown'] = $contentMarkdown;
$this->attributes['content_html'] = $converter->convertToHtml($contentMarkdown); $this->attributes['content_html'] = $converter->convert($contentMarkdown);
return $this; return $this;
} }
......
...@@ -23,7 +23,12 @@ use CodeIgniter\Entity\Entity; ...@@ -23,7 +23,12 @@ use CodeIgniter\Entity\Entity;
use CodeIgniter\Files\File; use CodeIgniter\Files\File;
use CodeIgniter\HTTP\Files\UploadedFile; use CodeIgniter\HTTP\Files\UploadedFile;
use CodeIgniter\I18n\Time; use CodeIgniter\I18n\Time;
use League\CommonMark\CommonMarkConverter; use League\CommonMark\Environment\Environment;
use League\CommonMark\Extension\Autolink\AutolinkExtension;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
use League\CommonMark\MarkdownConverter;
use Modules\Auth\Entities\User; use Modules\Auth\Entities\User;
use RuntimeException; use RuntimeException;
...@@ -375,13 +380,21 @@ class Podcast extends Entity ...@@ -375,13 +380,21 @@ class Podcast extends Entity
public function setDescriptionMarkdown(string $descriptionMarkdown): static public function setDescriptionMarkdown(string $descriptionMarkdown): static
{ {
$converter = new CommonMarkConverter([ $config = [
'html_input' => 'strip', 'html_input' => 'escape',
'allow_unsafe_links' => false, 'allow_unsafe_links' => false,
]); ];
$environment = new Environment($config);
$environment->addExtension(new CommonMarkCoreExtension());
$environment->addExtension(new AutolinkExtension());
$environment->addExtension(new SmartPunctExtension());
$environment->addExtension(new DisallowedRawHtmlExtension());
$converter = new MarkdownConverter($environment);
$this->attributes['description_markdown'] = $descriptionMarkdown; $this->attributes['description_markdown'] = $descriptionMarkdown;
$this->attributes['description_html'] = $converter->convertToHtml($descriptionMarkdown); $this->attributes['description_html'] = $converter->convert($descriptionMarkdown);
return $this; return $this;
} }
...@@ -399,17 +412,25 @@ class Podcast extends Entity ...@@ -399,17 +412,25 @@ class Podcast extends Entity
return $this; return $this;
} }
$converter = new CommonMarkConverter([ $config = [
'html_input' => 'strip', 'html_input' => 'escape',
'allow_unsafe_links' => false, 'allow_unsafe_links' => false,
]); ];
$environment = new Environment($config);
$environment->addExtension(new CommonMarkCoreExtension());
$environment->addExtension(new AutolinkExtension());
$environment->addExtension(new SmartPunctExtension());
$environment->addExtension(new DisallowedRawHtmlExtension());
$converter = new MarkdownConverter($environment);
$this->attributes[ $this->attributes[
'episode_description_footer_markdown' 'episode_description_footer_markdown'
] = $episodeDescriptionFooterMarkdown; ] = $episodeDescriptionFooterMarkdown;
$this->attributes[ $this->attributes[
'episode_description_footer_html' 'episode_description_footer_html'
] = $converter->convertToHtml($episodeDescriptionFooterMarkdown); ] = $converter->convert($episodeDescriptionFooterMarkdown);
return $this; return $this;
} }
......
<?php
declare(strict_types=1);
if (! function_exists('form_markdown_textarea')) {
/**
* Textarea field
*
* @param mixed $data
* @param mixed $extra
*/
function form_markdown_textarea($data = '', string $value = '', $extra = ''): string
{
$defaults = [
'name' => is_array($data) ? '' : $data,
'cols' => '40',
'rows' => '10',
];
if (! is_array($data) || ! isset($data['value'])) {
$val = $value;
} else {
$val = $data['value'];
unset($data['value']); // textareas don't use the value attribute
}
// Unsets default rows and cols if defined in extra field as array or string.
if ((is_array($extra) && array_key_exists('rows', $extra)) || (is_string($extra) && stripos(
preg_replace('~\s+~', '', $extra),
'rows='
) !== false)) {
unset($defaults['rows']);
}
if ((is_array($extra) && array_key_exists('cols', $extra)) || (is_string($extra) && stripos(
preg_replace('~\s+~', '', $extra),
'cols='
) !== false)) {
unset($defaults['cols']);
}
return '<textarea ' . rtrim(parse_form_attributes($data, $defaults)) . stringify_attributes($extra) . '>'
. $val
. "</textarea>\n";
}
}
...@@ -68,10 +68,16 @@ class PodcastEpisode extends ObjectType ...@@ -68,10 +68,16 @@ class PodcastEpisode extends ObjectType
'type' => 'Link', 'type' => 'Link',
'mediaType' => $episode->audio->file_mimetype, 'mediaType' => $episode->audio->file_mimetype,
], ],
'transcript' => $episode->transcript->file_url,
'chapters' => $episode->chapters->file_url,
]; ];
if ($episode->transcript !== null) {
$this->audio['transcript'] = $episode->transcript->file_url;
}
if ($episode->chapters !== null) {
$this->audio['chapters'] = $episode->chapters->file_url;
}
$this->comments = url_to('episode-comments', $episode->podcast->handle, $episode->slug); $this->comments = url_to('episode-comments', $episode->podcast->handle, $episode->slug);
if ($episode->published_at !== null) { if ($episode->published_at !== null) {
......
...@@ -30,6 +30,8 @@ class Alert extends Component ...@@ -30,6 +30,8 @@ class Alert extends Component
$title = $this->title === null ? '' : '<div class="font-semibold">' . $this->title . '</div>'; $title = $this->title === null ? '' : '<div class="font-semibold">' . $this->title . '</div>';
$class = 'inline-flex w-full p-2 text-sm border rounded ' . $variantClasses[$this->variant] . ' ' . $this->class; $class = 'inline-flex w-full p-2 text-sm border rounded ' . $variantClasses[$this->variant] . ' ' . $this->class;
unset($this->attributes['slot']);
unset($this->attributes['variant']);
$attributes = stringify_attributes($this->attributes); $attributes = stringify_attributes($this->attributes);
return <<<HTML return <<<HTML
......
...@@ -6,6 +6,16 @@ namespace App\Views\Components\Forms; ...@@ -6,6 +6,16 @@ namespace App\Views\Components\Forms;
class MarkdownEditor extends FormComponent class MarkdownEditor extends FormComponent
{ {
/**
* @var string[]
*/
protected array $disallowList = [];
public function setDisallowList(string $value): void
{
$this->disallowList = explode(',', $value);
}
public function render(): string public function render(): string
{ {
$editorClass = 'w-full flex flex-col bg-elevated border-3 border-contrast rounded-lg overflow-hidden focus-within:ring-accent ' . $this->class; $editorClass = 'w-full flex flex-col bg-elevated border-3 border-contrast rounded-lg overflow-hidden focus-within:ring-accent ' . $this->class;
...@@ -13,30 +23,83 @@ class MarkdownEditor extends FormComponent ...@@ -13,30 +23,83 @@ class MarkdownEditor extends FormComponent
$this->attributes['class'] = 'bg-elevated border-none focus:border-none focus:outline-none focus:ring-0 w-full h-full'; $this->attributes['class'] = 'bg-elevated border-none focus:border-none focus:outline-none focus:ring-0 w-full h-full';
$this->attributes['rows'] = 6; $this->attributes['rows'] = 6;
// dd(htmlspecialchars_decode($this->value));
$value = htmlspecialchars_decode($this->value); $value = htmlspecialchars_decode($this->value);
$textarea = form_textarea($this->attributes, old($this->name, $value, false)); $oldValue = old($this->name);
$icons = [ if ($oldValue === null) {
'heading' => icon('heading'), $oldValue = $value;
'bold' => icon('bold'), }
'italic' => icon('italic'), $textarea = form_textarea($this->attributes, $oldValue);
'list-unordered' => icon('list-unordered'), $markdownIcon = icon(
'list-ordered' => icon('list-ordered'), 'markdown',
'quote' => icon('quote'), 'mr-1 text-lg opacity-40'
'link' => icon('link'), );
'image-add' => icon('image-add'),
'markdown' => icon(
'markdown',
'mr-1 text-lg opacity-40'
),
];
$translations = [ $translations = [
'write' => lang('Common.forms.editor.write'), 'write' => lang('Common.forms.editor.write'),
'preview' => lang('Common.forms.editor.preview'), 'preview' => lang('Common.forms.editor.preview'),
'help' => lang('Common.forms.editor.help'), 'help' => lang('Common.forms.editor.help'),
]; ];
$toolbarGroups = [
[
[
'name' => 'header',
'tag' => 'md-header',
'icon' => icon('heading'),
],
[
'name' => 'bold',
'tag' => 'md-bold',
'icon' => icon('bold'),
],
[
'name' => 'italic',
'tag' => 'md-italic',
'icon' => icon('italic'),
],
],
[
[
'name' => 'unordered-list',
'tag' => 'md-unordered-list',
'icon' => icon('list-unordered'),
],
[
'name' => 'ordered-list',
'tag' => 'md-ordered-list ',
'icon' => icon('list-ordered'),
],
],
[
[
'name' => 'quote',
'tag' => 'md-quote',
'icon' => icon('quote'),
],
[
'name' => 'link',
'tag' => 'md-link',
'icon' => icon('link'),
],
[
'name' => 'image',
'tag' => 'md-image',
'icon' => icon('image-add'),
],
],
];
$toolbarContent = '';
foreach ($toolbarGroups as $buttonsGroup) {
$toolbarContent .= '<div class="inline-flex text-2xl gap-x-1">';
foreach ($buttonsGroup as $button) {
if (! in_array($button['name'], $this->disallowList, true)) {
$toolbarContent .= '<' . $button['tag'] . ' class="opacity-50 hover:opacity-100 focus:ring-accent focus:opacity-100">' . $button['icon'] . '</' . $button['tag'] . '>';
}
}
$toolbarContent .= '</div>';
}
return <<<HTML return <<<HTML
<div class="{$editorClass}"> <div class="{$editorClass}">
<header class="px-2"> <header class="px-2">
...@@ -45,22 +108,7 @@ class MarkdownEditor extends FormComponent ...@@ -45,22 +108,7 @@ class MarkdownEditor extends FormComponent
<button type="button" slot="write" class="px-2 font-semibold focus:ring-inset focus:ring-accent">{$translations['write']}</button> <button type="button" slot="write" class="px-2 font-semibold focus:ring-inset focus:ring-accent">{$translations['write']}</button>
<button type="button" slot="preview" class="px-2 font-semibold focus:ring-inset focus:ring-accent">{$translations['preview']}</button> <button type="button" slot="preview" class="px-2 font-semibold focus:ring-inset focus:ring-accent">{$translations['preview']}</button>
</markdown-write-preview> </markdown-write-preview>
<markdown-toolbar for="{$this->id}" class="flex gap-4 px-2 py-1"> <markdown-toolbar for="{$this->id}" class="flex gap-4 px-2 py-1">{$toolbarContent}</markdown-toolbar>
<div class="inline-flex text-2xl gap-x-1">
<md-header class="opacity-50 hover:opacity-100 focus:ring-accent focus:opacity-100">{$icons['heading']}</md-header>
<md-bold class="opacity-50 hover:opacity-100 focus:ring-accent focus:opacity-100" data-hotkey-scope="{$this->id}" data-hotkey="Control+b,Meta+b">{$icons['bold']}</md-bold>
<md-italic class="opacity-50 hover:opacity-100 focus:ring-accent focus:opacity-100" data-hotkey-scope="{$this->id}" data-hotkey="Control+i,Meta+i">{$icons['italic']}</md-italic>
</div>
<div class="inline-flex text-2xl gap-x-1">
<md-unordered-list class="opacity-50 hover:opacity-100 focus:ring-accent focus:opacity-100">{$icons['list-unordered']}</md-unordered-list>
<md-ordered-list class="opacity-50 hover:opacity-100 focus:ring-accent focus:opacity-100">{$icons['list-ordered']}</md-ordered-list>
</div>
<div class="inline-flex text-2xl gap-x-1">
<md-quote class="opacity-50 hover:opacity-100 focus:ring-accent focus:opacity-100">{$icons['quote']}</md-quote>
<md-link class="opacity-50 hover:opacity-100 focus:ring-accent focus:opacity-100" data-hotkey-scope="{$this->id}" data-hotkey="Control+k,Meta+k">{$icons['link']}</md-link>
<md-image class="opacity-50 hover:opacity-100 focus:ring-accent focus:opacity-100">{$icons['image-add']}</md-image>
</div>
</markdown-toolbar>
</div> </div>
</header> </header>
<div class="relative"> <div class="relative">
...@@ -68,7 +116,7 @@ class MarkdownEditor extends FormComponent ...@@ -68,7 +116,7 @@ class MarkdownEditor extends FormComponent
<markdown-preview for="{$this->id}" class="absolute top-0 left-0 hidden w-full h-full max-w-full px-3 py-2 overflow-y-auto prose bg-base" showClass="bg-elevated" /> <markdown-preview for="{$this->id}" class="absolute top-0 left-0 hidden w-full h-full max-w-full px-3 py-2 overflow-y-auto prose bg-base" showClass="bg-elevated" />
</div> </div>
<footer class="flex px-2 py-1 border-t bg-base"> <footer class="flex px-2 py-1 border-t bg-base">
<a href="https://commonmark.org/help/" class="inline-flex items-center text-xs font-semibold text-skin-muted hover:text-skin-base" target="_blank" rel="noopener noreferrer">{$icons['markdown']}{$translations['help']}</a> <a href="https://commonmark.org/help/" class="inline-flex items-center text-xs font-semibold text-skin-muted hover:text-skin-base" target="_blank" rel="noopener noreferrer">{$markdownIcon}{$translations['help']}</a>
</footer> </footer>
</div> </div>
HTML; HTML;
......
{ {
"name": "podlibre/castopod-host", "name": "podlibre/castopod-host",
"version": "1.0.0-beta5", "version": "1.0.0-beta6",
"type": "project", "type": "project",
"description": "Castopod Host is an open-source hosting platform made for podcasters who want engage and interact with their audience.", "description": "Castopod Host is an open-source hosting platform made for podcasters who want engage and interact with their audience.",
"homepage": "https://castopod.org", "homepage": "https://castopod.org",
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
"geoip2/geoip2": "^v2.11.0", "geoip2/geoip2": "^v2.11.0",
"myth/auth": "dev-develop", "myth/auth": "dev-develop",
"codeigniter4/codeigniter4": "dev-develop", "codeigniter4/codeigniter4": "dev-develop",
"league/commonmark": "^v1.6.6", "league/commonmark": "^2.2",
"vlucas/phpdotenv": "^v5.3.0", "vlucas/phpdotenv": "^v5.3.0",
"league/html-to-markdown": "^v5.0.1", "league/html-to-markdown": "^v5.0.1",
"opawg/user-agents-php": "^v1.0", "opawg/user-agents-php": "^v1.0",
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "1cd09e20009835bd1480554991ad2ff1", "content-hash": "bf66875f72680d0f5953f03bf31c8612",
"packages": [ "packages": [
{ {
"name": "brick/math", "name": "brick/math",
...@@ -307,6 +307,74 @@ ...@@ -307,6 +307,74 @@
], ],
"time": "2021-06-07T13:58:28+00:00" "time": "2021-06-07T13:58:28+00:00"
}, },
{
"name": "dflydev/dot-access-data",
"version": "v3.0.1",
"source": {
"type": "git",
"url": "https://github.com/dflydev/dflydev-dot-access-data.git",
"reference": "0992cc19268b259a39e86f296da5f0677841f42c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/0992cc19268b259a39e86f296da5f0677841f42c",
"reference": "0992cc19268b259a39e86f296da5f0677841f42c",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
"require-dev": {
"phpstan/phpstan": "^0.12.42",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.3",
"scrutinizer/ocular": "1.6.0",
"squizlabs/php_codesniffer": "^3.5",
"vimeo/psalm": "^3.14"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Dflydev\\DotAccessData\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": ["MIT"],
"authors": [
{
"name": "Dragonfly Development Inc.",
"email": "info@dflydev.com",
"homepage": "http://dflydev.com"
},
{
"name": "Beau Simensen",
"email": "beau@dflydev.com",
"homepage": "http://beausimensen.com"
},
{
"name": "Carlos Frutos",
"email": "carlos@kiwing.it",
"homepage": "https://github.com/cfrutos"
},
{
"name": "Colin O'Dell",
"email": "colinodell@gmail.com",
"homepage": "https://www.colinodell.com"
}
],
"description": "Given a deep data structure, access data by dot notation.",
"homepage": "https://github.com/dflydev/dflydev-dot-access-data",
"keywords": ["access", "data", "dot", "notation"],
"support": {
"issues": "https://github.com/dflydev/dflydev-dot-access-data/issues",
"source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.1"
},
"time": "2021-08-13T13:06:58+00:00"
},
{ {
"name": "essence/dom", "name": "essence/dom",
"version": "1.0.0", "version": "1.0.0",
...@@ -783,40 +851,52 @@ ...@@ -783,40 +851,52 @@
}, },
{ {
"name": "league/commonmark", "name": "league/commonmark",
"version": "1.6.6", "version": "2.2.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/thephpleague/commonmark.git", "url": "https://github.com/thephpleague/commonmark.git",
"reference": "c4228d11e30d7493c6836d20872f9582d8ba6dcf" "reference": "f8afb78f087777b040e0ab8a6b6ca93f6fc3f18a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/c4228d11e30d7493c6836d20872f9582d8ba6dcf", "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/f8afb78f087777b040e0ab8a6b6ca93f6fc3f18a",
"reference": "c4228d11e30d7493c6836d20872f9582d8ba6dcf", "reference": "f8afb78f087777b040e0ab8a6b6ca93f6fc3f18a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-mbstring": "*", "ext-mbstring": "*",
"php": "^7.1 || ^8.0" "league/config": "^1.1.1",
}, "php": "^7.4 || ^8.0",
"conflict": { "psr/event-dispatcher": "^1.0",
"scrutinizer/ocular": "1.7.*" "symfony/deprecation-contracts": "^2.1 || ^3.0",
"symfony/polyfill-php80": "^1.15"
}, },
"require-dev": { "require-dev": {
"cebe/markdown": "~1.0", "cebe/markdown": "^1.0",
"commonmark/commonmark.js": "0.29.2", "commonmark/cmark": "0.30.0",
"erusev/parsedown": "~1.0", "commonmark/commonmark.js": "0.30.0",
"composer/package-versions-deprecated": "^1.8",
"erusev/parsedown": "^1.0",
"ext-json": "*", "ext-json": "*",
"github/gfm": "0.29.0", "github/gfm": "0.29.0",
"michelf/php-markdown": "~1.4", "michelf/php-markdown": "^1.4",
"mikehaertl/php-shellcommand": "^1.4", "phpstan/phpstan": "^0.12.88 || ^1.0.0",
"phpstan/phpstan": "^0.12.90", "phpunit/phpunit": "^9.5.5",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.2", "scrutinizer/ocular": "^1.8.1",
"scrutinizer/ocular": "^1.5", "symfony/finder": "^5.3",
"symfony/finder": "^4.2" "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0",
"unleashedtech/php-coding-standard": "^3.1",
"vimeo/psalm": "^4.7.3"
},
"suggest": {
"symfony/yaml": "v2.3+ required if using the Front Matter extension"
}, },
"bin": ["bin/commonmark"],
"type": "library", "type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.3-dev"
}
},
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"League\\CommonMark\\": "src" "League\\CommonMark\\": "src"
...@@ -832,7 +912,7 @@ ...@@ -832,7 +912,7 @@
"role": "Lead Developer" "role": "Lead Developer"
} }
], ],
"description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and Github-Flavored Markdown (GFM)", "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)",
"homepage": "https://commonmark.thephpleague.com", "homepage": "https://commonmark.thephpleague.com",
"keywords": [ "keywords": [
"commonmark", "commonmark",
...@@ -846,15 +926,12 @@ ...@@ -846,15 +926,12 @@
], ],
"support": { "support": {
"docs": "https://commonmark.thephpleague.com/", "docs": "https://commonmark.thephpleague.com/",
"forum": "https://github.com/thephpleague/commonmark/discussions",
"issues": "https://github.com/thephpleague/commonmark/issues", "issues": "https://github.com/thephpleague/commonmark/issues",
"rss": "https://github.com/thephpleague/commonmark/releases.atom", "rss": "https://github.com/thephpleague/commonmark/releases.atom",
"source": "https://github.com/thephpleague/commonmark" "source": "https://github.com/thephpleague/commonmark"
}, },
"funding": [ "funding": [
{
"url": "https://enjoy.gitstore.app/repositories/thephpleague/commonmark",
"type": "custom"
},
{ {
"url": "https://www.colinodell.com/sponsor", "url": "https://www.colinodell.com/sponsor",
"type": "custom" "type": "custom"
...@@ -867,16 +944,92 @@ ...@@ -867,16 +944,92 @@
"url": "https://github.com/colinodell", "url": "https://github.com/colinodell",
"type": "github" "type": "github"
}, },
{
"url": "https://www.patreon.com/colinodell",
"type": "patreon"
},
{ {
"url": "https://tidelift.com/funding/github/packagist/league/commonmark", "url": "https://tidelift.com/funding/github/packagist/league/commonmark",
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-07-17T17:13:23+00:00" "time": "2022-01-25T14:37:33+00:00"
},
{
"name": "league/config",
"version": "v1.1.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/config.git",
"reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e",
"reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e",
"shasum": ""
},
"require": {
"dflydev/dot-access-data": "^3.0.1",
"nette/schema": "^1.2",
"php": "^7.4 || ^8.0"
},
"require-dev": {
"phpstan/phpstan": "^0.12.90",
"phpunit/phpunit": "^9.5.5",
"scrutinizer/ocular": "^1.8.1",
"unleashedtech/php-coding-standard": "^3.1",
"vimeo/psalm": "^4.7.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.2-dev"
}
},
"autoload": {
"psr-4": {
"League\\Config\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": ["BSD-3-Clause"],
"authors": [
{
"name": "Colin O'Dell",
"email": "colinodell@gmail.com",
"homepage": "https://www.colinodell.com",
"role": "Lead Developer"
}
],
"description": "Define configuration arrays with strict schemas and access values with dot notation",
"homepage": "https://config.thephpleague.com",
"keywords": [
"array",
"config",
"configuration",
"dot",
"dot-access",
"nested",
"schema"
],
"support": {
"docs": "https://config.thephpleague.com/",
"issues": "https://github.com/thephpleague/config/issues",
"rss": "https://github.com/thephpleague/config/releases.atom",
"source": "https://github.com/thephpleague/config"
},
"funding": [
{
"url": "https://www.colinodell.com/sponsor",
"type": "custom"
},
{
"url": "https://www.paypal.me/colinpodell/10.00",
"type": "custom"
},
{
"url": "https://github.com/colinodell",
"type": "github"
}
],
"time": "2021-08-14T12:15:32+00:00"
}, },
{ {
"name": "league/html-to-markdown", "name": "league/html-to-markdown",
...@@ -1236,6 +1389,138 @@ ...@@ -1236,6 +1389,138 @@
], ],
"time": "2021-06-10T04:25:01+00:00" "time": "2021-06-10T04:25:01+00:00"
}, },
{
"name": "nette/schema",
"version": "v1.2.2",
"source": {
"type": "git",
"url": "https://github.com/nette/schema.git",
"reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df",
"reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df",
"shasum": ""
},
"require": {
"nette/utils": "^2.5.7 || ^3.1.5 || ^4.0",
"php": ">=7.1 <8.2"
},
"require-dev": {
"nette/tester": "^2.3 || ^2.4",
"phpstan/phpstan-nette": "^0.12",
"tracy/tracy": "^2.7"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.2-dev"
}
},
"autoload": {
"classmap": ["src/"]
},
"notification-url": "https://packagist.org/downloads/",
"license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"],
"authors": [
{
"name": "David Grudl",
"homepage": "https://davidgrudl.com"
},
{
"name": "Nette Community",
"homepage": "https://nette.org/contributors"
}
],
"description": "📐 Nette Schema: validating data structures against a given Schema.",
"homepage": "https://nette.org",
"keywords": ["config", "nette"],
"support": {
"issues": "https://github.com/nette/schema/issues",
"source": "https://github.com/nette/schema/tree/v1.2.2"
},
"time": "2021-10-15T11:40:02+00:00"
},
{
"name": "nette/utils",
"version": "v3.2.2",
"source": {
"type": "git",
"url": "https://github.com/nette/utils.git",
"reference": "967cfc4f9a1acd5f1058d76715a424c53343c20c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nette/utils/zipball/967cfc4f9a1acd5f1058d76715a424c53343c20c",
"reference": "967cfc4f9a1acd5f1058d76715a424c53343c20c",
"shasum": ""
},
"require": {
"php": ">=7.2 <8.1"
},
"conflict": {
"nette/di": "<3.0.6"
},
"require-dev": {
"nette/tester": "~2.0",
"phpstan/phpstan": "^0.12",
"tracy/tracy": "^2.3"
},
"suggest": {
"ext-gd": "to use Image",
"ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()",
"ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()",
"ext-json": "to use Nette\\Utils\\Json",
"ext-mbstring": "to use Strings::lower() etc...",
"ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()",
"ext-xml": "to use Strings::length() etc. when mbstring is not available"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.2-dev"
}
},
"autoload": {
"classmap": ["src/"]
},
"notification-url": "https://packagist.org/downloads/",
"license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"],
"authors": [
{
"name": "David Grudl",
"homepage": "https://davidgrudl.com"
},
{
"name": "Nette Community",
"homepage": "https://nette.org/contributors"
}
],
"description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.",
"homepage": "https://nette.org",
"keywords": [
"array",
"core",
"datetime",
"images",
"json",
"nette",
"paginator",
"password",
"slugify",
"string",
"unicode",
"utf-8",
"utility",
"validation"
],
"support": {
"issues": "https://github.com/nette/utils/issues",
"source": "https://github.com/nette/utils/tree/v3.2.2"
},
"time": "2021-03-03T22:53:25+00:00"
},
{ {
"name": "opawg/user-agents-php", "name": "opawg/user-agents-php",
"version": "v1.0", "version": "v1.0",
...@@ -1541,9 +1826,53 @@ ...@@ -1541,9 +1826,53 @@
"description": "Common interface for caching libraries", "description": "Common interface for caching libraries",
"keywords": ["cache", "psr", "psr-6"], "keywords": ["cache", "psr", "psr-6"],
"support": { "support": {
"source": "https://github.com/php-fig/cache/tree/master" "source": "https://github.com/php-fig/cache/tree/master"
},
"time": "2016-08-06T20:24:11+00:00"
},
{
"name": "psr/event-dispatcher",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/event-dispatcher.git",
"reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0",
"reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0",
"shasum": ""
},
"require": {
"php": ">=7.2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\EventDispatcher\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": ["MIT"],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Standard interfaces for event handling.",
"keywords": ["events", "psr", "psr-14"],
"support": {
"issues": "https://github.com/php-fig/event-dispatcher/issues",
"source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0"
}, },
"time": "2016-08-06T20:24:11+00:00" "time": "2019-01-08T18:20:26+00:00"
}, },
{ {
"name": "psr/log", "name": "psr/log",
...@@ -1741,6 +2070,69 @@ ...@@ -1741,6 +2070,69 @@
], ],
"time": "2020-08-18T17:17:46+00:00" "time": "2020-08-18T17:17:46+00:00"
}, },
{
"name": "symfony/deprecation-contracts",
"version": "v2.4.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627",
"reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.4-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"autoload": {
"files": ["function.php"]
},
"notification-url": "https://packagist.org/downloads/",
"license": ["MIT"],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2021-03-23T23:28:01+00:00"
},
{ {
"name": "symfony/polyfill-ctype", "name": "symfony/polyfill-ctype",
"version": "v1.23.0", "version": "v1.23.0",
...@@ -2787,85 +3179,6 @@ ...@@ -2787,85 +3179,6 @@
}, },
"time": "2021-02-28T12:30:32+00:00" "time": "2021-02-28T12:30:32+00:00"
}, },
{
"name": "nette/utils",
"version": "v3.2.2",
"source": {
"type": "git",
"url": "https://github.com/nette/utils.git",
"reference": "967cfc4f9a1acd5f1058d76715a424c53343c20c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nette/utils/zipball/967cfc4f9a1acd5f1058d76715a424c53343c20c",
"reference": "967cfc4f9a1acd5f1058d76715a424c53343c20c",
"shasum": ""
},
"require": {
"php": ">=7.2 <8.1"
},
"conflict": {
"nette/di": "<3.0.6"
},
"require-dev": {
"nette/tester": "~2.0",
"phpstan/phpstan": "^0.12",
"tracy/tracy": "^2.3"
},
"suggest": {
"ext-gd": "to use Image",
"ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()",
"ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()",
"ext-json": "to use Nette\\Utils\\Json",
"ext-mbstring": "to use Strings::lower() etc...",
"ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()",
"ext-xml": "to use Strings::length() etc. when mbstring is not available"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.2-dev"
}
},
"autoload": {
"classmap": ["src/"]
},
"notification-url": "https://packagist.org/downloads/",
"license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"],
"authors": [
{
"name": "David Grudl",
"homepage": "https://davidgrudl.com"
},
{
"name": "Nette Community",
"homepage": "https://nette.org/contributors"
}
],
"description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.",
"homepage": "https://nette.org",
"keywords": [
"array",
"core",
"datetime",
"images",
"json",
"nette",
"paginator",
"password",
"slugify",
"string",
"unicode",
"utf-8",
"utility",
"validation"
],
"support": {
"issues": "https://github.com/nette/utils/issues",
"source": "https://github.com/nette/utils/tree/v3.2.2"
},
"time": "2021-03-03T22:53:25+00:00"
},
{ {
"name": "nikic/php-parser", "name": "nikic/php-parser",
"version": "v4.10.5", "version": "v4.10.5",
...@@ -3838,50 +4151,6 @@ ...@@ -3838,50 +4151,6 @@
}, },
"time": "2021-03-05T17:36:06+00:00" "time": "2021-03-05T17:36:06+00:00"
}, },
{
"name": "psr/event-dispatcher",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/event-dispatcher.git",
"reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0",
"reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0",
"shasum": ""
},
"require": {
"php": ">=7.2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\EventDispatcher\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": ["MIT"],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Standard interfaces for event handling.",
"keywords": ["events", "psr", "psr-14"],
"support": {
"issues": "https://github.com/php-fig/event-dispatcher/issues",
"source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0"
},
"time": "2019-01-08T18:20:26+00:00"
},
{ {
"name": "rector/rector", "name": "rector/rector",
"version": "0.11.16", "version": "0.11.16",
...@@ -5271,69 +5540,6 @@ ...@@ -5271,69 +5540,6 @@
], ],
"time": "2021-05-26T17:57:12+00:00" "time": "2021-05-26T17:57:12+00:00"
}, },
{
"name": "symfony/deprecation-contracts",
"version": "v2.4.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627",
"reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.4-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"autoload": {
"files": ["function.php"]
},
"notification-url": "https://packagist.org/downloads/",
"license": ["MIT"],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2021-03-23T23:28:01+00:00"
},
{ {
"name": "symfony/error-handler", "name": "symfony/error-handler",
"version": "v5.3.0", "version": "v5.3.0",
......
...@@ -27,15 +27,9 @@ class ActivityRequest ...@@ -27,15 +27,9 @@ class ActivityRequest
protected ?Activity $activity = null; protected ?Activity $activity = null;
/** /**
* @var array<string, string[]> * @var array<string, mixed>
*/ */
protected array $options = [ protected array $options = [];
'headers' => [
'Content-Type' => 'application/activity+json',
'Accept' => 'application/activity+json',
// TODO: outgoing and incoming requests
],
];
public function __construct(string $uri, ?string $activityPayload = null) public function __construct(string $uri, ?string $activityPayload = null)
{ {
...@@ -45,12 +39,21 @@ class ActivityRequest ...@@ -45,12 +39,21 @@ class ActivityRequest
$this->request->setBody($activityPayload); $this->request->setBody($activityPayload);
} }
$this->options = [
'headers' => [
'Content-Type' => 'application/activity+json',
'Accept' => 'application/activity+json',
'User-Agent' => 'Castopod/' . CP_VERSION . '; +' . base_url('', 'https'),
// TODO: outgoing and incoming requests
],
];
$this->uri = new URI($uri); $this->uri = new URI($uri);
} }
public function post(): void public function post(): void
{ {
// send Message to Fediverse instance // outgoing message to Fediverse instance
$this->request->post((string) $this->uri, $this->options); $this->request->post((string) $this->uri, $this->options);
} }
...@@ -80,7 +83,7 @@ class ActivityRequest ...@@ -80,7 +83,7 @@ class ActivityRequest
$digest = 'SHA-256=' . base64_encode($this->getBodyDigest()); $digest = 'SHA-256=' . base64_encode($this->getBodyDigest());
$contentType = $this->options['headers']['Content-Type']; $contentType = $this->options['headers']['Content-Type'];
$contentLength = (string) strlen($this->request->getBody()); $contentLength = (string) strlen($this->request->getBody());
$userAgent = 'Castopod'; $userAgent = 'Castopod/' . CP_VERSION . '; +' . base_url('', 'https');
$plainText = "(request-target): post {$path}\nhost: {$host}\ndate: {$date}\ndigest: {$digest}\ncontent-type: {$contentType}\ncontent-length: {$contentLength}\nuser-agent: {$userAgent}"; $plainText = "(request-target): post {$path}\nhost: {$host}\ndate: {$date}\ndigest: {$digest}\ncontent-type: {$contentType}\ncontent-length: {$contentLength}\nuser-agent: {$userAgent}";
......
...@@ -108,12 +108,13 @@ class ActorController extends Controller ...@@ -108,12 +108,13 @@ class ActorController extends Controller
if ($replyToPost !== null) { if ($replyToPost !== null) {
// TODO: strip content from html to retrieve message // TODO: strip content from html to retrieve message
// remove all html tags and reconstruct message with mentions? // remove all html tags and reconstruct message with mentions?
extract_text_from_html($payload->object->content); $message = get_message_from_object($payload->object);
$reply = new Post([ $reply = new Post([
'uri' => $payload->object->id, 'uri' => $payload->object->id,
'actor_id' => $payloadActor->id, 'actor_id' => $payloadActor->id,
'in_reply_to_id' => $replyToPost->id, 'in_reply_to_id' => $replyToPost->id,
'message' => $payload->object->content, 'message' => $message,
'published_at' => Time::parse($payload->object->published), 'published_at' => Time::parse($payload->object->published),
]); ]);
} }
......
...@@ -27,11 +27,22 @@ class SchedulerController extends Controller ...@@ -27,11 +27,22 @@ class SchedulerController extends Controller
// Send activity to all followers // Send activity to all followers
foreach ($scheduledActivities as $scheduledActivity) { foreach ($scheduledActivities as $scheduledActivity) {
// send activity to all actor followers if ($scheduledActivity->target_actor_id !== null) {
send_activity_to_followers( if ($scheduledActivity->actor_id !== $scheduledActivity->target_actor_id) {
$scheduledActivity->actor, // send activity to targeted actor
json_encode($scheduledActivity->payload, JSON_THROW_ON_ERROR), send_activity_to_actor(
); $scheduledActivity->actor,
$scheduledActivity->targetActor,
json_encode($scheduledActivity->payload, JSON_THROW_ON_ERROR)
);
}
} else {
// send activity to all actor followers
send_activity_to_followers(
$scheduledActivity->actor,
json_encode($scheduledActivity->payload, JSON_THROW_ON_ERROR),
);
}
// set activity post to delivered // set activity post to delivered
model('ActivityModel') model('ActivityModel')
......
...@@ -22,7 +22,7 @@ use RuntimeException; ...@@ -22,7 +22,7 @@ use RuntimeException;
* @property string|null $summary * @property string|null $summary
* @property string|null $private_key * @property string|null $private_key
* @property string|null $public_key * @property string|null $public_key
* @property string|null $public_key_id * @property string $public_key_id
* @property string|null $avatar_image_url * @property string|null $avatar_image_url
* @property string|null $avatar_image_mimetype * @property string|null $avatar_image_mimetype
* @property string|null $cover_image_url * @property string|null $cover_image_url
......
...@@ -97,6 +97,25 @@ if (! function_exists('accept_follow')) { ...@@ -97,6 +97,25 @@ if (! function_exists('accept_follow')) {
} }
} }
if (! function_exists('send_activity_to_actor')) {
/**
* Sends an activity to all actor followers
*/
function send_activity_to_actor(Actor $actor, Actor $targetActor, string $activityPayload): void
{
try {
$acceptRequest = new ActivityRequest($targetActor->inbox_url, $activityPayload);
if ($actor->private_key !== null) {
$acceptRequest->sign($actor->public_key_id, $actor->private_key);
}
$acceptRequest->post();
} catch (Exception $exception) {
// log error
log_message('critical', $exception->getMessage());
}
}
}
if (! function_exists('send_activity_to_followers')) { if (! function_exists('send_activity_to_followers')) {
/** /**
* Sends an activity to all actor followers * Sends an activity to all actor followers
...@@ -104,14 +123,7 @@ if (! function_exists('send_activity_to_followers')) { ...@@ -104,14 +123,7 @@ if (! function_exists('send_activity_to_followers')) {
function send_activity_to_followers(Actor $actor, string $activityPayload): void function send_activity_to_followers(Actor $actor, string $activityPayload): void
{ {
foreach ($actor->followers as $follower) { foreach ($actor->followers as $follower) {
try { send_activity_to_actor($actor, $follower, $activityPayload);
$acceptRequest = new ActivityRequest($follower->inbox_url, $activityPayload);
$acceptRequest->sign($actor->public_key_id, $actor->private_key);
$acceptRequest->post();
} catch (Exception $exception) {
// log error
log_message('critical', $exception->getMessage());
}
} }
} }
} }
...@@ -261,7 +273,7 @@ if (! function_exists('create_actor_from_uri')) { ...@@ -261,7 +273,7 @@ if (! function_exists('create_actor_from_uri')) {
$newActor->public_key = $actorPayload->publicKey->publicKeyPem; $newActor->public_key = $actorPayload->publicKey->publicKeyPem;
$newActor->private_key = null; $newActor->private_key = null;
$newActor->display_name = $actorPayload->name; $newActor->display_name = $actorPayload->name;
$newActor->summary = $actorPayload->summary; $newActor->summary = property_exists($actorPayload, 'summary') ? $actorPayload->summary : null;
if (property_exists($actorPayload, 'icon')) { if (property_exists($actorPayload, 'icon')) {
$newActor->avatar_image_url = $actorPayload->icon->url; $newActor->avatar_image_url = $actorPayload->icon->url;
$newActor->avatar_image_mimetype = $actorPayload->icon->mediaType; $newActor->avatar_image_mimetype = $actorPayload->icon->mediaType;
...@@ -272,8 +284,8 @@ if (! function_exists('create_actor_from_uri')) { ...@@ -272,8 +284,8 @@ if (! function_exists('create_actor_from_uri')) {
$newActor->cover_image_mimetype = $actorPayload->image->mediaType; $newActor->cover_image_mimetype = $actorPayload->image->mediaType;
} }
$newActor->inbox_url = $actorPayload->inbox; $newActor->inbox_url = $actorPayload->inbox;
$newActor->outbox_url = $actorPayload->outbox; $newActor->outbox_url = property_exists($actorPayload, 'outbox') ? $actorPayload->outbox : null;
$newActor->followers_url = $actorPayload->followers; $newActor->followers_url = property_exists($actorPayload, 'followers') ? $actorPayload->followers : null;
if (! ($newActorId = model('ActorModel')->insert($newActor, true))) { if (! ($newActorId = model('ActorModel')->insert($newActor, true))) {
return null; return null;
...@@ -307,6 +319,36 @@ if (! function_exists('extract_text_from_html')) { ...@@ -307,6 +319,36 @@ if (! function_exists('extract_text_from_html')) {
} }
} }
if (! function_exists('get_message_from_object')) {
/**
* Gets the message from content, if no content key is present, checks for content in contentMap
*
* TODO: store multiple languages, convert markdown
*
* @return string|false
*/
function get_message_from_object(stdClass $object): string | false
{
if (property_exists($object, 'content')) {
extract_text_from_html($object->content);
return $object->content;
}
$message = '';
if (property_exists($object, 'contentMap')) {
// TODO: update message to be json? (include all languages?)
if (property_exists($object->contentMap, 'en')) {
extract_text_from_html($object->contentMap->en);
$message = $object->contentMap->en;
} else {
$message = current($object->contentMap);
}
}
return $message;
}
}
if (! function_exists('linkify')) { if (! function_exists('linkify')) {
/** /**
* Turn all link elements in clickable links. Transforms urls and handles * Turn all link elements in clickable links. Transforms urls and handles
......
...@@ -28,18 +28,14 @@ class HttpSignature ...@@ -28,18 +28,14 @@ class HttpSignature
/** /**
* @var string * @var string
*/ */
private const SIGNATURE_PATTERN = '/^ private const SIGNATURE_PATTERN = '/
keyId="(?P<keyId> (?=.*(keyId="(?P<keyId>https?:\/\/[\w\-\.]+[\w]+(:[\d]+)?[\w\-\.#\/@]+)"))
(https?:\/\/[\w\-\.]+[\w]+) (?=.*(signature="(?P<signature>[\w+\/]+={0,2})"))
(:[\d]+)? (?=.*(headers="\(request-target\)(?P<headers>[\w\\-\s]+)"))?
([\w\-\.#\/@]+) (?=.*(algorithm="(?P<algorithm>[\w\-]+)"))?
)",
algorithm="(?P<algorithm>[\w\-]+)",
(headers="\(request-target\) (?P<headers>[\w\\-\s]+)",)?
signature="(?P<signature>[\w+\/]+={0,2})"
/x'; /x';
protected ?IncomingRequest $request = null; protected IncomingRequest $request;
public function __construct(IncomingRequest $request = null) public function __construct(IncomingRequest $request = null)
{ {
...@@ -66,7 +62,8 @@ class HttpSignature ...@@ -66,7 +62,8 @@ class HttpSignature
$requestTime = Time::createFromFormat('D, d M Y H:i:s T', $dateHeader->getValue()); $requestTime = Time::createFromFormat('D, d M Y H:i:s T', $dateHeader->getValue());
$diff = $requestTime->difference($currentTime); $diff = $requestTime->difference($currentTime);
if ($diff->getSeconds() > 3600) { $diffSeconds = $diff->getSeconds();
if ($diffSeconds > 3600 || $diffSeconds < 0) {
throw new Exception('Request must be made within the last hour.'); throw new Exception('Request must be made within the last hour.');
} }
...@@ -74,6 +71,7 @@ class HttpSignature ...@@ -74,6 +71,7 @@ class HttpSignature
if (! ($digestHeader = $this->request->header('digest'))) { if (! ($digestHeader = $this->request->header('digest'))) {
throw new Exception('Request must include a digest header'); throw new Exception('Request must include a digest header');
} }
// compute body digest and compare with header digest // compute body digest and compare with header digest
$bodyDigest = hash('sha256', $this->request->getBody(), true); $bodyDigest = hash('sha256', $this->request->getBody(), true);
$digest = 'SHA-256=' . base64_encode($bodyDigest); $digest = 'SHA-256=' . base64_encode($bodyDigest);
...@@ -94,7 +92,8 @@ class HttpSignature ...@@ -94,7 +92,8 @@ class HttpSignature
// set $keyId, $headers and $signature variables // set $keyId, $headers and $signature variables
$keyId = $parts['keyId']; $keyId = $parts['keyId'];
$headers = $parts['headers']; $algorithm = $parts['algorithm'];
$headers = $parts['headers'] ?? 'date';
$signature = $parts['signature']; $signature = $parts['signature'];
// Fetch the public key linked from keyId // Fetch the public key linked from keyId
...@@ -102,19 +101,14 @@ class HttpSignature ...@@ -102,19 +101,14 @@ class HttpSignature
$actorResponse = $actorRequest->get(); $actorResponse = $actorRequest->get();
$actor = json_decode($actorResponse->getBody(), false, 512, JSON_THROW_ON_ERROR); $actor = json_decode($actorResponse->getBody(), false, 512, JSON_THROW_ON_ERROR);
$publicKeyPem = $actor->publicKey->publicKeyPem; $publicKeyPem = (string) $actor->publicKey->publicKeyPem;
// Create a comparison string from the plaintext headers we got // Create a comparison string from the plaintext headers we got
// in the same order as was given in the signature header, // in the same order as was given in the signature header,
$data = $this->getPlainText(explode(' ', trim($headers))); $data = $this->getPlainText(explode(' ', trim($headers)));
// Verify that string using the public key and the original signature. // Verify the data string using the public key and the original signature.
$rsa = new RSA(); return $this->verifySignature($publicKeyPem, $data, $signature, $algorithm);
$rsa->setHash('sha256');
$rsa->setSignatureMode(RSA::SIGNATURE_PKCS1);
$rsa->loadKey($publicKeyPem);
return $rsa->verify($data, base64_decode($signature, true));
} }
/** /**
...@@ -124,7 +118,7 @@ class HttpSignature ...@@ -124,7 +118,7 @@ class HttpSignature
*/ */
private function splitSignature(string $signature): array | false private function splitSignature(string $signature): array | false
{ {
if (! preg_match(self::SIGNATURE_PATTERN, $signature, $matches)) { if (! preg_match(self::SIGNATURE_PATTERN, $signature, $matches, PREG_UNMATCHED_AS_NULL)) {
// Signature pattern failed // Signature pattern failed
return false; return false;
} }
...@@ -162,4 +156,27 @@ class HttpSignature ...@@ -162,4 +156,27 @@ class HttpSignature
return implode("\n", $strings); return implode("\n", $strings);
} }
/**
* Verifies the signature depending on the algorithm sent
*/
private function verifySignature(
string $publicKeyPem,
string $data,
string $signature,
string $algorithm = 'rsa-sha256'
): bool {
if ($algorithm === 'rsa-sha512' || $algorithm === 'rsa-sha256') {
$hash = substr($algorithm, strpos($algorithm, '-') + 1);
$rsa = new RSA();
$rsa->setHash($hash);
$rsa->setSignatureMode(RSA::SIGNATURE_PKCS1);
$rsa->loadKey($publicKeyPem);
return $rsa->verify($data, (string) base64_decode($signature, true));
}
// not implemented
return false;
}
} }
...@@ -68,7 +68,7 @@ class FavouriteModel extends BaseUuidModel ...@@ -68,7 +68,7 @@ class FavouriteModel extends BaseUuidModel
->newActivity( ->newActivity(
'Like', 'Like',
$actor->id, $actor->id,
null, $post->actor_id,
$post->id, $post->id,
$likeActivity->toJSON(), $likeActivity->toJSON(),
$post->published_at, $post->published_at,
...@@ -134,7 +134,7 @@ class FavouriteModel extends BaseUuidModel ...@@ -134,7 +134,7 @@ class FavouriteModel extends BaseUuidModel
->newActivity( ->newActivity(
'Undo', 'Undo',
$actor->id, $actor->id,
null, $post->actor_id,
$post->id, $post->id,
$undoActivity->toJSON(), $undoActivity->toJSON(),
$post->published_at, $post->published_at,
......
...@@ -299,7 +299,7 @@ class PostModel extends BaseUuidModel ...@@ -299,7 +299,7 @@ class PostModel extends BaseUuidModel
->newActivity( ->newActivity(
'Create', 'Create',
$post->actor_id, $post->actor_id,
null, $post->in_reply_to_id === null ? null : $post->reply_to_post->actor_id,
$newPostId, $newPostId,
$createActivity->toJSON(), $createActivity->toJSON(),
$post->published_at, $post->published_at,
...@@ -499,7 +499,7 @@ class PostModel extends BaseUuidModel ...@@ -499,7 +499,7 @@ class PostModel extends BaseUuidModel
->newActivity( ->newActivity(
'Announce', 'Announce',
$actor->id, $actor->id,
null, $post->actor_id,
$post->id, $post->id,
$announceActivity->toJSON(), $announceActivity->toJSON(),
$reblog->published_at, $reblog->published_at,
...@@ -559,7 +559,7 @@ class PostModel extends BaseUuidModel ...@@ -559,7 +559,7 @@ class PostModel extends BaseUuidModel
->newActivity( ->newActivity(
'Undo', 'Undo',
$reblogPost->actor_id, $reblogPost->actor_id,
null, $reblogPost->reblog_of_post->actor_id,
$reblogPost->reblog_of_id, $reblogPost->reblog_of_id,
$undoActivity->toJSON(), $undoActivity->toJSON(),
Time::now(), Time::now(),
......
...@@ -39,6 +39,8 @@ class NoteObject extends ObjectType ...@@ -39,6 +39,8 @@ class NoteObject extends ObjectType
$this->attributedTo = $post->actor->uri; $this->attributedTo = $post->actor->uri;
if ($post->in_reply_to_id !== null) { if ($post->in_reply_to_id !== null) {
$this->to[] = $post->reply_to_post->actor->uri;
$this->inReplyTo = $post->reply_to_post->uri; $this->inReplyTo = $post->reply_to_post->uri;
} }
......