Yassine Doghri 2f525c0f6e feat(fediverse): implement activitypub protocols + update user interface
- add "ActivityPub" library to handle server to server federation and basic
  client to server protocols using activitypub:
  - add webfinger endpoint to look for actor
  - add actor definition with inbox / outbox / followers
  - remote follow an actor
  - create notes with possible preview cards
  - interract with favourites, reblogs and replies
  - block incoming actors and/or domains
  - broadcast/schedule activities to fediverse followers using a cron task
- For castopod, the podcast is the actor:
  - overwrite the activitypub library for castopod's specific needs
  - perform basic interactions administrating a podcast to interact with fediverse users:
    - create notes with episode attachment
    - favourite and share a note + reply
    - add specific castopod_namespaces for podcasts and episodes definitions
- overwrite CodeIgniter's Route service to include alternate-content option for
  activitystream requests
- update episode publication logic:
  - remove publication inputs in create / edit episode form
  - publish / schedule or unpublish an episode after creation
  - the podcaster publishes a note when publishing an episode
- Javascript / Typescript modules:
  - fix Dropdown.ts to keep dropdown menu in foreground
  - add Modal.ts for funding links modal
  - add Toggler.ts to toggle various css states in ui
- User Interface:
  - update tailwindcss to v2
  - use castopod's pine and rose colors
  - update public layout to a 3 column layout
  - add pages in public for podcast activity, episode list and notes
  - update episode page to include linked notes
  - remove previous and next episodes from episode pages
  - show different public views depending on whether user is authenticated or not
  - use Kumbh Sans and Montserrat fonts
- update CodeIgniter's config files
- with CodeIgniter's new requirements, update docker environments are now based on
  php v7.3 image
- move Image entity to Libraries
- update composer and npm packages to latest versions

closes #69 #65 #85, fixes #51 #91 #92 #88
2021-04-02 17:20:02 +00:00

207 lines
6.5 KiB
PHP

<?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;
}
}