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
This commit is contained in:
Yassine Doghri 2021-04-02 17:20:02 +00:00
parent dd3ac9b4ab
commit 2f525c0f6e
476 changed files with 24041 additions and 9081 deletions

View File

@ -1,8 +1,8 @@
FROM php:7.2-fpm
FROM php:7.3-fpm
COPY --from=composer /usr/bin/composer /usr/bin/composer
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash -
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
RUN apt-get update && \
apt-get install -y nodejs

11
.gitignore vendored
View File

@ -138,7 +138,6 @@ node_modules
# public folder
public/*
!public/media
!public/media/~person
!public/.htaccess
!public/favicon.ico
!public/index.php
@ -147,10 +146,14 @@ public/*
# public media folder
public/media/*
!public/media/index.html
!public/media/podcasts
!public/media/persons
# public person folder
public/media/~person/*
!public/media/~person/index.html
public/media/podcasts/*
!public/media/podcasts/index.html
public/media/persons/*
!public/media/persons/index.html
# Generated files
app/Language/en/PersonsTaxonomy.php

View File

@ -4,7 +4,7 @@
{
"files": "*.php",
"options": {
"phpVersion": "7.2",
"phpVersion": "7.3",
"singleQuote": true
}
},

View File

@ -7,7 +7,7 @@
+ writable/***
+ .env.example
+ DEPENDENCIES.md
+ LICENSE
+ LICENSE.md
+ README.md
+ INSTALL.md
- **

View File

@ -9,7 +9,8 @@
"apply",
"responsive",
"variants",
"screen"
"screen",
"layer"
]
}
],

17
.svgo.icons.js Normal file
View File

@ -0,0 +1,17 @@
module.exports = {
plugins: [
"removeXMLNS",
"removeDimensions",
"sortAttrs",
{
name: "addAttributesToSVGElement",
params: {
attributes: [
{ fill: "currentColor" },
{ width: "1em" },
{ height: "1em" },
],
},
},
],
};

View File

@ -1,9 +0,0 @@
plugins:
- removeXMLNS: true
- removeDimensions: true
- addAttributesToSVGElement:
attributes:
- fill: currentColor
- width: "1em"
- height: "1em"
- sortAttrs: true

12
.svgo.js Normal file
View File

@ -0,0 +1,12 @@
module.exports = {
plugins: [
{
name: "removeViewBox",
active: false,
},
"removeXMLNS",
"removeDimensions",
"sortAttrs",
"prefixIds",
],
};

View File

@ -1,5 +0,0 @@
plugins:
- removeXMLNS: true
- removeDimensions: true
- sortAttrs: true
- prefixIds: true

View File

@ -4,7 +4,7 @@ Castopod uses the following components:
PHP Dependencies:
- [Code Igniter 4](https://codeigniter.com)
- [CodeIgniter 4](https://codeigniter.com)
([MIT License](https://codeigniter.com/user_guide/license.html))
- [WhichBrowser/Parser-PHP](https://github.com/WhichBrowser/Parser-PHP)
([MIT License](https://github.com/WhichBrowser/Parser-PHP/blob/master/LICENSE))
@ -24,6 +24,14 @@ PHP Dependencies:
([MIT License](https://github.com/podlibre/user-agents-php/blob/main/LICENSE))
- [podlibre/ipcat](https://github.com/podlibre/ipcat)
([GNU General Public License v3.0](https://github.com/podlibre/ipcat/blob/master/LICENSE))
- [podlibre/podcast-namespace](https://code.podlibre.org/podlibre/podcastnamespace)
([MIT License](https://code.podlibre.org/podlibre/podcastnamespace/-/blob/master/LICENSE))
- [phpseclib](https://phpseclib.com/)
([MIT License](https://github.com/phpseclib/phpseclib/blob/master/LICENSE))
- [codeigniter4-uuid](https://github.com/michalsn/codeigniter4-uuid)
([MIT License](https://github.com/michalsn/codeigniter4-uuid/blob/develop/LICENSE))
- [essence](https://github.com/essence/essence)
([The FreeBSD License](https://github.com/essence/essence/blob/master/LICENSE.txt))
Javascript dependencies:
@ -39,9 +47,15 @@ Javascript dependencies:
([MIT License](https://github.com/jshjohnson/Choices/blob/master/LICENSE))
- [flatpickr](https://flatpickr.js.org/)
([MIT License](https://github.com/flatpickr/flatpickr/blob/master/LICENSE.md))
- [popperjs](https://popper.js.org/)
([MIT License](https://github.com/popperjs/popper-core/blob/master/LICENSE.md))
Other:
- [Kumbh Sans](https://fonts.google.com/specimen/Kumbh+Sans)
([Open Font License](https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL))
- [Montserrat](https://fonts.google.com/specimen/Montserrat)
([Open Font License](https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL))
- [RemixIcon](https://remixicon.com/)
([Apache License 2.0](https://github.com/Remix-Design/RemixIcon/blob/master/License))
- [OPAWG/User agent list](https://github.com/opawg/user-agents)

View File

@ -1,4 +1,4 @@
FROM php:7.2-fpm
FROM php:7.3-fpm
COPY . /castopod
WORKDIR /castopod
@ -25,3 +25,9 @@ RUN echo "file_uploads = On\n" \
"post_max_size = 120M\n" \
"max_execution_time = 300\n" \
> /usr/local/etc/php/conf.d/uploads.ini
# install cron
RUN apt-get update && \
apt-get install -y cron
RUN crontab /castopod/crontab

View File

@ -1,13 +1,16 @@
# How to install Castopod
# How to install Castopod <!-- omit in toc -->
Castopod was thought to be easy to install. Whether using dedicated or shared
hosting, you can install it on most PHP-MySQL compatible web servers.
## Table of contents <!-- omit in toc -->
- [Install instructions](#install-instructions)
- [(optional) Manual configuration](#optional-manual-configuration)
- [Web Server Requirements](#web-server-requirements)
- [PHP v7.2 or higher](#php-v72-or-higher)
- [PHP v7.3 or higher](#php-v73-or-higher)
- [MySQL compatible database](#mysql-compatible-database)
- [Privileges](#privileges)
- [(Optional) Other recommendations](#optional-other-recommendations)
- [Security concerns](#security-concerns)
@ -19,9 +22,16 @@ hosting, you can install it on most PHP-MySQL compatible web servers.
1. Download and unzip the Castopod package onto the web server if you havent
already.
- ⚠️ Set the web server document root to the `public/` sub-folder.
2. Run the Castopod install script by going to the install wizard page
2. ⚠️ For broadcasting social activities to the fediverse, add a cron task on
your web server to run every minute (replace the paths accordingly):
```php
* * * * * /path/to/php /path/to/castopod/public/index.php scheduled-activities
```
3. Run the Castopod install script by going to the install wizard page
(`https://your_domain_name.com/cp-install`) in your favorite web browser.
3. Follow the instructions on your screen.
4. Follow the instructions on your screen.
All done, start podcasting!
@ -36,13 +46,12 @@ Before uploading Castopod files to your web server:
## Web Server Requirements
### PHP v7.2 or higher
### PHP v7.3 or higher
PHP version 7.2 or higher is required, with the following extensions installed:
PHP version 7.3 or higher is required, with the following extensions installed:
- [intl](http://php.net/manual/en/intl.requirements.php)
- [libcurl](http://php.net/manual/en/curl.requirements.php) if you plan to use
the HTTP\CURLRequest library
- [libcurl](http://php.net/manual/en/curl.requirements.php)
- [mbstring](http://php.net/manual/en/mbstring.installation.php)
Additionally, make sure that the following extensions are enabled in your PHP:

661
LICENSE
View File

@ -1,661 +0,0 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Castopod
Copyright (C) 2020 Podlibre
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<http://www.gnu.org/licenses/>.

598
LICENSE.md Normal file
View File

@ -0,0 +1,598 @@
# GNU Affero General Public License
_Version 3, 19 November 2007_ _Copyright © 2007 Free Software Foundation, Inc.
&lt;<http://fsf.org/>&gt;_
Everyone is permitted to copy and distribute verbatim copies of this license
document, but changing it is not allowed.
## Preamble
The GNU Affero General Public License is a free, copyleft license for software
and other kinds of works, specifically designed to ensure cooperation with the
community in the case of network server software.
The licenses for most software and other practical works are designed to take
away your freedom to share and change the works. By contrast, our General Public
Licenses are intended to guarantee your freedom to share and change all versions
of a program--to make sure it remains free software for all its users.
When we speak of free software, we are referring to freedom, not price. Our
General Public Licenses are designed to make sure that you have the freedom to
distribute copies of free software (and charge for them if you wish), that you
receive source code or can get it if you want it, that you can change the
software or use pieces of it in new free programs, and that you know you can do
these things.
Developers that use our General Public Licenses protect your rights with two
steps: **(1)** assert copyright on the software, and **(2)** offer you this
License which gives you legal permission to copy, distribute and/or modify the
software.
A secondary benefit of defending all users' freedom is that improvements made in
alternate versions of the program, if they receive widespread use, become
available for other developers to incorporate. Many developers of free software
are heartened and encouraged by the resulting cooperation. However, in the case
of software used on network servers, this result may fail to come about. The GNU
General Public License permits making a modified version and letting the public
access it on a server without ever releasing its source code to the public.
The GNU Affero General Public License is designed specifically to ensure that,
in such cases, the modified source code becomes available to the community. It
requires the operator of a network server to provide the source code of the
modified version running there to the users of that server. Therefore, public
use of a modified version, on a publicly accessible server, gives the public
access to the source code of the modified version.
An older license, called the Affero General Public License and published by
Affero, was designed to accomplish similar goals. This is a different license,
not a version of the Affero GPL, but Affero has released a new version of the
Affero GPL which permits relicensing under this license.
The precise terms and conditions for copying, distribution and modification
follow.
## TERMS AND CONDITIONS
### 0. Definitions
“This License” refers to version 3 of the GNU Affero General Public License.
“Copyright” also means copyright-like laws that apply to other kinds of works,
such as semiconductor masks.
“The Program” refers to any copyrightable work licensed under this License. Each
licensee is addressed as “you”. “Licensees” and “recipients” may be individuals
or organizations.
To “modify” a work means to copy from or adapt all or part of the work in a
fashion requiring copyright permission, other than the making of an exact copy.
The resulting work is called a “modified version” of the earlier work or a work
“based on” the earlier work.
A “covered work” means either the unmodified Program or a work based on the
Program.
To “propagate” a work means to do anything with it that, without permission,
would make you directly or secondarily liable for infringement under applicable
copyright law, except executing it on a computer or modifying a private copy.
Propagation includes copying, distribution (with or without modification),
making available to the public, and in some countries other activities as well.
To “convey” a work means any kind of propagation that enables other parties to
make or receive copies. Mere interaction with a user through a computer network,
with no transfer of a copy, is not conveying.
An interactive user interface displays “Appropriate Legal Notices” to the extent
that it includes a convenient and prominently visible feature that **(1)**
displays an appropriate copyright notice, and **(2)** tells the user that there
is no warranty for the work (except to the extent that warranties are provided),
that licensees may convey the work under this License, and how to view a copy of
this License. If the interface presents a list of user commands or options, such
as a menu, a prominent item in the list meets this criterion.
### 1. Source Code
The “source code” for a work means the preferred form of the work for making
modifications to it. “Object code” means any non-source form of a work.
A “Standard Interface” means an interface that either is an official standard
defined by a recognized standards body, or, in the case of interfaces specified
for a particular programming language, one that is widely used among developers
working in that language.
The “System Libraries” of an executable work include anything, other than the
work as a whole, that **(a)** is included in the normal form of packaging a
Major Component, but which is not part of that Major Component, and **(b)**
serves only to enable use of the work with that Major Component, or to implement
a Standard Interface for which an implementation is available to the public in
source code form. A “Major Component”, in this context, means a major essential
component (kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to produce the
work, or an object code interpreter used to run it.
The “Corresponding Source” for a work in object code form means all the source
code needed to generate, install, and (for an executable work) run the object
code and to modify the work, including scripts to control those activities.
However, it does not include the work's System Libraries, or general-purpose
tools or generally available free programs which are used unmodified in
performing those activities but which are not part of the work. For example,
Corresponding Source includes interface definition files associated with source
files for the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require, such as by
intimate data communication or control flow between those subprograms and other
parts of the work.
The Corresponding Source need not include anything that users can regenerate
automatically from other parts of the Corresponding Source.
The Corresponding Source for a work in source code form is that same work.
### 2. Basic Permissions
All rights granted under this License are granted for the term of copyright on
the Program, and are irrevocable provided the stated conditions are met. This
License explicitly affirms your unlimited permission to run the unmodified
Program. The output from running a covered work is covered by this License only
if the output, given its content, constitutes a covered work. This License
acknowledges your rights of fair use or other equivalent, as provided by
copyright law.
You may make, run and propagate covered works that you do not convey, without
conditions so long as your license otherwise remains in force. You may convey
covered works to others for the sole purpose of having them make modifications
exclusively for you, or provide you with facilities for running those works,
provided that you comply with the terms of this License in conveying all
material for which you do not control copyright. Those thus making or running
the covered works for you must do so exclusively on your behalf, under your
direction and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under the conditions
stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
### 3. Protecting Users' Legal Rights From Anti-Circumvention Law
No covered work shall be deemed part of an effective technological measure under
any applicable law fulfilling obligations under article 11 of the WIPO copyright
treaty adopted on 20 December 1996, or similar laws prohibiting or restricting
circumvention of such measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention is
effected by exercising rights under this License with respect to the covered
work, and you disclaim any intention to limit operation or modification of the
work as a means of enforcing, against the work's users, your or third parties'
legal rights to forbid circumvention of technological measures.
### 4. Conveying Verbatim Copies
You may convey verbatim copies of the Program's source code as you receive it,
in any medium, provided that you conspicuously and appropriately publish on each
copy an appropriate copyright notice; keep intact all notices stating that this
License and any non-permissive terms added in accord with section 7 apply to the
code; keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey, and you may
offer support or warranty protection for a fee.
### 5. Conveying Modified Source Versions
You may convey a work based on the Program, or the modifications to produce it
from the Program, in the form of source code under the terms of section 4,
provided that you also meet all of these conditions:
- **a)** The work must carry prominent notices stating that you modified it, and
giving a relevant date.
- **b)** The work must carry prominent notices stating that it is released under
this License and any conditions added under section 7. This requirement
modifies the requirement in section 4 to “keep intact all notices”.
- **c)** You must license the entire work, as a whole, under this License to
anyone who comes into possession of a copy. This License will therefore apply,
along with any applicable section 7 additional terms, to the whole of the
work, and all its parts, regardless of how they are packaged. This License
gives no permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
- **d)** If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive interfaces
that do not display Appropriate Legal Notices, your work need not make them do
so.
A compilation of a covered work with other separate and independent works, which
are not by their nature extensions of the covered work, and which are not
combined with it such as to form a larger program, in or on a volume of a
storage or distribution medium, is called an “aggregate” if the compilation and
its resulting copyright are not used to limit the access or legal rights of the
compilation's users beyond what the individual works permit. Inclusion of a
covered work in an aggregate does not cause this License to apply to the other
parts of the aggregate.
### 6. Conveying Non-Source Forms
You may convey a covered work in object code form under the terms of sections 4
and 5, provided that you also convey the machine-readable Corresponding Source
under the terms of this License, in one of these ways:
- **a)** Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the Corresponding
Source fixed on a durable physical medium customarily used for software
interchange.
- **b)** Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a written offer,
valid for at least three years and valid for as long as you offer spare parts
or customer support for that product model, to give anyone who possesses the
object code either **(1)** a copy of the Corresponding Source for all the
software in the product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no more than
your reasonable cost of physically performing this conveying of source, or
**(2)** access to copy the Corresponding Source from a network server at no
charge.
- **c)** Convey individual copies of the object code with a copy of the written
offer to provide the Corresponding Source. This alternative is allowed only
occasionally and noncommercially, and only if you received the object code
with such an offer, in accord with subsection 6b.
- **d)** Convey the object code by offering access from a designated place
(gratis or for a charge), and offer equivalent access to the Corresponding
Source in the same way through the same place at no further charge. You need
not require recipients to copy the Corresponding Source along with the object
code. If the place to copy the object code is a network server, the
Corresponding Source may be on a different server (operated by you or a third
party) that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the Corresponding
Source, you remain obligated to ensure that it is available for as long as
needed to satisfy these requirements.
- **e)** Convey the object code using peer-to-peer transmission, provided you
inform other peers where the object code and Corresponding Source of the work
are being offered to the general public at no charge under subsection 6d.
A separable portion of the object code, whose source code is excluded from the
Corresponding Source as a System Library, need not be included in conveying the
object code work.
A “User Product” is either **(1)** a “consumer product”, which means any
tangible personal property which is normally used for personal, family, or
household purposes, or **(2)** anything designed or sold for incorporation into
a dwelling. In determining whether a product is a consumer product, doubtful
cases shall be resolved in favor of coverage. For a particular product received
by a particular user, “normally used” refers to a typical or common use of that
class of product, regardless of the status of the particular user or of the way
in which the particular user actually uses, or expects or is expected to use,
the product. A product is a consumer product regardless of whether the product
has substantial commercial, industrial or non-consumer uses, unless such uses
represent the only significant mode of use of the product.
“Installation Information” for a User Product means any methods, procedures,
authorization keys, or other information required to install and execute
modified versions of a covered work in that User Product from a modified version
of its Corresponding Source. The information must suffice to ensure that the
continued functioning of the modified object code is in no case prevented or
interfered with solely because modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as part of a
transaction in which the right of possession and use of the User Product is
transferred to the recipient in perpetuity or for a fixed term (regardless of
how the transaction is characterized), the Corresponding Source conveyed under
this section must be accompanied by the Installation Information. But this
requirement does not apply if neither you nor any third party retains the
ability to install modified object code on the User Product (for example, the
work has been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates for a
work that has been modified or installed by the recipient, or for the User
Product in which it has been modified or installed. Access to a network may be
denied when the modification itself materially and adversely affects the
operation of the network or violates the rules and protocols for communication
across the network.
Corresponding Source conveyed, and Installation Information provided, in accord
with this section must be in a format that is publicly documented (and with an
implementation available to the public in source code form), and must require no
special password or key for unpacking, reading or copying.
### 7. Additional Terms
“Additional permissions” are terms that supplement the terms of this License by
making exceptions from one or more of its conditions. Additional permissions
that are applicable to the entire Program shall be treated as though they were
included in this License, to the extent that they are valid under applicable
law. If additional permissions apply only to part of the Program, that part may
be used separately under those permissions, but the entire Program remains
governed by this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option remove any
additional permissions from that copy, or from any part of it. (Additional
permissions may be written to require their own removal in certain cases when
you modify the work.) You may place additional permissions on material, added by
you to a covered work, for which you have or can give appropriate copyright
permission.
Notwithstanding any other provision of this License, for material you add to a
covered work, you may (if authorized by the copyright holders of that material)
supplement the terms of this License with terms:
- **a)** Disclaiming warranty or limiting liability differently from the terms
of sections 15 and 16 of this License; or
- **b)** Requiring preservation of specified reasonable legal notices or author
attributions in that material or in the Appropriate Legal Notices displayed by
works containing it; or
- **c)** Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in reasonable ways
as different from the original version; or
- **d)** Limiting the use for publicity purposes of names of licensors or
authors of the material; or
- **e)** Declining to grant rights under trademark law for use of some trade
names, trademarks, or service marks; or
- **f)** Requiring indemnification of licensors and authors of that material by
anyone who conveys the material (or modified versions of it) with contractual
assumptions of liability to the recipient, for any liability that these
contractual assumptions directly impose on those licensors and authors.
All other non-permissive additional terms are considered “further restrictions”
within the meaning of section 10. If the Program as you received it, or any part
of it, contains a notice stating that it is governed by this License along with
a term that is a further restriction, you may remove that term. If a license
document contains a further restriction but permits relicensing or conveying
under this License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does not survive
such relicensing or conveying.
If you add terms to a covered work in accord with this section, you must place,
in the relevant source files, a statement of the additional terms that apply to
those files, or a notice indicating where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the form of a
separately written license, or stated as exceptions; the above requirements
apply either way.
### 8. Termination
You may not propagate or modify a covered work except as expressly provided
under this License. Any attempt otherwise to propagate or modify it is void, and
will automatically terminate your rights under this License (including any
patent licenses granted under the third paragraph of section 11).
However, if you cease all violation of this License, then your license from a
particular copyright holder is reinstated **(a)** provisionally, unless and
until the copyright holder explicitly and finally terminates your license, and
**(b)** permanently, if the copyright holder fails to notify you of the
violation by some reasonable means prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is reinstated
permanently if the copyright holder notifies you of the violation by some
reasonable means, this is the first time you have received notice of violation
of this License (for any work) from that copyright holder, and you cure the
violation prior to 30 days after your receipt of the notice.
Termination of your rights under this section does not terminate the licenses of
parties who have received copies or rights from you under this License. If your
rights have been terminated and not permanently reinstated, you do not qualify
to receive new licenses for the same material under section 10.
### 9. Acceptance Not Required for Having Copies
You are not required to accept this License in order to receive or run a copy of
the Program. Ancillary propagation of a covered work occurring solely as a
consequence of using peer-to-peer transmission to receive a copy likewise does
not require acceptance. However, nothing other than this License grants you
permission to propagate or modify any covered work. These actions infringe
copyright if you do not accept this License. Therefore, by modifying or
propagating a covered work, you indicate your acceptance of this License to do
so.
### 10. Automatic Licensing of Downstream Recipients
Each time you convey a covered work, the recipient automatically receives a
license from the original licensors, to run, modify and propagate that work,
subject to this License. You are not responsible for enforcing compliance by
third parties with this License.
An “entity transaction” is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered work results
from an entity transaction, each party to that transaction who receives a copy
of the work also receives whatever licenses to the work the party's predecessor
in interest had or could give under the previous paragraph, plus a right to
possession of the Corresponding Source of the work from the predecessor in
interest, if the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the rights
granted or affirmed under this License. For example, you may not impose a
license fee, royalty, or other charge for exercise of rights granted under this
License, and you may not initiate litigation (including a cross-claim or
counterclaim in a lawsuit) alleging that any patent claim is infringed by
making, using, selling, offering for sale, or importing the Program or any
portion of it.
### 11. Patents
A “contributor” is a copyright holder who authorizes use under this License of
the Program or a work on which the Program is based. The work thus licensed is
called the contributor's “contributor version”.
A contributor's “essential patent claims” are all patent claims owned or
controlled by the contributor, whether already acquired or hereafter acquired,
that would be infringed by some manner, permitted by this License, of making,
using, or selling its contributor version, but do not include claims that would
be infringed only as a consequence of further modification of the contributor
version. For purposes of this definition, “control” includes the right to grant
patent sublicenses in a manner consistent with the requirements of this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free patent
license under the contributor's essential patent claims, to make, use, sell,
offer for sale, import and otherwise run, modify and propagate the contents of
its contributor version.
In the following three paragraphs, a “patent license” is any express agreement
or commitment, however denominated, not to enforce a patent (such as an express
permission to practice a patent or covenant not to sue for patent infringement).
To “grant” such a patent license to a party means to make such an agreement or
commitment not to enforce a patent against the party.
If you convey a covered work, knowingly relying on a patent license, and the
Corresponding Source of the work is not available for anyone to copy, free of
charge and under the terms of this License, through a publicly available network
server or other readily accessible means, then you must either **(1)** cause the
Corresponding Source to be so available, or **(2)** arrange to deprive yourself
of the benefit of the patent license for this particular work, or **(3)**
arrange, in a manner consistent with the requirements of this License, to extend
the patent license to downstream recipients. “Knowingly relying” means you have
actual knowledge that, but for the patent license, your conveying the covered
work in a country, or your recipient's use of the covered work in a country,
would infringe one or more identifiable patents in that country that you have
reason to believe are valid.
If, pursuant to or in connection with a single transaction or arrangement, you
convey, or propagate by procuring conveyance of, a covered work, and grant a
patent license to some of the parties receiving the covered work authorizing
them to use, propagate, modify or convey a specific copy of the covered work,
then the patent license you grant is automatically extended to all recipients of
the covered work and works based on it.
A patent license is “discriminatory” if it does not include within the scope of
its coverage, prohibits the exercise of, or is conditioned on the non-exercise
of one or more of the rights that are specifically granted under this License.
You may not convey a covered work if you are a party to an arrangement with a
third party that is in the business of distributing software, under which you
make payment to the third party based on the extent of your activity of
conveying the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory patent
license **(a)** in connection with copies of the covered work conveyed by you
(or copies made from those copies), or **(b)** primarily for and in connection
with specific products or compilations that contain the covered work, unless you
entered into that arrangement, or that patent license was granted, prior to 28
March 2007.
Nothing in this License shall be construed as excluding or limiting any implied
license or other defenses to infringement that may otherwise be available to you
under applicable patent law.
### 12. No Surrender of Others' Freedom
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not excuse
you from the conditions of this License. If you cannot convey a covered work so
as to satisfy simultaneously your obligations under this License and any other
pertinent obligations, then as a consequence you may not convey it at all. For
example, if you agree to terms that obligate you to collect a royalty for
further conveying from those to whom you convey the Program, the only way you
could satisfy both those terms and this License would be to refrain entirely
from conveying the Program.
### 13. Remote Network Interaction; Use with the GNU General Public License
Notwithstanding any other provision of this License, if you modify the Program,
your modified version must prominently offer all users interacting with it
remotely through a computer network (if your version supports such interaction)
an opportunity to receive the Corresponding Source of your version by providing
access to the Corresponding Source from a network server at no charge, through
some standard or customary means of facilitating copying of software. This
Corresponding Source shall include the Corresponding Source for any work covered
by version 3 of the GNU General Public License that is incorporated pursuant to
the following paragraph.
Notwithstanding any other provision of this License, you have permission to link
or combine any covered work with a work licensed under version 3 of the GNU
General Public License into a single combined work, and to convey the resulting
work. The terms of this License will continue to apply to the part which is the
covered work, but the work with which it is combined will remain governed by
version 3 of the GNU General Public License.
### 14. Revised Versions of this License
The Free Software Foundation may publish revised and/or new versions of the GNU
Affero General Public License from time to time. Such new versions will be
similar in spirit to the present version, but may differ in detail to address
new problems or concerns.
Each version is given a distinguishing version number. If the Program specifies
that a certain numbered version of the GNU Affero General Public License “or any
later version” applies to it, you have the option of following the terms and
conditions either of that numbered version or of any later version published by
the Free Software Foundation. If the Program does not specify a version number
of the GNU Affero General Public License, you may choose any version ever
published by the Free Software Foundation.
If the Program specifies that a proxy can decide which future versions of the
GNU Affero General Public License can be used, that proxy's public statement of
acceptance of a version permanently authorizes you to choose that version for
the Program.
Later license versions may give you additional or different permissions.
However, no additional obligations are imposed on any author or copyright holder
as a result of your choosing to follow a later version.
### 15. Disclaimer of Warranty
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER
PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER
EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE
QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
### 16. Limitation of Liability
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY
COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS
PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL,
INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED
INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE
PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY
HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
### 17. Interpretation of Sections 15 and 16
If the disclaimer of warranty and limitation of liability provided above cannot
be given local legal effect according to their terms, reviewing courts shall
apply local law that most closely approximates an absolute waiver of all civil
liability in connection with the Program, unless a warranty or assumption of
liability accompanies a copy of the Program in return for a fee.
_END OF TERMS AND CONDITIONS_
## How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest possible use
to the public, the best way to achieve this is to make it free software which
everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest to attach
them to the start of each source file to most effectively state the exclusion of
warranty; and each file should have at least the “copyright” line and a pointer
to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer network,
you should also make sure that it provides a way for users to get its source.
For example, if your program is a web application, its interface could display a
“Source” link that leads users to an archive of the code. There are many ways
you could offer source, and different solutions will be better for different
programs; see section 13 for the specific requirements.
You should also get your employer (if you work as a programmer) or school, if
any, to sign a “copyright disclaimer” for the program, if necessary. For more
information on this, and how to apply and follow the GNU AGPL, see
&lt;<http://www.gnu.org/licenses/>&gt;.

View File

@ -3,12 +3,12 @@
Castopod is an open-source podcast hosting solution for everyone.\
Whether you are a beginner, an amateur or a professional, you will get everything
you need:\
Create, upload, publish, and get comprehensive audience measurement that respects your
listeners privacy.
Create, upload, publish, and get comprehensive audience measurement that
respects your listeners privacy.
Castopod is a free and open-source solution (AGPL v3).\
Whether you choose to install it on your own server or have it hosted by a
professional, all your data and analytics belong to you and you only.
Whether you choose to install it on your own server or have it hosted by a professional,
all your data and analytics belong to you and you only.
![Castopod Logo](https://podlibre.org/static/images/Castopod-Mascot-Server.svg)
@ -18,7 +18,9 @@ Castopod can be hosted on any PHP/MySQL server:\
Unzip it and you are ready to broadcast.
To install Castopod on your server:
- Download [Castopod latest Package (zip or tar.gz)](https://code.podlibre.org/podlibre/castopod/-/releases),
- Download
[Castopod latest Package (zip or tar.gz)](https://code.podlibre.org/podlibre/castopod/-/releases),
- Follow the procedure “[How to install Castopod](./INSTALL.md)”.
## Documentation

View File

@ -0,0 +1,14 @@
<?php namespace Config;
use ActivityPub\Config\ActivityPub as ActivityPubBase;
class ActivityPub extends ActivityPubBase
{
/**
* --------------------------------------------------------------------
* ActivityPub Objects
* --------------------------------------------------------------------
*/
public $actorObject = 'App\Libraries\PodcastActor';
public $noteObject = 'App\Libraries\NoteObject';
}

View File

@ -6,309 +6,487 @@ use CodeIgniter\Config\BaseConfig;
class App extends BaseConfig
{
/*
|--------------------------------------------------------------------------
| Base Site URL
|--------------------------------------------------------------------------
|
| URL to your CodeIgniter root. Typically this will be your base URL,
| WITH a trailing slash:
|
| http://example.com/
|
| If this is not set then CodeIgniter will try guess the protocol, domain
| and path to your installation. However, you should always configure this
| explicitly and never rely on auto-guessing, especially in production
| environments.
|
*/
public $baseURL = 'http://127.0.0.1:8080/';
/**
* --------------------------------------------------------------------------
* Base Site URL
* --------------------------------------------------------------------------
*
* URL to your CodeIgniter root. Typically this will be your base URL,
* WITH a trailing slash:
*
* http://example.com/
*
* If this is not set then CodeIgniter will try guess the protocol, domain
* and path to your installation. However, you should always configure this
* explicitly and never rely on auto-guessing, especially in production
* environments.
*
* @var string
*/
public $baseURL = 'http://localhost:8080/';
/*
|--------------------------------------------------------------------------
| Media Base URL
|--------------------------------------------------------------------------
|
| URL to your media root. Typically this will be your base URL,
| WITH a trailing slash:
|
| http://cdn.example.com/
|
*/
/**
* --------------------------------------------------------------------------
* Media Base URL
* --------------------------------------------------------------------------
*
* URL to your media root. Typically this will be your base URL,
* WITH a trailing slash:
*
* http://cdn.example.com/
*/
public $mediaBaseURL = 'http://127.0.0.2:8080/';
/*
|--------------------------------------------------------------------------
| Index File
|--------------------------------------------------------------------------
|
| Typically this will be your index.php file, unless you've renamed it to
| something else. If you are using mod_rewrite to remove the page set this
| variable so that it is blank.
|
*/
/**
* --------------------------------------------------------------------------
* Index File
* --------------------------------------------------------------------------
*
* Typically this will be your index.php file, unless you've renamed it to
* something else. If you are using mod_rewrite to remove the page set this
* variable so that it is blank.
*
* @var string
*/
public $indexPage = '';
/*
|--------------------------------------------------------------------------
| URI PROTOCOL
|--------------------------------------------------------------------------
|
| This item determines which getServer global should be used to retrieve the
| URI string. The default setting of 'REQUEST_URI' works for most servers.
| If your links do not seem to work, try one of the other delicious flavors:
|
| 'REQUEST_URI' Uses $_SERVER['REQUEST_URI']
| 'QUERY_STRING' Uses $_SERVER['QUERY_STRING']
| 'PATH_INFO' Uses $_SERVER['PATH_INFO']
|
| WARNING: If you set this to 'PATH_INFO', URIs will always be URL-decoded!
*/
/**
* --------------------------------------------------------------------------
* URI PROTOCOL
* --------------------------------------------------------------------------
*
* This item determines which getServer global should be used to retrieve the
* URI string. The default setting of 'REQUEST_URI' works for most servers.
* If your links do not seem to work, try one of the other delicious flavors:
*
* 'REQUEST_URI' Uses $_SERVER['REQUEST_URI']
* 'QUERY_STRING' Uses $_SERVER['QUERY_STRING']
* 'PATH_INFO' Uses $_SERVER['PATH_INFO']
*
* WARNING: If you set this to 'PATH_INFO', URIs will always be URL-decoded!
*
* @var string
*/
public $uriProtocol = 'REQUEST_URI';
/*
|--------------------------------------------------------------------------
| Default Locale
|--------------------------------------------------------------------------
|
| The Locale roughly represents the language and location that your visitor
| is viewing the site from. It affects the language strings and other
| strings (like currency markers, numbers, etc), that your program
| should run under for this request.
|
*/
/**
* --------------------------------------------------------------------------
* Default Locale
* --------------------------------------------------------------------------
*
* The Locale roughly represents the language and location that your visitor
* is viewing the site from. It affects the language strings and other
* strings (like currency markers, numbers, etc), that your program
* should run under for this request.
*
* @var string
*/
public $defaultLocale = 'en';
/*
|--------------------------------------------------------------------------
| Negotiate Locale
|--------------------------------------------------------------------------
|
| If true, the current Request object will automatically determine the
| language to use based on the value of the Accept-Language header.
|
| If false, no automatic detection will be performed.
|
*/
/**
* --------------------------------------------------------------------------
* Negotiate Locale
* --------------------------------------------------------------------------
*
* If true, the current Request object will automatically determine the
* language to use based on the value of the Accept-Language header.
*
* If false, no automatic detection will be performed.
*
* @var boolean
*/
public $negotiateLocale = true;
/*
|--------------------------------------------------------------------------
| Supported Locales
|--------------------------------------------------------------------------
|
| If $negotiateLocale is true, this array lists the locales supported
| by the application in descending order of priority. If no match is
| found, the first locale will be used.
|
*/
/**
* --------------------------------------------------------------------------
* Supported Locales
* --------------------------------------------------------------------------
*
* If $negotiateLocale is true, this array lists the locales supported
* by the application in descending order of priority. If no match is
* found, the first locale will be used.
*
* @var string[]
*/
public $supportedLocales = ['en', 'fr'];
/*
|--------------------------------------------------------------------------
| Application Timezone
|--------------------------------------------------------------------------
|
| The default timezone that will be used in your application to display
| dates with the date helper, and can be retrieved through app_timezone()
|
*/
/**
* --------------------------------------------------------------------------
* Application Timezone
* --------------------------------------------------------------------------
*
* The default timezone that will be used in your application to display
* dates with the date helper, and can be retrieved through app_timezone()
*
* @var string
*/
public $appTimezone = 'UTC';
/*
|--------------------------------------------------------------------------
| Default Character Set
|--------------------------------------------------------------------------
|
| This determines which character set is used by default in various methods
| that require a character set to be provided.
|
| See http://php.net/htmlspecialchars for a list of supported charsets.
|
*/
/**
* --------------------------------------------------------------------------
* Default Character Set
* --------------------------------------------------------------------------
*
* This determines which character set is used by default in various methods
* that require a character set to be provided.
*
* @see http://php.net/htmlspecialchars for a list of supported charsets.
*
* @var string
*/
public $charset = 'UTF-8';
/*
|--------------------------------------------------------------------------
| URI PROTOCOL
|--------------------------------------------------------------------------
|
| If true, this will force every request made to this application to be
| made via a secure connection (HTTPS). If the incoming request is not
| secure, the user will be redirected to a secure version of the page
| and the HTTP Strict Transport Security header will be set.
*/
/**
* --------------------------------------------------------------------------
* URI PROTOCOL
* --------------------------------------------------------------------------
*
* If true, this will force every request made to this application to be
* made via a secure connection (HTTPS). If the incoming request is not
* secure, the user will be redirected to a secure version of the page
* and the HTTP Strict Transport Security header will be set.
*
* @var boolean
*/
public $forceGlobalSecureRequests = false;
/*
|--------------------------------------------------------------------------
| Session Variables
|--------------------------------------------------------------------------
|
| 'sessionDriver'
|
| The storage driver to use: files, database, redis, memcached
| - CodeIgniter\Session\Handlers\FileHandler
| - CodeIgniter\Session\Handlers\DatabaseHandler
| - CodeIgniter\Session\Handlers\MemcachedHandler
| - CodeIgniter\Session\Handlers\RedisHandler
|
| 'sessionCookieName'
|
| The session cookie name, must contain only [0-9a-z_-] characters
|
| 'sessionExpiration'
|
| The number of SECONDS you want the session to last.
| Setting to 0 (zero) means expire when the browser is closed.
|
| 'sessionSavePath'
|
| The location to save sessions to, driver dependent.
|
| For the 'files' driver, it's a path to a writable directory.
| WARNING: Only absolute paths are supported!
|
| For the 'database' driver, it's a table name.
| Please read up the manual for the format with other session drivers.
|
| IMPORTANT: You are REQUIRED to set a valid save path!
|
| 'sessionMatchIP'
|
| Whether to match the user's IP address when reading the session data.
|
| WARNING: If you're using the database driver, don't forget to update
| your session table's PRIMARY KEY when changing this setting.
|
| 'sessionTimeToUpdate'
|
| How many seconds between CI regenerating the session ID.
|
| 'sessionRegenerateDestroy'
|
| Whether to destroy session data associated with the old session ID
| when auto-regenerating the session ID. When set to FALSE, the data
| will be later deleted by the garbage collector.
|
| Other session cookie settings are shared with the rest of the application,
| except for 'cookie_prefix' and 'cookie_httponly', which are ignored here.
|
*/
/**
* --------------------------------------------------------------------------
* Session Driver
* --------------------------------------------------------------------------
*
* The session storage driver to use:
* - `CodeIgniter\Session\Handlers\FileHandler`
* - `CodeIgniter\Session\Handlers\DatabaseHandler`
* - `CodeIgniter\Session\Handlers\MemcachedHandler`
* - `CodeIgniter\Session\Handlers\RedisHandler`
*
* @var string
*/
public $sessionDriver = 'CodeIgniter\Session\Handlers\FileHandler';
/**
* --------------------------------------------------------------------------
* Session Cookie Name
* --------------------------------------------------------------------------
*
* The session cookie name, must contain only [0-9a-z_-] characters
*
* @var string
*/
public $sessionCookieName = 'ci_session';
/**
* --------------------------------------------------------------------------
* Session Expiration
* --------------------------------------------------------------------------
*
* The number of SECONDS you want the session to last.
* Setting to 0 (zero) means expire when the browser is closed.
*
* @var integer
*/
public $sessionExpiration = 7200;
/**
* --------------------------------------------------------------------------
* Session Save Path
* --------------------------------------------------------------------------
*
* The location to save sessions to and is driver dependent.
*
* For the 'files' driver, it's a path to a writable directory.
* WARNING: Only absolute paths are supported!
*
* For the 'database' driver, it's a table name.
* Please read up the manual for the format with other session drivers.
*
* IMPORTANT: You are REQUIRED to set a valid save path!
*
* @var string
*/
public $sessionSavePath = WRITEPATH . 'session';
/**
* --------------------------------------------------------------------------
* Session Match IP
* --------------------------------------------------------------------------
*
* Whether to match the user's IP address when reading the session data.
*
* WARNING: If you're using the database driver, don't forget to update
* your session table's PRIMARY KEY when changing this setting.
*
* @var boolean
*/
public $sessionMatchIP = false;
/**
* --------------------------------------------------------------------------
* Session Time to Update
* --------------------------------------------------------------------------
*
* How many seconds between CI regenerating the session ID.
*
* @var integer
*/
public $sessionTimeToUpdate = 300;
/**
* --------------------------------------------------------------------------
* Session Regenerate Destroy
* --------------------------------------------------------------------------
*
* Whether to destroy session data associated with the old session ID
* when auto-regenerating the session ID. When set to FALSE, the data
* will be later deleted by the garbage collector.
*
* @var boolean
*/
public $sessionRegenerateDestroy = false;
/*
|--------------------------------------------------------------------------
| Cookie Related Variables
|--------------------------------------------------------------------------
|
| 'cookiePrefix' = Set a cookie name prefix if you need to avoid collisions
| 'cookieDomain' = Set to .your-domain.com for site-wide cookies
| 'cookiePath' = Typically will be a forward slash
| 'cookieSecure' = Cookie will only be set if a secure HTTPS connection exists.
| 'cookieHTTPOnly' = Cookie will only be accessible via HTTP(S) (no javascript)
|
| Note: These settings (with the exception of 'cookie_prefix' and
| 'cookie_httponly') will also affect sessions.
|
*/
/**
* --------------------------------------------------------------------------
* Cookie Prefix
* --------------------------------------------------------------------------
*
* Set a cookie name prefix if you need to avoid collisions.
*
* @var string
*/
public $cookiePrefix = '';
/**
* --------------------------------------------------------------------------
* Cookie Domain
* --------------------------------------------------------------------------
*
* Set to `.your-domain.com` for site-wide cookies.
*
* @var string
*/
public $cookieDomain = '';
/**
* --------------------------------------------------------------------------
* Cookie Path
* --------------------------------------------------------------------------
*
* Typically will be a forward slash.
*
* @var string
*/
public $cookiePath = '/';
/**
* --------------------------------------------------------------------------
* Cookie Secure
* --------------------------------------------------------------------------
*
* Cookie will only be set if a secure HTTPS connection exists.
*
* @var boolean
*/
public $cookieSecure = false;
/**
* --------------------------------------------------------------------------
* Cookie HTTP Only
* --------------------------------------------------------------------------
*
* Cookie will only be accessible via HTTP(S) (no JavaScript).
*
* @var boolean
*/
public $cookieHTTPOnly = false;
/*
|--------------------------------------------------------------------------
| Reverse Proxy IPs
|--------------------------------------------------------------------------
|
| If your server is behind a reverse proxy, you must whitelist the proxy
| IP addresses from which CodeIgniter should trust headers such as
| HTTP_X_FORWARDED_FOR and HTTP_CLIENT_IP in order to properly identify
| the visitor's IP address.
|
| You can use both an array or a comma-separated list of proxy addresses,
| as well as specifying whole subnets. Here are a few examples:
|
| Comma-separated: '10.0.1.200,192.168.5.0/24'
| Array: array('10.0.1.200', '192.168.5.0/24')
*/
/**
* --------------------------------------------------------------------------
* Cookie SameSite
* --------------------------------------------------------------------------
*
* Configure cookie SameSite setting. Allowed values are:
* - None
* - Lax
* - Strict
* - ''
*
* Defaults to `Lax` for compatibility with modern browsers. Setting `''`
* (empty string) means no SameSite attribute will be set on cookies. If
* set to `None`, `$cookieSecure` must also be set.
*
* @var string 'Lax'|'None'|'Strict'
*/
public $cookieSameSite = 'Lax';
/**
* --------------------------------------------------------------------------
* Reverse Proxy IPs
* --------------------------------------------------------------------------
*
* If your server is behind a reverse proxy, you must whitelist the proxy
* IP addresses from which CodeIgniter should trust headers such as
* HTTP_X_FORWARDED_FOR and HTTP_CLIENT_IP in order to properly identify
* the visitor's IP address.
*
* You can use both an array or a comma-separated list of proxy addresses,
* as well as specifying whole subnets. Here are a few examples:
*
* Comma-separated: '10.0.1.200,192.168.5.0/24'
* Array: ['10.0.1.200', '192.168.5.0/24']
*
* @var string|string[]
*/
public $proxyIPs = '';
/*
|--------------------------------------------------------------------------
| Cross Site Request Forgery
|--------------------------------------------------------------------------
| Enables a CSRF cookie token to be set. When set to TRUE, token will be
| checked on a submitted form. If you are accepting user data, it is strongly
| recommended CSRF protection be enabled.
|
| CSRFTokenName = The token name
| CSRFHeaderName = The header name
| CSRFCookieName = The cookie name
| CSRFExpire = The number in seconds the token should expire.
| CSRFRegenerate = Regenerate token on every submission
| CSRFRedirect = Redirect to previous page with error on failure
*/
/**
* --------------------------------------------------------------------------
* CSRF Token Name
* --------------------------------------------------------------------------
*
* The token name.
*
* @deprecated Use `Config\Security` $tokenName property instead of using this property.
*
* @var string
*/
public $CSRFTokenName = 'csrf_test_name';
/**
* --------------------------------------------------------------------------
* CSRF Header Name
* --------------------------------------------------------------------------
*
* The header name.
*
* @deprecated Use `Config\Security` $headerName property instead of using this property.
*
* @var string
*/
public $CSRFHeaderName = 'X-CSRF-TOKEN';
/**
* --------------------------------------------------------------------------
* CSRF Cookie Name
* --------------------------------------------------------------------------
*
* The cookie name.
*
* @deprecated Use `Config\Security` $cookieName property instead of using this property.
*
* @var string
*/
public $CSRFCookieName = 'csrf_cookie_name';
/**
* --------------------------------------------------------------------------
* CSRF Expire
* --------------------------------------------------------------------------
*
* The number in seconds the token should expire.
*
* @deprecated Use `Config\Security` $expire property instead of using this property.
*
* @var integer
*/
public $CSRFExpire = 7200;
/**
* --------------------------------------------------------------------------
* CSRF Regenerate
* --------------------------------------------------------------------------
*
* Regenerate token on every submission?
*
* @deprecated Use `Config\Security` $regenerate property instead of using this property.
*
* @var boolean
*/
public $CSRFRegenerate = true;
/**
* --------------------------------------------------------------------------
* CSRF Redirect
* --------------------------------------------------------------------------
*
* Redirect to previous page with error on failure?
*
* @deprecated Use `Config\Security` $redirect property instead of using this property.
*
* @var boolean
*/
public $CSRFRedirect = true;
/*
|--------------------------------------------------------------------------
| Content Security Policy
|--------------------------------------------------------------------------
| Enables the Response's Content Secure Policy to restrict the sources that
| can be used for images, scripts, CSS files, audio, video, etc. If enabled,
| the Response object will populate default values for the policy from the
| ContentSecurityPolicy.php file. Controllers can always add to those
| restrictions at run time.
|
| For a better understanding of CSP, see these documents:
| - http://www.html5rocks.com/en/tutorials/security/content-security-policy/
| - http://www.w3.org/TR/CSP/
*/
/**
* --------------------------------------------------------------------------
* CSRF SameSite
* --------------------------------------------------------------------------
*
* Setting for CSRF SameSite cookie token. Allowed values are:
* - None
* - Lax
* - Strict
* - ''
*
* Defaults to `Lax` as recommended in this link:
*
* @see https://portswigger.net/web-security/csrf/samesite-cookies
*
* @deprecated Use `Config\Security` $samesite property instead of using this property.
*
* @var string
*/
public $CSRFSameSite = 'Lax';
/**
* --------------------------------------------------------------------------
* Content Security Policy
* --------------------------------------------------------------------------
*
* Enables the Response's Content Secure Policy to restrict the sources that
* can be used for images, scripts, CSS files, audio, video, etc. If enabled,
* the Response object will populate default values for the policy from the
* `ContentSecurityPolicy.php` file. Controllers can always add to those
* restrictions at run time.
*
* For a better understanding of CSP, see these documents:
*
* @see http://www.html5rocks.com/en/tutorials/security/content-security-policy/
* @see http://www.w3.org/TR/CSP/
*
* @var boolean
*/
public $CSPEnabled = false;
/*
|--------------------------------------------------------------------------
| Media root folder
|--------------------------------------------------------------------------
| Defines the root folder for media files storage
*/
/**
* --------------------------------------------------------------------------
* Media root folder
* --------------------------------------------------------------------------
* Defines the root folder for media files storage
*/
public $mediaRoot = 'media';
/*
|--------------------------------------------------------------------------
| Admin gateway
|--------------------------------------------------------------------------
| Defines a base route for all admin pages
*/
/**
* --------------------------------------------------------------------------
* Admin gateway
* --------------------------------------------------------------------------
* Defines a base route for all admin pages
*/
public $adminGateway = 'cp-admin';
/*
|--------------------------------------------------------------------------
| Auth gateway
|--------------------------------------------------------------------------
| Defines a base route for all authentication related pages
*/
/**
* --------------------------------------------------------------------------
* Auth gateway
* --------------------------------------------------------------------------
* Defines a base route for all authentication related pages
*/
public $authGateway = 'cp-auth';
/*
|--------------------------------------------------------------------------
| Install gateway
|--------------------------------------------------------------------------
| Defines a base route for instance installation
*/
/**
* --------------------------------------------------------------------------
* Install gateway
* --------------------------------------------------------------------------
* Defines a base route for instance installation
*/
public $installGateway = 'cp-install';
}

View File

@ -2,90 +2,66 @@
namespace Config;
require_once SYSTEMPATH . 'Config/AutoloadConfig.php';
use CodeIgniter\Config\AutoloadConfig;
/**
* -------------------------------------------------------------------
* AUTO-LOADER
* -------------------------------------------------------------------
*
* This file defines the namespaces and class maps so the Autoloader
* can find the files as needed.
*
* NOTE: If you use an identical key in $psr4 or $classmap, then
* the values in this file will overwrite the framework's values.
*/
class Autoload extends \CodeIgniter\Config\AutoloadConfig
class Autoload extends AutoloadConfig
{
/**
* -------------------------------------------------------------------
* Namespaces
* -------------------------------------------------------------------
* This maps the locations of any namespaces in your application to
* their location on the file system. These are used by the autoloader
* to locate files the first time they have been instantiated.
*
* The '/app' and '/system' directories are already mapped for you.
* you may change the name of the 'App' namespace if you wish,
* but this should be done prior to creating any namespaced classes,
* else you will need to modify all of those classes for this to work.
*
* Prototype:
*
* $psr4 = [
* 'CodeIgniter' => SYSTEMPATH,
* 'App' => APPPATH
* ];
*
* @var array<string, string>
*/
public $psr4 = [
'App' => APPPATH,
APP_NAMESPACE => APPPATH, // For custom app namespace
'Config' => APPPATH . 'Config',
'ActivityPub' => APPPATH . 'Libraries/ActivityPub',
];
public $classmap = [];
//--------------------------------------------------------------------
/**
* Collects the application-specific autoload settings and merges
* them with the framework's required settings.
* -------------------------------------------------------------------
* Class Map
* -------------------------------------------------------------------
* The class map provides a map of class names and their exact
* location on the drive. Classes loaded in this manner will have
* slightly faster performance because they will not have to be
* searched for within one or more directories as they would if they
* were being autoloaded through a namespace.
*
* NOTE: If you use an identical key in $psr4 or $classmap, then
* the values in this file will overwrite the framework's values.
* Prototype:
*
* $classmap = [
* 'MyClass' => '/path/to/class/file.php'
* ];
*
* @var array<string, string>
*/
public function __construct()
{
parent::__construct();
/**
* -------------------------------------------------------------------
* Namespaces
* -------------------------------------------------------------------
* This maps the locations of any namespaces in your application
* to their location on the file system. These are used by the
* Autoloader to locate files the first time they have been instantiated.
*
* The '/app' and '/system' directories are already mapped for
* you. You may change the name of the 'App' namespace if you wish,
* but this should be done prior to creating any namespaced classes,
* else you will need to modify all of those classes for this to work.
*
* DO NOT change the name of the CodeIgniter namespace or your application
* WILL break. *
* Prototype:
*
* $Config['psr4'] = [
* 'CodeIgniter' => SYSPATH
* `];
*/
$psr4 = [
'App' => APPPATH, // To ensure filters, etc still found,
APP_NAMESPACE => APPPATH, // For custom namespace
'Config' => APPPATH . 'Config',
];
/**
* -------------------------------------------------------------------
* Class Map
* -------------------------------------------------------------------
* The class map provides a map of class names and their exact
* location on the drive. Classes loaded in this manner will have
* slightly faster performance because they will not have to be
* searched for within one or more directories as they would if they
* were being autoloaded through a namespace.
*
* Prototype:
*
* $Config['classmap'] = [
* 'MyClass' => '/path/to/class/file.php'
* ];
*/
$classmap = [];
//--------------------------------------------------------------------
// Do Not Edit Below This Line
//--------------------------------------------------------------------
$this->psr4 = array_merge($this->psr4, $psr4);
$this->classmap = array_merge($this->classmap, $classmap);
unset($psr4, $classmap);
}
//--------------------------------------------------------------------
public $classmap = [];
}

View File

@ -1,33 +1,32 @@
<?php
/*
|--------------------------------------------------------------------------
| ERROR DISPLAY
|--------------------------------------------------------------------------
| In development, we want to show as many errors as possible to help
| make sure they don't make it to production. And save us hours of
| painful debugging.
/**
* --------------------------------------------------------------------------
* ERROR DISPLAY
* --------------------------------------------------------------------------
* In development, we want to show as many errors as possible to help
* make sure they don't make it to production. And save us hours of
* painful debugging.
*/
error_reporting(-1);
ini_set('display_errors', '1');
/*
|--------------------------------------------------------------------------
| DEBUG BACKTRACES
|--------------------------------------------------------------------------
| If true, this constant will tell the error screens to display debug
| backtraces along with the other error information. If you would
| prefer to not see this, set this value to false.
/**
* --------------------------------------------------------------------------
* DEBUG BACKTRACES
* --------------------------------------------------------------------------
* If true, this constant will tell the error screens to display debug
* backtraces along with the other error information. If you would
* prefer to not see this, set this value to false.
*/
defined('SHOW_DEBUG_BACKTRACE') || define('SHOW_DEBUG_BACKTRACE', true);
/*
|--------------------------------------------------------------------------
| DEBUG MODE
|--------------------------------------------------------------------------
| Debug mode is an experimental flag that can allow changes throughout
| the system. This will control whether Kint is loaded, and a few other
| items. It can always be used within your own application too.
/**
* --------------------------------------------------------------------------
* DEBUG MODE
* --------------------------------------------------------------------------
* Debug mode is an experimental flag that can allow changes throughout
* the system. This will control whether Kint is loaded, and a few other
* items. It can always be used within your own application too.
*/
defined('CI_DEBUG') || define('CI_DEBUG', 1);
defined('CI_DEBUG') || define('CI_DEBUG', true);

View File

@ -1,11 +1,11 @@
<?php
/*
|--------------------------------------------------------------------------
| ERROR DISPLAY
|--------------------------------------------------------------------------
| Don't show ANY in production environments. Instead, let the system catch
| it and display a generic error message.
/**
* --------------------------------------------------------------------------
* ERROR DISPLAY
* --------------------------------------------------------------------------
* Don't show ANY in production environments. Instead, let the system catch
* it and display a generic error message.
*/
ini_set('display_errors', '0');
error_reporting(
@ -14,16 +14,15 @@ error_reporting(
~E_DEPRECATED &
~E_STRICT &
~E_USER_NOTICE &
~E_USER_DEPRECATED
~E_USER_DEPRECATED,
);
/*
|--------------------------------------------------------------------------
| DEBUG MODE
|--------------------------------------------------------------------------
| Debug mode is an experimental flag that can allow changes throughout
| the system. It's not widely used currently, and may not survive
| release of the framework.
/**
* --------------------------------------------------------------------------
* DEBUG MODE
* --------------------------------------------------------------------------
* Debug mode is an experimental flag that can allow changes throughout
* the system. It's not widely used currently, and may not survive
* release of the framework.
*/
defined('CI_DEBUG') || define('CI_DEBUG', 0);
defined('CI_DEBUG') || define('CI_DEBUG', false);

View File

@ -1,33 +1,32 @@
<?php
/*
|--------------------------------------------------------------------------
| ERROR DISPLAY
|--------------------------------------------------------------------------
| In development, we want to show as many errors as possible to help
| make sure they don't make it to production. And save us hours of
| painful debugging.
/**
* --------------------------------------------------------------------------
* ERROR DISPLAY
* --------------------------------------------------------------------------
* In development, we want to show as many errors as possible to help
* make sure they don't make it to production. And save us hours of
* painful debugging.
*/
error_reporting(-1);
ini_set('display_errors', '1');
/*
|--------------------------------------------------------------------------
| DEBUG BACKTRACES
|--------------------------------------------------------------------------
| If true, this constant will tell the error screens to display debug
| backtraces along with the other error information. If you would
| prefer to not see this, set this value to false.
/**
* --------------------------------------------------------------------------
* DEBUG BACKTRACES
* --------------------------------------------------------------------------
* If true, this constant will tell the error screens to display debug
* backtraces along with the other error information. If you would
* prefer to not see this, set this value to false.
*/
defined('SHOW_DEBUG_BACKTRACE') || define('SHOW_DEBUG_BACKTRACE', true);
/*
|--------------------------------------------------------------------------
| DEBUG MODE
|--------------------------------------------------------------------------
| Debug mode is an experimental flag that can allow changes throughout
| the system. It's not widely used currently, and may not survive
| release of the framework.
/**
* --------------------------------------------------------------------------
* DEBUG MODE
* --------------------------------------------------------------------------
* Debug mode is an experimental flag that can allow changes throughout
* the system. It's not widely used currently, and may not survive
* release of the framework.
*/
defined('CI_DEBUG') || define('CI_DEBUG', 1);
defined('CI_DEBUG') || define('CI_DEBUG', true);

View File

@ -2,83 +2,111 @@
namespace Config;
use CodeIgniter\Cache\Handlers\DummyHandler;
use CodeIgniter\Cache\Handlers\FileHandler;
use CodeIgniter\Cache\Handlers\MemcachedHandler;
use CodeIgniter\Cache\Handlers\PredisHandler;
use CodeIgniter\Cache\Handlers\RedisHandler;
use CodeIgniter\Cache\Handlers\WincacheHandler;
use CodeIgniter\Config\BaseConfig;
class Cache extends BaseConfig
{
/*
|--------------------------------------------------------------------------
| Primary Handler
|--------------------------------------------------------------------------
|
| The name of the preferred handler that should be used. If for some reason
| it is not available, the $backupHandler will be used in its place.
|
*/
/**
* --------------------------------------------------------------------------
* Primary Handler
* --------------------------------------------------------------------------
*
* The name of the preferred handler that should be used. If for some reason
* it is not available, the $backupHandler will be used in its place.
*
* @var string
*/
public $handler = 'file';
/*
|--------------------------------------------------------------------------
| Backup Handler
|--------------------------------------------------------------------------
|
| The name of the handler that will be used in case the first one is
| unreachable. Often, 'file' is used here since the filesystem is
| always available, though that's not always practical for the app.
|
*/
/**
* --------------------------------------------------------------------------
* Backup Handler
* --------------------------------------------------------------------------
*
* The name of the handler that will be used in case the first one is
* unreachable. Often, 'file' is used here since the filesystem is
* always available, though that's not always practical for the app.
*
* @var string
*/
public $backupHandler = 'dummy';
/*
|--------------------------------------------------------------------------
| Cache Directory Path
|--------------------------------------------------------------------------
|
| The path to where cache files should be stored, if using a file-based
| system.
|
*/
/**
* --------------------------------------------------------------------------
* Cache Directory Path
* --------------------------------------------------------------------------
*
* The path to where cache files should be stored, if using a file-based
* system.
*
* @var string
*
* @deprecated Use the driver-specific variant under $file
*/
public $storePath = WRITEPATH . 'cache/';
/*
|--------------------------------------------------------------------------
| Cache Include Query String
|--------------------------------------------------------------------------
|
| Whether to take the URL query string into consideration when generating
| output cache files. Valid options are:
|
| false = Disabled
| true = Enabled, take all query parameters into account.
| Please be aware that this may result in numerous cache
| files generated for the same page over and over again.
| array('q') = Enabled, but only take into account the specified list
| of query parameters.
|
*/
/**
* --------------------------------------------------------------------------
* Cache Include Query String
* --------------------------------------------------------------------------
*
* Whether to take the URL query string into consideration when generating
* output cache files. Valid options are:
*
* false = Disabled
* true = Enabled, take all query parameters into account.
* Please be aware that this may result in numerous cache
* files generated for the same page over and over again.
* array('q') = Enabled, but only take into account the specified list
* of query parameters.
*
* @var boolean|string[]
*/
public $cacheQueryString = false;
/*
|--------------------------------------------------------------------------
| Key Prefix
|--------------------------------------------------------------------------
|
| This string is added to all cache item names to help avoid collisions
| if you run multiple applications with the same cache engine.
|
*/
/**
* --------------------------------------------------------------------------
* Key Prefix
* --------------------------------------------------------------------------
*
* This string is added to all cache item names to help avoid collisions
* if you run multiple applications with the same cache engine.
*
* @var string
*/
public $prefix = '';
/*
| -------------------------------------------------------------------------
| Memcached settings
| -------------------------------------------------------------------------
| Your Memcached servers can be specified below, if you are using
| the Memcached drivers.
|
| See: https://codeigniter.com/user_guide/libraries/caching.html#memcached
|
*/
/**
* --------------------------------------------------------------------------
* File settings
* --------------------------------------------------------------------------
* Your file storage preferences can be specified below, if you are using
* the File driver.
*
* @var array<string, string|int|null>
*/
public $file = [
'storePath' => WRITEPATH . 'cache/',
'mode' => 0640,
];
/**
* -------------------------------------------------------------------------
* Memcached settings
* -------------------------------------------------------------------------
* Your Memcached servers can be specified below, if you are using
* the Memcached drivers.
*
* @see https://codeigniter.com/user_guide/libraries/caching.html#memcached
*
* @var array<string, string|int|boolean>
*/
public $memcached = [
'host' => '127.0.0.1',
'port' => 11211,
@ -86,14 +114,15 @@ class Cache extends BaseConfig
'raw' => false,
];
/*
| -------------------------------------------------------------------------
| Redis settings
| -------------------------------------------------------------------------
| Your Redis server can be specified below, if you are using
| the Redis or Predis drivers.
|
*/
/**
* -------------------------------------------------------------------------
* Redis settings
* -------------------------------------------------------------------------
* Your Redis server can be specified below, if you are using
* the Redis or Predis drivers.
*
* @var array<string, string|int|null>
*/
public $redis = [
'host' => '127.0.0.1',
'password' => null,
@ -102,21 +131,22 @@ class Cache extends BaseConfig
'database' => 0,
];
/*
|--------------------------------------------------------------------------
| Available Cache Handlers
|--------------------------------------------------------------------------
|
| This is an array of cache engine alias' and class names. Only engines
| that are listed here are allowed to be used.
|
*/
/**
* --------------------------------------------------------------------------
* Available Cache Handlers
* --------------------------------------------------------------------------
*
* This is an array of cache engine alias' and class names. Only engines
* that are listed here are allowed to be used.
*
* @var array<string, string>
*/
public $validHandlers = [
'dummy' => \CodeIgniter\Cache\Handlers\DummyHandler::class,
'file' => \CodeIgniter\Cache\Handlers\FileHandler::class,
'memcached' => \CodeIgniter\Cache\Handlers\MemcachedHandler::class,
'predis' => \CodeIgniter\Cache\Handlers\PredisHandler::class,
'redis' => \CodeIgniter\Cache\Handlers\RedisHandler::class,
'wincache' => \CodeIgniter\Cache\Handlers\WincacheHandler::class,
'dummy' => DummyHandler::class,
'file' => FileHandler::class,
'memcached' => MemcachedHandler::class,
'predis' => PredisHandler::class,
'redis' => RedisHandler::class,
'wincache' => WincacheHandler::class,
];
}

View File

@ -1,46 +1,50 @@
<?php
//--------------------------------------------------------------------------
// Castopod Version
//--------------------------------------------------------------------------
// The Castopod version number to display.
//
// NOTE: this constant is updated upon release with Continuous Integration.
//
/*
| --------------------------------------------------------------------
| Castopod Version
| --------------------------------------------------------------------
|
| The Castopod version number to display.
|
| NOTE: this constant is updated upon release with Continuous Integration.
*/
defined('CP_VERSION') || define('CP_VERSION', '1.0.0-alpha.41');
//--------------------------------------------------------------------
// App Namespace
//--------------------------------------------------------------------
// This defines the default Namespace that is used throughout
// CodeIgniter to refer to the Application directory. Change
// this constant to change the namespace that all application
// classes should use.
//
// NOTE: changing this will require manually modifying the
// existing namespaces of App\* namespaced-classes.
//
/*
| --------------------------------------------------------------------
| App Namespace
| --------------------------------------------------------------------
|
| This defines the default Namespace that is used throughout
| CodeIgniter to refer to the Application directory. Change
| this constant to change the namespace that all application
| classes should use.
|
| NOTE: changing this will require manually modifying the
| existing namespaces of App\* namespaced-classes.
*/
defined('APP_NAMESPACE') || define('APP_NAMESPACE', 'App');
/*
|--------------------------------------------------------------------------
| Composer Path
|--------------------------------------------------------------------------
|
| The path that Composer's autoload file is expected to live. By default,
| the vendor folder is in the Root directory, but you can customize that here.
*/
| --------------------------------------------------------------------------
| Composer Path
| --------------------------------------------------------------------------
|
| The path that Composer's autoload file is expected to live. By default,
| the vendor folder is in the Root directory, but you can customize that here.
*/
defined('COMPOSER_PATH') ||
define('COMPOSER_PATH', ROOTPATH . 'vendor/autoload.php');
/*
|--------------------------------------------------------------------------
| Timing Constants
|--------------------------------------------------------------------------
|
| Provide simple ways to work with the myriad of PHP functions that
| require information to be in seconds.
*/
|--------------------------------------------------------------------------
| Timing Constants
|--------------------------------------------------------------------------
|
| Provide simple ways to work with the myriad of PHP functions that
| require information to be in seconds.
*/
defined('SECOND') || define('SECOND', 1);
defined('MINUTE') || define('MINUTE', 60);
defined('HOUR') || define('HOUR', 3600);
@ -51,30 +55,30 @@ defined('YEAR') || define('YEAR', 31536000);
defined('DECADE') || define('DECADE', 315360000);
/*
|--------------------------------------------------------------------------
| Exit Status Codes
|--------------------------------------------------------------------------
|
| Used to indicate the conditions under which the script is exit()ing.
| While there is no universal standard for error codes, there are some
| broad conventions. Three such conventions are mentioned below, for
| those who wish to make use of them. The CodeIgniter defaults were
| chosen for the least overlap with these conventions, while still
| leaving room for others to be defined in future versions and user
| applications.
|
| The three main conventions used for determining exit status codes
| are as follows:
|
| Standard C/C++ Library (stdlibc):
| http://www.gnu.org/software/libc/manual/html_node/Exit-Status.html
| (This link also contains other GNU-specific conventions)
| BSD sysexits.h:
| http://www.gsp.com/cgi-bin/man.cgi?section=3&topic=sysexits
| Bash scripting:
| http://tldp.org/LDP/abs/html/exitcodes.html
|
*/
| --------------------------------------------------------------------------
| Exit Status Codes
| --------------------------------------------------------------------------
|
| Used to indicate the conditions under which the script is exit()ing.
| While there is no universal standard for error codes, there are some
| broad conventions. Three such conventions are mentioned below, for
| those who wish to make use of them. The CodeIgniter defaults were
| chosen for the least overlap with these conventions, while still
| leaving room for others to be defined in future versions and user
| applications.
|
| The three main conventions used for determining exit status codes
| are as follows:
|
| Standard C/C++ Library (stdlibc):
| http://www.gnu.org/software/libc/manual/html_node/Exit-Status.html
| (This link also contains other GNU-specific conventions)
| BSD sysexits.h:
| http://www.gsp.com/cgi-bin/man.cgi?section=3&topic=sysexits
| Bash scripting:
| http://tldp.org/LDP/abs/html/exitcodes.html
|
*/
defined('EXIT_SUCCESS') || define('EXIT_SUCCESS', 0); // no errors
defined('EXIT_ERROR') || define('EXIT_ERROR', 1); // generic error
defined('EXIT_CONFIG') || define('EXIT_CONFIG', 3); // configuration error

View File

@ -5,45 +5,155 @@ namespace Config;
use CodeIgniter\Config\BaseConfig;
/**
* Class ContentSecurityPolicyConfig
*
* Stores the default settings for the ContentSecurityPolicy, if you
* choose to use it. The values here will be read in and set as defaults
* for the site. If needed, they can be overridden on a page-by-page basis.
*
* Suggested reference for explanations:
* https://www.html5rocks.com/en/tutorials/security/content-security-policy/
*
* @package Config
* @see https://www.html5rocks.com/en/tutorials/security/content-security-policy/
*/
class ContentSecurityPolicy extends BaseConfig
{
// broadbrush CSP management
//-------------------------------------------------------------------------
// Broadbrush CSP management
//-------------------------------------------------------------------------
public $reportOnly = false; // default CSP report context
public $reportURI = null; // URL to send violation reports to
public $upgradeInsecureRequests = false; // toggle for forcing https
/**
* Default CSP report context
*
* @var boolean
*/
public $reportOnly = false;
// sources allowed; string or array of strings
/**
* Specifies a URL where a browser will send reports
* when a content security policy is violated.
*
* @var string|null
*/
public $reportURI = null;
/**
* Instructs user agents to rewrite URL schemes, changing
* HTTP to HTTPS. This directive is for websites with
* large numbers of old URLs that need to be rewritten.
*
* @var boolean
*/
public $upgradeInsecureRequests = false;
//-------------------------------------------------------------------------
// Sources allowed
// Note: once you set a policy to 'none', it cannot be further restricted
//-------------------------------------------------------------------------
public $defaultSrc = null; // will default to self if not over-ridden
/**
* Will default to self if not overridden
*
* @var string|string[]|null
*/
public $defaultSrc = null;
/**
* Lists allowed scripts' URLs.
*
* @var string|string[]
*/
public $scriptSrc = 'self';
/**
* Lists allowed stylesheets' URLs.
*
* @var string|string[]
*/
public $styleSrc = 'self';
/**
* Defines the origins from which images can be loaded.
*
* @var string|string[]
*/
public $imageSrc = 'self';
public $baseURI = null; // will default to self if not over-ridden
/**
* Restricts the URLs that can appear in a page's `<base>` element.
*
* Will default to self if not overridden
*
* @var string|string[]|null
*/
public $baseURI = null;
/**
* Lists the URLs for workers and embedded frame contents
*
* @var string|string[]
*/
public $childSrc = 'self';
/**
* Limits the origins that you can connect to (via XHR,
* WebSockets, and EventSource).
*
* @var string|string[]
*/
public $connectSrc = 'self';
/**
* Specifies the origins that can serve web fonts.
*
* @var string|string[]
*/
public $fontSrc = null;
/**
* Lists valid endpoints for submission from `<form>` tags.
*
* @var string|string[]
*/
public $formAction = 'self';
/**
* Specifies the sources that can embed the current page.
* This directive applies to `<frame>`, `<iframe>`, `<embed>`,
* and `<applet>` tags. This directive can't be used in
* `<meta>` tags and applies only to non-HTML resources.
*
* @var string|string[]|null
*/
public $frameAncestors = null;
/**
* Restricts the origins allowed to deliver video and audio.
*
* @var string|string[]|null
*/
public $mediaSrc = null;
/**
* Allows control over Flash and other plugins.
*
* @var string|string[]
*/
public $objectSrc = 'self';
/**
* @var string|string[]|null
*/
public $manifestSrc = null;
// mime types allowed; string or array of strings
/**
* Limits the kinds of plugins a page may invoke.
*
* @var string|string[]|null
*/
public $pluginTypes = null;
// list of actions allowed; string or array of strings
/**
* List of actions allowed.
*
* @var string|string[]|null
*/
public $sandbox = null;
}

View File

@ -2,13 +2,12 @@
namespace Config;
use CodeIgniter\Database\Config;
/**
* Database Configuration
*
* @package Config
*/
class Database extends \CodeIgniter\Database\Config
class Database extends Config
{
/**
* The directory that holds the Migrations
@ -16,7 +15,7 @@ class Database extends \CodeIgniter\Database\Config
*
* @var string
*/
public $filesPath = APPPATH . 'Database/';
public $filesPath = APPPATH . 'Database' . DIRECTORY_SEPARATOR;
/**
* Lets you choose which connection group to
@ -41,10 +40,8 @@ class Database extends \CodeIgniter\Database\Config
'DBPrefix' => 'cp_',
'pConnect' => false,
'DBDebug' => ENVIRONMENT !== 'production',
'cacheOn' => false,
'cacheDir' => '',
'charset' => 'utf8',
'DBCollat' => 'utf8_general_ci',
'charset' => 'utf8mb4',
'DBCollat' => 'utf8mb4_unicode_ci',
'swapPre' => '',
'encrypt' => false,
'compress' => false,
@ -69,8 +66,6 @@ class Database extends \CodeIgniter\Database\Config
'DBPrefix' => 'db_', // Needed to ensure we're working correctly with prefixes live. DO NOT REMOVE FOR CI DEVS
'pConnect' => false,
'DBDebug' => ENVIRONMENT !== 'production',
'cacheOn' => false,
'cacheDir' => '',
'charset' => 'utf8',
'DBCollat' => 'utf8_general_ci',
'swapPre' => '',
@ -92,21 +87,6 @@ class Database extends \CodeIgniter\Database\Config
// we don't overwrite live data on accident.
if (ENVIRONMENT === 'testing') {
$this->defaultGroup = 'tests';
// Under Travis-CI, we can set an ENV var named 'DB_GROUP'
// so that we can test against multiple databases.
if ($group = getenv('DB')) {
if (is_file(TESTPATH . 'travis/Database.php')) {
require TESTPATH . 'travis/Database.php';
if (
!empty($dbconfig) &&
array_key_exists($group, $dbconfig)
) {
$this->tests = $dbconfig[$group];
}
}
}
}
}

View File

@ -2,14 +2,13 @@
namespace Config;
/**
* DocTypes
*
* @package Config
*/
class DocTypes
{
/**
* List of valid document types.
*
* @var array<string, string>
*/
public $list = [
'xhtml11' =>
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',

View File

@ -12,25 +12,56 @@ use CodeIgniter\Config\BaseConfig;
*/
class Encryption extends BaseConfig
{
/*
|--------------------------------------------------------------------------
| Encryption Key Starter
|--------------------------------------------------------------------------
|
| If you use the Encryption class you must set an encryption key (seed).
| You need to ensure it is long enough for the cipher and mode you plan to use.
| See the user guide for more info.
/**
* --------------------------------------------------------------------------
* Encryption Key Starter
* --------------------------------------------------------------------------
*
* If you use the Encryption class you must set an encryption key (seed).
* You need to ensure it is long enough for the cipher and mode you plan to use.
* See the user guide for more info.
*
* @var string
*/
public $key = '';
/*
|--------------------------------------------------------------------------
| Encryption driver to use
|--------------------------------------------------------------------------
|
| One of the supported drivers, eg 'OpenSSL' or 'Sodium'.
| The default driver, if you don't specify one, is 'OpenSSL'.
/**
* --------------------------------------------------------------------------
* Encryption Driver to Use
* --------------------------------------------------------------------------
*
* One of the supported encryption drivers.
*
* Available drivers:
* - OpenSSL
* - Sodium
*
* @var string
*/
public $driver = 'OpenSSL';
/**
* --------------------------------------------------------------------------
* SodiumHandler's Padding Length in Bytes
* --------------------------------------------------------------------------
*
* This is the number of bytes that will be padded to the plaintext message
* before it is encrypted. This value should be greater than zero.
*
* See the user guide for more information on padding.
*
* @var integer
*/
public $blockSize = 16;
/**
* --------------------------------------------------------------------------
* Encryption digest
* --------------------------------------------------------------------------
*
* HMAC digest to use, e.g. 'SHA512' or 'SHA256'. Default value is 'SHA512'.
*
* @var string
*/
public $digest = 'SHA512';
}

View File

@ -3,6 +3,7 @@
namespace Config;
use CodeIgniter\Events\Events;
use CodeIgniter\Exceptions\FrameworkException;
/*
* --------------------------------------------------------------------
@ -23,11 +24,15 @@ use CodeIgniter\Events\Events;
Events::on('pre_system', function () {
if (ENVIRONMENT !== 'testing') {
while (\ob_get_level() > 0) {
\ob_end_flush();
if (ini_get('zlib.output_compression')) {
throw FrameworkException::forEnabledZlibOutputCompression();
}
\ob_start(function ($buffer) {
while (ob_get_level() > 0) {
ob_end_flush();
}
ob_start(function ($buffer) {
return $buffer;
});
}
@ -38,11 +43,98 @@ Events::on('pre_system', function () {
* --------------------------------------------------------------------
* If you delete, they will no longer be collected.
*/
if (ENVIRONMENT !== 'production') {
if (CI_DEBUG) {
Events::on(
'DBQuery',
'CodeIgniter\Debug\Toolbar\Collectors\Database::collect'
'CodeIgniter\Debug\Toolbar\Collectors\Database::collect',
);
Services::toolbar()->respond();
}
});
Events::on('login', function ($user) {
helper('auth');
// set interact_as_actor_id value
$userPodcasts = $user->podcasts;
if ($userPodcasts = $user->podcasts) {
set_interact_as_actor($userPodcasts[0]->id);
}
});
Events::on('logout', function ($user) {
helper('auth');
// remove user's interact_as_actor session
remove_interact_as_actor($user->id);
});
/*
* --------------------------------------------------------------------
* ActivityPub events
* --------------------------------------------------------------------
* Update episode metadata counts
*/
Events::on('on_note_add', function ($note) {
if ($note->episode_id) {
model('EpisodeModel')
->where('id', $note->episode_id)
->increment('notes_total');
}
});
Events::on('on_note_remove', function ($note) {
if ($note->episode_id) {
model('EpisodeModel')
->where('id', $note->episode_id)
->decrement('notes_total', 1 + $note->reblogs_count);
model('EpisodeModel')
->where('id', $note->episode_id)
->decrement('reblogs_total', $note->reblogs_count);
model('EpisodeModel')
->where('id', $note->episode_id)
->decrement('favourites_total', $note->favourites_count);
}
});
Events::on('on_note_reblog', function ($actor, $note) {
if ($episodeId = $note->episode_id) {
model('EpisodeModel')
->where('id', $episodeId)
->increment('reblogs_total');
model('EpisodeModel')
->where('id', $episodeId)
->increment('notes_total');
}
});
Events::on('on_note_undo_reblog', function ($reblogNote) {
if ($episodeId = $reblogNote->reblog_of_note->episode_id) {
model('EpisodeModel')
->where('id', $episodeId)
->decrement('reblogs_total');
model('EpisodeModel')
->where('id', $episodeId)
->decrement('notes_total');
}
});
Events::on('on_note_favourite', function ($actor, $note) {
if ($note->episode_id) {
model('EpisodeModel')
->where('id', $note->episode_id)
->increment('favourites_total');
}
});
Events::on('on_note_undo_favourite', function ($actor, $note) {
if ($note->episode_id) {
model('EpisodeModel')
->where('id', $note->episode_id)
->decrement('favourites_total');
}
});

View File

@ -2,42 +2,47 @@
namespace Config;
use CodeIgniter\Config\BaseConfig;
/**
* Setup how the exception handler works.
*
* @package Config
*/
class Exceptions
class Exceptions extends BaseConfig
{
/*
|--------------------------------------------------------------------------
| LOG EXCEPTIONS?
|--------------------------------------------------------------------------
| If true, then exceptions will be logged
| through Services::Log.
|
| Default: true
/**
* --------------------------------------------------------------------------
* LOG EXCEPTIONS?
* --------------------------------------------------------------------------
* If true, then exceptions will be logged
* through Services::Log.
*
* Default: true
*
* @var boolean
*/
public $log = true;
/*
|--------------------------------------------------------------------------
| DO NOT LOG STATUS CODES
|--------------------------------------------------------------------------
| Any status codes here will NOT be logged if logging is turned on.
| By default, only 404 (Page Not Found) exceptions are ignored.
/**
* --------------------------------------------------------------------------
* DO NOT LOG STATUS CODES
* --------------------------------------------------------------------------
* Any status codes here will NOT be logged if logging is turned on.
* By default, only 404 (Page Not Found) exceptions are ignored.
*
* @var array
*/
public $ignoreCodes = [404];
/*
|--------------------------------------------------------------------------
| Error Views Path
|--------------------------------------------------------------------------
| This is the path to the directory that contains the 'cli' and 'html'
| directories that hold the views used to generate errors.
|
| Default: APPPATH.'Views/errors'
*/
/**
* --------------------------------------------------------------------------
* Error Views Path
* --------------------------------------------------------------------------
* This is the path to the directory that contains the 'cli' and 'html'
* directories that hold the views used to generate errors.
*
* Default: APPPATH.'Views/errors'
*
* @var string
*/
public $errorViewPath = APPPATH . 'Views/errors';
}

View File

@ -3,40 +3,65 @@
namespace Config;
use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Filters\CSRF;
use CodeIgniter\Filters\DebugToolbar;
use CodeIgniter\Filters\Honeypot;
class Filters extends BaseConfig
{
// Makes reading things below nicer,
// and simpler to change out script that's used.
/**
* Configures aliases for Filter classes to
* make reading things nicer and simpler.
*
* @var array
*/
public $aliases = [
'csrf' => \CodeIgniter\Filters\CSRF::class,
'toolbar' => \CodeIgniter\Filters\DebugToolbar::class,
'honeypot' => \CodeIgniter\Filters\Honeypot::class,
'csrf' => CSRF::class,
'toolbar' => DebugToolbar::class,
'honeypot' => Honeypot::class,
'login' => \Myth\Auth\Filters\LoginFilter::class,
'role' => \Myth\Auth\Filters\RoleFilter::class,
'permission' => \App\Filters\Permission::class,
'permission' => \App\Filters\PermissionFilter::class,
'activity-pub' => \ActivityPub\Filters\ActivityPubFilter::class,
];
// Always applied before every request
/**
* List of filter aliases that are always
* applied before and after every request.
*
* @var array
*/
public $globals = [
'before' => [
//'honeypot'
// 'honeypot',
// 'csrf',
],
'after' => [
'toolbar',
//'honeypot'
// 'honeypot',
],
];
// Works on all of a particular HTTP method
// (GET, POST, etc) as BEFORE filters only
// like: 'post' => ['CSRF', 'throttle'],
/**
* List of filter aliases that works on a
* particular HTTP method (GET, POST, etc.).
*
* Example:
* 'post' => ['csrf', 'throttle']
*
* @var array
*/
public $methods = [];
// List filter aliases and any before/after uri patterns
// that they should run on, like:
// 'isLoggedIn' => ['before' => ['account/*', 'profiles/*']],
/**
* List of filter aliases that should run on any
* before or after URI patterns.
*
* Example:
* 'isLoggedIn' => ['before' => ['account/*', 'profiles/*']]
*
* @var array
*/
public $filters = [];
public function __construct()

View File

@ -2,6 +2,8 @@
namespace Config;
class ForeignCharacters extends \CodeIgniter\Config\ForeignCharacters
use CodeIgniter\Config\ForeignCharacters as BaseForeignCharacters;
class ForeignCharacters extends BaseForeignCharacters
{
}

View File

@ -3,43 +3,62 @@
namespace Config;
use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Format\FormatterInterface;
class Format extends BaseConfig
{
/*
|--------------------------------------------------------------------------
| Available Response Formats
|--------------------------------------------------------------------------
|
| When you perform content negotiation with the request, these are the
| available formats that your application supports. This is currently
| only used with the API\ResponseTrait. A valid Formatter must exist
| for the specified format.
|
| These formats are only checked when the data passed to the respond()
| method is an array.
|
*/
/**
* --------------------------------------------------------------------------
* Available Response Formats
* --------------------------------------------------------------------------
*
* When you perform content negotiation with the request, these are the
* available formats that your application supports. This is currently
* only used with the API\ResponseTrait. A valid Formatter must exist
* for the specified format.
*
* These formats are only checked when the data passed to the respond()
* method is an array.
*
* @var string[]
*/
public $supportedResponseFormats = [
'application/json',
'application/xml', // machine-readable XML
'text/xml', // human-readable XML
];
/*
|--------------------------------------------------------------------------
| Formatters
|--------------------------------------------------------------------------
|
| Lists the class to use to format responses with of a particular type.
| For each mime type, list the class that should be used. Formatters
| can be retrieved through the getFormatter() method.
|
*/
/**
* --------------------------------------------------------------------------
* Formatters
* --------------------------------------------------------------------------
*
* Lists the class to use to format responses with of a particular type.
* For each mime type, list the class that should be used. Formatters
* can be retrieved through the getFormatter() method.
*
* @var array<string, string>
*/
public $formatters = [
'application/json' => \CodeIgniter\Format\JSONFormatter::class,
'application/xml' => \CodeIgniter\Format\XMLFormatter::class,
'text/xml' => \CodeIgniter\Format\XMLFormatter::class,
'application/json' => 'CodeIgniter\Format\JSONFormatter',
'application/xml' => 'CodeIgniter\Format\XMLFormatter',
'text/xml' => 'CodeIgniter\Format\XMLFormatter',
];
/**
* --------------------------------------------------------------------------
* Formatters Options
* --------------------------------------------------------------------------
*
* Additional Options to adjust default formatters behaviour.
* For each mime type, list the additional options that should be used.
*
* @var array<string, int>
*/
public $formatterOptions = [
'application/json' => JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES,
'application/xml' => 0,
'text/xml' => 0,
];
//--------------------------------------------------------------------
@ -49,26 +68,12 @@ class Format extends BaseConfig
*
* @param string $mime
*
* @return \CodeIgniter\Format\FormatterInterface
* @return FormatterInterface
*
* @deprecated This is an alias of `\CodeIgniter\Format\Format::getFormatter`. Use that instead.
*/
public function getFormatter(string $mime)
{
if (!array_key_exists($mime, $this->formatters)) {
throw new \InvalidArgumentException(
'No Formatter defined for mime type: ' . $mime
);
}
$class = $this->formatters[$mime];
if (!class_exists($class)) {
throw new \BadMethodCallException(
$class . ' is not a valid Formatter.'
);
}
return new $class();
return Services::format()->getFormatter($mime);
}
//--------------------------------------------------------------------
}

44
app/Config/Generators.php Normal file
View File

@ -0,0 +1,44 @@
<?php
namespace Config;
use CodeIgniter\Config\BaseConfig;
class Generators extends BaseConfig
{
/**
* --------------------------------------------------------------------------
* Generator Commands' Views
* --------------------------------------------------------------------------
*
* This array defines the mapping of generator commands to the view files
* they are using. If you need to customize them for your own, copy these
* view files in your own folder and indicate the location here.
*
* You will notice that the views have special placeholders enclosed in
* curly braces `{...}`. These placeholders are used internally by the
* generator commands in processing replacements, thus you are warned
* not to delete them or modify the names. If you will do so, you may
* end up disrupting the scaffolding process and throw errors.
*
* YOU HAVE BEEN WARNED!
*
* @var array<string, string>
*/
public $views = [
'make:command' =>
'CodeIgniter\Commands\Generators\Views\command.tpl.php',
'make:controller' =>
'CodeIgniter\Commands\Generators\Views\controller.tpl.php',
'make:entity' => 'CodeIgniter\Commands\Generators\Views\entity.tpl.php',
'make:filter' => 'CodeIgniter\Commands\Generators\Views\filter.tpl.php',
'make:migration' =>
'CodeIgniter\Commands\Generators\Views\migration.tpl.php',
'make:model' => 'CodeIgniter\Commands\Generators\Views\model.tpl.php',
'make:seeder' => 'CodeIgniter\Commands\Generators\Views\seeder.tpl.php',
'make:validation' =>
'CodeIgniter\Commands\Generators\Views\validation.tpl.php',
'session:migration' =>
'CodeIgniter\Commands\Generators\Views\migration.tpl.php',
];
}

View File

@ -12,6 +12,7 @@ class Honeypot extends BaseConfig
* @var boolean
*/
public $hidden = true;
/**
* Honeypot Label Content
*
@ -32,4 +33,11 @@ class Honeypot extends BaseConfig
* @var string
*/
public $template = '<label>{label}</label><input type="text" name="{name}" value=""/>';
/**
* Honeypot container
*
* @var string
*/
public $container = '<div style="display:none">{template}</div>';
}

View File

@ -3,6 +3,8 @@
namespace Config;
use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Images\Handlers\GDHandler;
use CodeIgniter\Images\Handlers\ImageMagickHandler;
class Images extends BaseConfig
{
@ -24,20 +26,20 @@ class Images extends BaseConfig
/**
* The available handler classes.
*
* @var array
* @var array<string, string>
*/
public $handlers = [
'gd' => \CodeIgniter\Images\Handlers\GDHandler::class,
'imagick' => \CodeIgniter\Images\Handlers\ImageMagickHandler::class,
'gd' => GDHandler::class,
'imagick' => ImageMagickHandler::class,
];
/**
* --------------------------------------------------------------------------
* Uploaded images resizing sizes (in px)
* --------------------------------------------------------------------------
* The sizes listed below determine the resizing of images when uploaded.
* All uploaded images are of 1:1 ratio (width and height are the same).
*/
/*
|--------------------------------------------------------------------------
| Uploaded images resizing sizes (in px)
|--------------------------------------------------------------------------
| The sizes listed below determine the resizing of images when uploaded.
| All uploaded images are of 1:1 ratio (width and height are the same).
*/
/**
* @var integer
@ -68,12 +70,12 @@ class Images extends BaseConfig
*/
public $id3Size = 500;
/**
* --------------------------------------------------------------------------
* Uploaded images naming extensions
* --------------------------------------------------------------------------
* The properties listed below set the name extensions for the resized images
*/
/*
|--------------------------------------------------------------------------
| Uploaded images naming extensions
|--------------------------------------------------------------------------
| The properties listed below set the name extensions for the resized images
*/
/**
* @var string

View File

@ -5,26 +5,23 @@ namespace Config;
use CodeIgniter\Config\BaseConfig;
use Kint\Renderer\Renderer;
/**
* --------------------------------------------------------------------------
* Kint
* --------------------------------------------------------------------------
*
* We use Kint's `RichRenderer` and `CLIRenderer`. This area contains options
* that you can set to customize how Kint works for you.
*
* @see https://kint-php.github.io/kint/ for details on these settings.
*/
class Kint extends BaseConfig
{
/*
|--------------------------------------------------------------------------
| Kint
|--------------------------------------------------------------------------
|
| We use Kint's RichRenderer and CLIRenderer. This area contains options
| that you can set to customize how Kint works for you.
|
| For details on these settings, see Kint's docs:
| https://kint-php.github.io/kint/
|
*/
/*
|--------------------------------------------------------------------------
| Global Settings
|--------------------------------------------------------------------------
*/
|--------------------------------------------------------------------------
| Global Settings
|--------------------------------------------------------------------------
*/
public $plugins = null;
@ -35,10 +32,10 @@ class Kint extends BaseConfig
public $expanded = false;
/*
|--------------------------------------------------------------------------
| RichRenderer Settings
|--------------------------------------------------------------------------
*/
|--------------------------------------------------------------------------
| RichRenderer Settings
|--------------------------------------------------------------------------
*/
public $richTheme = 'aante-light.css';
public $richFolder = false;
@ -50,10 +47,10 @@ class Kint extends BaseConfig
public $richTabPlugins = null;
/*
|--------------------------------------------------------------------------
| CLI Settings
|--------------------------------------------------------------------------
*/
|--------------------------------------------------------------------------
| CLI Settings
|--------------------------------------------------------------------------
*/
public $cliColors = true;
public $cliForceUTF8 = false;

View File

@ -6,76 +6,82 @@ use CodeIgniter\Config\BaseConfig;
class Logger extends BaseConfig
{
/*
|--------------------------------------------------------------------------
| Error Logging Threshold
|--------------------------------------------------------------------------
|
| You can enable error logging by setting a threshold over zero. The
| threshold determines what gets logged. Any values below or equal to the
| threshold will be logged. Threshold options are:
|
| 0 = Disables logging, Error logging TURNED OFF
| 1 = Emergency Messages - System is unusable
| 2 = Alert Messages - Action Must Be Taken Immediately
| 3 = Critical Messages - Application component unavailable, unexpected exception.
| 4 = Runtime Errors - Don't need immediate action, but should be monitored.
| 5 = Warnings - Exceptional occurrences that are not errors.
| 6 = Notices - Normal but significant events.
| 7 = Info - Interesting events, like user logging in, etc.
| 8 = Debug - Detailed debug information.
| 9 = All Messages
|
| You can also pass an array with threshold levels to show individual error types
|
| array(1, 2, 3, 8) = Emergency, Alert, Critical, and Debug messages
|
| For a live site you'll usually enable Critical or higher (3) to be logged otherwise
| your log files will fill up very fast.
|
*/
public $threshold = 3;
/**
* --------------------------------------------------------------------------
* Error Logging Threshold
* --------------------------------------------------------------------------
*
* You can enable error logging by setting a threshold over zero. The
* threshold determines what gets logged. Any values below or equal to the
* threshold will be logged.
*
* Threshold options are:
*
* - 0 = Disables logging, Error logging TURNED OFF
* - 1 = Emergency Messages - System is unusable
* - 2 = Alert Messages - Action Must Be Taken Immediately
* - 3 = Critical Messages - Application component unavailable, unexpected exception.
* - 4 = Runtime Errors - Don't need immediate action, but should be monitored.
* - 5 = Warnings - Exceptional occurrences that are not errors.
* - 6 = Notices - Normal but significant events.
* - 7 = Info - Interesting events, like user logging in, etc.
* - 8 = Debug - Detailed debug information.
* - 9 = All Messages
*
* You can also pass an array with threshold levels to show individual error types
*
* array(1, 2, 3, 8) = Emergency, Alert, Critical, and Debug messages
*
* For a live site you'll usually enable Critical or higher (3) to be logged otherwise
* your log files will fill up very fast.
*
* @var integer|array
*/
public $threshold = 4;
/*
|--------------------------------------------------------------------------
| Date Format for Logs
|--------------------------------------------------------------------------
|
| Each item that is logged has an associated date. You can use PHP date
| codes to set your own date formatting
|
*/
/**
* --------------------------------------------------------------------------
* Date Format for Logs
* --------------------------------------------------------------------------
*
* Each item that is logged has an associated date. You can use PHP date
* codes to set your own date formatting
*
* @var string
*/
public $dateFormat = 'Y-m-d H:i:s';
/*
|--------------------------------------------------------------------------
| Log Handlers
|--------------------------------------------------------------------------
|
| The logging system supports multiple actions to be taken when something
| is logged. This is done by allowing for multiple Handlers, special classes
| designed to write the log to their chosen destinations, whether that is
| a file on the getServer, a cloud-based service, or even taking actions such
| as emailing the dev team.
|
| Each handler is defined by the class name used for that handler, and it
| MUST implement the CodeIgniter\Log\Handlers\HandlerInterface interface.
|
| The value of each key is an array of configuration items that are sent
| to the constructor of each handler. The only required configuration item
| is the 'handles' element, which must be an array of integer log levels.
| This is most easily handled by using the constants defined in the
| Psr\Log\LogLevel class.
|
| Handlers are executed in the order defined in this array, starting with
| the handler on top and continuing down.
|
*/
/**
* --------------------------------------------------------------------------
* Log Handlers
* --------------------------------------------------------------------------
*
* The logging system supports multiple actions to be taken when something
* is logged. This is done by allowing for multiple Handlers, special classes
* designed to write the log to their chosen destinations, whether that is
* a file on the getServer, a cloud-based service, or even taking actions such
* as emailing the dev team.
*
* Each handler is defined by the class name used for that handler, and it
* MUST implement the `CodeIgniter\Log\Handlers\HandlerInterface` interface.
*
* The value of each key is an array of configuration items that are sent
* to the constructor of each handler. The only required configuration item
* is the 'handles' element, which must be an array of integer log levels.
* This is most easily handled by using the constants defined in the
* `Psr\Log\LogLevel` class.
*
* Handlers are executed in the order defined in this array, starting with
* the handler on top and continuing down.
*
* @var array
*/
public $handlers = [
//--------------------------------------------------------------------
// File Handler
//--------------------------------------------------------------------
/*
* --------------------------------------------------------------------
* File Handler
* --------------------------------------------------------------------
*/
'CodeIgniter\Log\Handlers\FileHandler' => [
/*
* The log levels that this handler will handle.

View File

@ -6,46 +6,50 @@ use CodeIgniter\Config\BaseConfig;
class Migrations extends BaseConfig
{
/*
|--------------------------------------------------------------------------
| Enable/Disable Migrations
|--------------------------------------------------------------------------
|
| Migrations are enabled by default for security reasons.
| You should enable migrations whenever you intend to do a schema migration
| and disable it back when you're done.
|
*/
/**
* --------------------------------------------------------------------------
* Enable/Disable Migrations
* --------------------------------------------------------------------------
*
* Migrations are enabled by default.
*
* You should enable migrations whenever you intend to do a schema migration
* and disable it back when you're done.
*
* @var boolean
*/
public $enabled = true;
/*
|--------------------------------------------------------------------------
| Migrations table
|--------------------------------------------------------------------------
|
| This is the name of the table that will store the current migrations state.
| When migrations runs it will store in a database table which migration
| level the system is at. It then compares the migration level in this
| table to the $config['migration_version'] if they are not the same it
| will migrate up. This must be set.
|
*/
/**
* --------------------------------------------------------------------------
* Migrations Table
* --------------------------------------------------------------------------
*
* This is the name of the table that will store the current migrations state.
* When migrations runs it will store in a database table which migration
* level the system is at. It then compares the migration level in this
* table to the $config['migration_version'] if they are not the same it
* will migrate up. This must be set.
*
* @var string
*/
public $table = 'migrations';
/*
|--------------------------------------------------------------------------
| Timestamp Format
|--------------------------------------------------------------------------
|
| This is the format that will be used when creating new migrations
| using the cli command:
| > php spark migrate:create
|
| Typical formats:
| YmdHis_
| Y-m-d-His_
| Y_m_d_His_
|
*/
/**
* --------------------------------------------------------------------------
* Timestamp Format
* --------------------------------------------------------------------------
*
* This is the format that will be used when creating new migrations
* using the CLI command:
* > php spark migrate:create
*
* Typical formats:
* - YmdHis_
* - Y-m-d-His_
* - Y_m_d_His_
*
* @var string
*/
public $timestampFormat = 'Y-m-d-His_';
}

View File

@ -2,19 +2,21 @@
namespace Config;
/*
| -------------------------------------------------------------------
| MIME TYPES
| -------------------------------------------------------------------
| This file contains an array of mime types. It is used by the
| Upload class to help identify allowed file types.
|
| When more than one variation for an extension exist (like jpg, jpeg, etc)
| the most common one should be first in the array to aid the guess*
| methods. The same applies when more than one mime-type exists for a
| single extension.
|
*/
/**
* Mimes
*
* This file contains an array of mime types. It is used by the
* Upload class to help identify allowed file types.
*
* When more than one variation for an extension exist (like jpg, jpeg, etc)
* the most common one should be first in the array to aid the guess*
* methods. The same applies when more than one mime-type exists for a
* single extension.
*
* When working with mime types, please make sure you have the ´fileinfo´
* extension enabled to reliably detect the media types.
*/
class Mimes
{
/**
@ -34,7 +36,6 @@ class Mimes
'text/csv',
'text/x-comma-separated-values',
'text/comma-separated-values',
'application/octet-stream',
'application/vnd.ms-excel',
'application/x-csv',
'text/x-csv',
@ -64,7 +65,6 @@ class Mimes
'application/pdf',
'application/force-download',
'application/x-download',
'binary/octet-stream',
],
'ai' => ['application/pdf', 'application/postscript'],
'eps' => 'application/postscript',
@ -134,6 +134,7 @@ class Mimes
'multipart/x-zip',
],
'rar' => [
'application/vnd.rar',
'application/x-rar',
'application/rar',
'application/x-rar-compressed',
@ -305,15 +306,18 @@ class Mimes
'application/x-jar',
'application/x-compressed',
],
'svg' => ['image/svg+xml', 'application/xml', 'text/xml'],
'svg' => ['image/svg+xml', 'image/svg', 'application/xml', 'text/xml'],
'vcf' => 'text/x-vcard',
'srt' => ['text/srt', 'text/plain', 'application/octet-stream'],
'vtt' => ['text/vtt', 'text/plain'],
'ico' => ['image/x-icon', 'image/x-ico', 'image/vnd.microsoft.icon'],
'stl' => [
'application/sla',
'application/vnd.ms-pki.stl',
'application/x-navistyle',
],
];
//--------------------------------------------------------------------
/**
* Attempts to determine the best mime type for the given file extension.
*
@ -334,41 +338,47 @@ class Mimes
: static::$mimes[$extension];
}
//--------------------------------------------------------------------
/**
* Attempts to determine the best file extension for a given mime type.
*
* @param string $type
* @param string $proposed_extension - default extension (in case there is more than one with the same mime type)
* @param string $type
* @param string|null $proposedExtension - default extension (in case there is more than one with the same mime type)
*
* @return string|null The extension determined, or null if unable to match.
*/
public static function guessExtensionFromType(
string $type,
?string $proposed_extension = null
string $proposedExtension = null
) {
$type = trim(strtolower($type), '. ');
$proposed_extension = trim(strtolower($proposed_extension));
$proposedExtension = trim(strtolower($proposedExtension));
if (
!is_null($proposed_extension) &&
array_key_exists($proposed_extension, static::$mimes) &&
in_array(
$type,
is_string(static::$mimes[$proposed_extension])
? [static::$mimes[$proposed_extension]]
: static::$mimes[$proposed_extension]
)
) {
return $proposed_extension;
if ($proposedExtension !== '') {
if (
array_key_exists($proposedExtension, static::$mimes) &&
in_array(
$type,
is_string(static::$mimes[$proposedExtension])
? [static::$mimes[$proposedExtension]]
: static::$mimes[$proposedExtension],
true,
)
) {
// The detected mime type matches with the proposed extension.
return $proposedExtension;
}
// An extension was proposed, but the media type does not match the mime type list.
return null;
}
// Reverse check the mime type list if no extension was proposed.
// This search is order sensitive!
foreach (static::$mimes as $ext => $types) {
if (
(is_string($types) && $types === $type) ||
(is_array($types) && in_array($type, $types))
(is_array($types) && in_array($type, $types, true))
) {
return $ext;
}
@ -376,6 +386,4 @@ class Mimes
return null;
}
//--------------------------------------------------------------------
}

View File

@ -2,62 +2,46 @@
namespace Config;
// Cannot extend BaseConfig or looping resources occurs.
class Modules
use CodeIgniter\Modules\Modules as BaseModules;
class Modules extends BaseModules
{
/*
|--------------------------------------------------------------------------
| Auto-Discovery Enabled?
|--------------------------------------------------------------------------
|
| If true, then auto-discovery will happen across all elements listed in
| $activeExplorers below. If false, no auto-discovery will happen at all,
| giving a slight performance boost.
/**
* --------------------------------------------------------------------------
* Enable Auto-Discovery?
* --------------------------------------------------------------------------
*
* If true, then auto-discovery will happen across all elements listed in
* $activeExplorers below. If false, no auto-discovery will happen at all,
* giving a slight performance boost.
*
* @var boolean
*/
public $enabled = true;
/*
|--------------------------------------------------------------------------
| Auto-Discovery Within Composer Packages Enabled?
|--------------------------------------------------------------------------
|
| If true, then auto-discovery will happen across all namespaces loaded
| by Composer, as well as the namespaces configured locally.
/**
* --------------------------------------------------------------------------
* Enable Auto-Discovery Within Composer Packages?
* --------------------------------------------------------------------------
*
* If true, then auto-discovery will happen across all namespaces loaded
* by Composer, as well as the namespaces configured locally.
*
* @var boolean
*/
public $discoverInComposer = true;
/*
|--------------------------------------------------------------------------
| Auto-discover Rules
|--------------------------------------------------------------------------
|
| Lists the aliases of all discovery classes that will be active
| and used during the current application request. If it is not
| listed here, only the base application elements will be used.
*/
public $activeExplorers = ['events', 'registrars', 'routes', 'services'];
/**
* Should the application auto-discover the requested resources.
* --------------------------------------------------------------------------
* Auto-Discovery Rules
* --------------------------------------------------------------------------
*
* Valid values are:
* - events
* - registrars
* - routes
* - services
* Aliases list of all discovery classes that will be active and used during
* the current application request.
*
* @param string $alias
* If it is not listed, only the base application elements will be used.
*
* @return boolean
* @var string[]
*/
public function shouldDiscover(string $alias)
{
if (!$this->enabled) {
return false;
}
$alias = strtolower($alias);
return in_array($alias, $this->activeExplorers);
}
public $aliases = ['events', 'filters', 'registrars', 'routes', 'services'];
}

View File

@ -6,32 +6,34 @@ use CodeIgniter\Config\BaseConfig;
class Pager extends BaseConfig
{
/*
|--------------------------------------------------------------------------
| Templates
|--------------------------------------------------------------------------
|
| Pagination links are rendered out using views to configure their
| appearance. This array contains aliases and the view names to
| use when rendering the links.
|
| Within each view, the Pager object will be available as $pager,
| and the desired group as $pagerGroup;
|
*/
/**
* --------------------------------------------------------------------------
* Templates
* --------------------------------------------------------------------------
*
* Pagination links are rendered out using views to configure their
* appearance. This array contains aliases and the view names to
* use when rendering the links.
*
* Within each view, the Pager object will be available as $pager,
* and the desired group as $pagerGroup;
*
* @var array<string, string>
*/
public $templates = [
'default_full' => 'App\Views\pager\default_full',
'default_simple' => 'CodeIgniter\Pager\Views\default_simple',
'default_head' => 'CodeIgniter\Pager\Views\default_head',
];
/*
|--------------------------------------------------------------------------
| Items Per Page
|--------------------------------------------------------------------------
|
| The default number of results shown in a single page.
|
*/
/**
* --------------------------------------------------------------------------
* Items Per Page
* --------------------------------------------------------------------------
*
* The default number of results shown in a single page.
*
* @var integer
*/
public $perPage = 20;
}

View File

@ -3,9 +3,12 @@
namespace Config;
/**
* Paths
*
* Holds the paths that are used by the system to
* locate the main directories, app, system, etc.
* Modifying these allows you to re-structure your application,
*
* Modifying these allows you to restructure your application,
* share a system folder between multiple applications, and more.
*
* All paths are relative to the project's root folder.
@ -13,34 +16,35 @@ namespace Config;
class Paths
{
/*
*---------------------------------------------------------------
/**
* ---------------------------------------------------------------
* SYSTEM FOLDER NAME
*---------------------------------------------------------------
* ---------------------------------------------------------------
*
* This variable must contain the name of your "system" folder.
* Include the path if the folder is not in the same directory
* as this file.
* This must contain the name of your "system" folder. Include
* the path if the folder is not in the same directory as this file.
*
* @var string
*/
public $systemDirectory =
__DIR__ . '/../../vendor/codeigniter4/codeigniter4/system';
/*
*---------------------------------------------------------------
/**
* ---------------------------------------------------------------
* APPLICATION FOLDER NAME
*---------------------------------------------------------------
* ---------------------------------------------------------------
*
* If you want this front controller to use a different "app"
* folder than the default one you can set its name here. The folder
* can also be renamed or relocated anywhere on your getServer. If
* you do, use a full getServer path. For more info please see the user guide:
* http://codeigniter.com/user_guide/general/managing_apps.html
* you do, use a full getServer path.
*
* NO TRAILING SLASH!
* @see http://codeigniter.com/user_guide/general/managing_apps.html
*
* @var string
*/
public $appDirectory = __DIR__ . '/..';
/*
/**
* ---------------------------------------------------------------
* WRITABLE DIRECTORY NAME
* ---------------------------------------------------------------
@ -50,23 +54,23 @@ class Paths
* need write permission to a single place that can be tucked away
* for maximum security, keeping it out of the app and/or
* system directories.
*
* @var string
*/
public $writableDirectory = __DIR__ . '/../../writable';
/*
/**
* ---------------------------------------------------------------
* TESTS DIRECTORY NAME
* ---------------------------------------------------------------
*
* This variable must contain the name of your "tests" directory.
* The writable directory allows you to group all directories that
* need write permission to a single place that can be tucked away
* for maximum security, keeping it out of the app and/or
* system directories.
*
* @var string
*/
public $testsDirectory = __DIR__ . '/../../tests';
/*
/**
* ---------------------------------------------------------------
* VIEW DIRECTORY NAME
* ---------------------------------------------------------------
@ -75,6 +79,8 @@ class Paths
* contains the view files used by your application. By
* default this is in `app/Views`. This value
* is used when no value is provided to `Services::renderer()`.
*
* @var string
*/
public $viewDirectory = __DIR__ . '/../Views';
}

View File

@ -29,10 +29,19 @@ $routes->setAutoRoute(false);
* --------------------------------------------------------------------
*/
$routes->addPlaceholder('podcastName', '[a-zA-Z0-9\_]{1,191}');
$routes->addPlaceholder('podcastName', '[a-zA-Z0-9\_]{1,32}');
$routes->addPlaceholder('slug', '[a-zA-Z0-9\-]{1,191}');
$routes->addPlaceholder('base64', '[A-Za-z0-9\.\_]+\-{0,2}');
$routes->addPlaceholder('platformType', '\bpodcasting|\bsocial|\bfunding');
$routes->addPlaceholder('noteAction', '\bfavourite|\breblog|\breply');
$routes->addPlaceholder(
'embeddablePlayerTheme',
'\blight|\bdark|\blight-transparent|\bdark-transparent',
);
$routes->addPlaceholder(
'uuid',
'[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}',
);
/**
* --------------------------------------------------------------------
@ -62,7 +71,7 @@ $routes->group(config('App')->installGateway, function ($routes) {
});
// Route for podcast audio file analytics (/audio/pack(podcast_id,episode_id,bytes_threshold,filesize,duration,date)/podcast_folder/filename.mp3)
$routes->add('audio/(:base64)/(:any)', 'Analytics::hit/$1/$2', [
$routes->get('audio/(:base64)/(:any)', 'Analytics::hit/$1/$2', [
'as' => 'analytics_hit',
]);
@ -150,7 +159,7 @@ $routes->group(
$routes->post('edit', 'Podcast::attemptEdit/$1', [
'filter' => 'permission:podcast-edit',
]);
$routes->add('delete', 'Podcast::delete/$1', [
$routes->get('delete', 'Podcast::delete/$1', [
'as' => 'podcast-delete',
'filter' => 'permission:podcasts-delete',
]);
@ -170,7 +179,7 @@ $routes->group(
[
'as' => 'podcast-person-remove',
'filter' => 'permission:podcast-edit',
]
],
);
});
@ -185,7 +194,7 @@ $routes->group(
[
'as' => 'podcast-analytics-webpages',
'filter' => 'permission:podcasts-view,podcast-view',
]
],
);
$routes->get(
'locations',
@ -193,7 +202,7 @@ $routes->group(
[
'as' => 'podcast-analytics-locations',
'filter' => 'permission:podcasts-view,podcast-view',
]
],
);
$routes->get(
'unique-listeners',
@ -201,7 +210,7 @@ $routes->group(
[
'as' => 'podcast-analytics-unique-listeners',
'filter' => 'permission:podcasts-view,podcast-view',
]
],
);
$routes->get(
'listening-time',
@ -209,7 +218,7 @@ $routes->group(
[
'as' => 'podcast-analytics-listening-time',
'filter' => 'permission:podcasts-view,podcast-view',
]
],
);
$routes->get(
'time-periods',
@ -217,7 +226,7 @@ $routes->group(
[
'as' => 'podcast-analytics-time-periods',
'filter' => 'permission:podcasts-view,podcast-view',
]
],
);
$routes->get(
'players',
@ -225,7 +234,7 @@ $routes->group(
[
'as' => 'podcast-analytics-players',
'filter' => 'permission:podcasts-view,podcast-view',
]
],
);
});
@ -235,7 +244,7 @@ $routes->group(
[
'as' => 'analytics-full-data',
'filter' => 'permission:podcasts-view,podcast-view',
]
],
);
$routes->get(
'analytics-data/(:segment)/(:segment)',
@ -243,7 +252,7 @@ $routes->group(
[
'as' => 'analytics-data',
'filter' => 'permission:podcasts-view,podcast-view',
]
],
);
$routes->get(
'analytics-data/(:segment)/(:segment)/(:num)',
@ -251,7 +260,7 @@ $routes->group(
[
'as' => 'analytics-filtered-data',
'filter' => 'permission:podcasts-view,podcast-view',
]
],
);
// Podcast episodes
@ -283,7 +292,50 @@ $routes->group(
$routes->post('edit', 'Episode::attemptEdit/$1/$2', [
'filter' => 'permission:podcast_episodes-edit',
]);
$routes->add('delete', 'Episode::delete/$1/$2', [
$routes->get('publish', 'Episode::publish/$1/$2', [
'as' => 'episode-publish',
'filter' =>
'permission:podcast-manage_publications',
]);
$routes->post(
'publish',
'Episode::attemptPublish/$1/$2',
[
'filter' =>
'permission:podcast-manage_publications',
],
);
$routes->get(
'publish-edit',
'Episode::publishEdit/$1/$2',
[
'as' => 'episode-publish_edit',
'filter' =>
'permission:podcast-manage_publications',
],
);
$routes->post(
'publish-edit',
'Episode::attemptPublishEdit/$1/$2',
[
'filter' =>
'permission:podcast-manage_publications',
],
);
$routes->get('unpublish', 'Episode::unpublish/$1/$2', [
'as' => 'episode-unpublish',
'filter' =>
'permission:podcast-manage_publications',
]);
$routes->post(
'unpublish',
'Episode::attemptUnpublish/$1/$2',
[
'filter' =>
'permission:podcast-manage_publications',
],
);
$routes->get('delete', 'Episode::delete/$1/$2', [
'as' => 'episode-delete',
'filter' => 'permission:podcast_episodes-delete',
]);
@ -293,7 +345,7 @@ $routes->group(
[
'as' => 'transcript-delete',
'filter' => 'permission:podcast_episodes-edit',
]
],
);
$routes->get(
'chapters-delete',
@ -301,7 +353,7 @@ $routes->group(
[
'as' => 'chapters-delete',
'filter' => 'permission:podcast_episodes-edit',
]
],
);
$routes->get(
'soundbites',
@ -309,22 +361,22 @@ $routes->group(
[
'as' => 'soundbites-edit',
'filter' => 'permission:podcast_episodes-edit',
]
],
);
$routes->post(
'soundbites',
'Episode::soundbitesAttemptEdit/$1/$2',
[
'filter' => 'permission:podcast_episodes-edit',
]
],
);
$routes->add(
$routes->get(
'soundbites/(:num)/delete',
'Episode::soundbiteDelete/$1/$2/$3',
[
'as' => 'soundbite-delete',
'filter' => 'permission:podcast_episodes-edit',
]
],
);
$routes->get(
'embeddable-player',
@ -332,7 +384,7 @@ $routes->group(
[
'as' => 'embeddable-player-add',
'filter' => 'permission:podcast_episodes-edit',
]
],
);
$routes->group('persons', function ($routes) {
@ -346,7 +398,7 @@ $routes->group(
[
'filter' =>
'permission:podcast_episodes-edit',
]
],
);
$routes->get(
'(:num)/remove',
@ -355,7 +407,7 @@ $routes->group(
'as' => 'episode-person-remove',
'filter' =>
'permission:podcast_episodes-edit',
]
],
);
});
});
@ -394,9 +446,9 @@ $routes->group(
[
'filter' =>
'permission:podcast-manage_contributors',
]
],
);
$routes->add('remove', 'Contributor::remove/$1/$2', [
$routes->get('remove', 'Contributor::remove/$1/$2', [
'as' => 'contributor-remove',
'filter' =>
'permission:podcast-manage_contributors',
@ -411,7 +463,7 @@ $routes->group(
[
'as' => 'platforms-podcasting',
'filter' => 'permission:podcast-manage_platforms',
]
],
);
$routes->get(
'social',
@ -419,7 +471,7 @@ $routes->group(
[
'as' => 'platforms-social',
'filter' => 'permission:podcast-manage_platforms',
]
],
);
$routes->get(
'funding',
@ -427,7 +479,7 @@ $routes->group(
[
'as' => 'platforms-funding',
'filter' => 'permission:podcast-manage_platforms',
]
],
);
$routes->post(
'save/(:platformType)',
@ -435,20 +487,35 @@ $routes->group(
[
'as' => 'platforms-save',
'filter' => 'permission:podcast-manage_platforms',
]
],
);
$routes->add(
$routes->get(
'(:slug)/podcast-platform-remove',
'PodcastPlatform::removePodcastPlatform/$1/$2',
[
'as' => 'podcast-platform-remove',
'filter' => 'permission:podcast-manage_platforms',
]
],
);
});
});
});
// Instance wide Fediverse config
$routes->group('fediverse', function ($routes) {
$routes->get('/', 'Fediverse::dashboard', [
'as' => 'fediverse-dashboard',
]);
$routes->get('blocked-actors', 'Fediverse::blockedActors', [
'as' => 'fediverse-blocked-actors',
'filter' => 'permission:fediverse-block_actors',
]);
$routes->get('blocked-domains', 'Fediverse::blockedDomains', [
'as' => 'fediverse-blocked-domains',
'filter' => 'permission:fediverse-block_domains',
]);
});
// Pages
$routes->group('pages', function ($routes) {
$routes->get('/', 'Page::list', ['as' => 'page-list']);
@ -470,7 +537,7 @@ $routes->group(
'filter' => 'permission:pages-manage',
]);
$routes->add('delete', 'Page::delete/$1', [
$routes->get('delete', 'Page::delete/$1', [
'as' => 'page-delete',
'filter' => 'permission:pages-manage',
]);
@ -504,19 +571,19 @@ $routes->group(
$routes->post('edit', 'User::attemptEdit/$1', [
'filter' => 'permission:users-manage_authorizations',
]);
$routes->add('ban', 'User::ban/$1', [
$routes->get('ban', 'User::ban/$1', [
'as' => 'user-ban',
'filter' => 'permission:users-manage_bans',
]);
$routes->add('unban', 'User::unBan/$1', [
$routes->get('unban', 'User::unBan/$1', [
'as' => 'user-unban',
'filter' => 'permission:users-manage_bans',
]);
$routes->add('force-pass-reset', 'User::forcePassReset/$1', [
$routes->get('force-pass-reset', 'User::forcePassReset/$1', [
'as' => 'user-force_pass_reset',
'filter' => 'permission:users-force_pass_reset',
]);
$routes->add('delete', 'User::delete/$1', [
$routes->get('delete', 'User::delete/$1', [
'as' => 'user-delete',
'filter' => 'permission:users-delete',
]);
@ -533,7 +600,7 @@ $routes->group(
]);
$routes->post('change-password', 'MyAccount::attemptChange/$1');
});
}
},
);
/**
@ -570,35 +637,142 @@ $routes->group(config('App')->authGateway, function ($routes) {
$routes->post('reset-password', 'Auth::attemptReset');
});
// Public routes
// Podcast's Public routes
$routes->group('@(:podcastName)', function ($routes) {
$routes->get('/', 'Podcast/$1', ['as' => 'podcast']);
$routes->group('(:slug)', function ($routes) {
$routes->get('/', 'Podcast::activity/$1', [
'as' => 'podcast-activity',
]);
// override default ActivityPub Library's actor route
$routes->get('/', 'Podcast::activity/$1', [
'as' => 'actor',
'alternate-content' => [
'application/activity+json' => [
'namespace' => 'ActivityPub\Controllers',
'controller-method' => 'ActorController/$1',
],
'application/ld+json; profile="https://www.w3.org/ns/activitystreams' => [
'namespace' => 'ActivityPub\Controllers',
'controller-method' => 'ActorController/$1',
],
],
]);
$routes->get('episodes', 'Podcast::episodes/$1', [
'as' => 'podcast-episodes',
]);
$routes->group('episodes/(:slug)', function ($routes) {
$routes->get('/', 'Episode/$1/$2', [
'as' => 'episode',
]);
$routes->get('oembed.json', 'Episode::oembedJSON/$1/$2', [
'as' => 'episode-oembed-json',
]);
$routes->get('oembed.xml', 'Episode::oembedXML/$1/$2', [
'as' => 'episode-oembed-xml',
]);
$routes->group('embeddable-player', function ($routes) {
$routes->get('/', 'Episode::embeddablePlayer/$1/$2', [
'as' => 'embeddable-player',
]);
$routes->get('(:slug)', 'Episode::embeddablePlayer/$1/$2/$3', [
'as' => 'embeddable-player-theme',
]);
$routes->get(
'(:embeddablePlayerTheme)',
'Episode::embeddablePlayer/$1/$2/$3',
[
'as' => 'embeddable-player-theme',
],
);
});
});
$routes->head('feed.xml', 'Feed/$1', ['as' => 'podcast_feed']);
$routes->get('feed.xml', 'Feed/$1', ['as' => 'podcast_feed']);
});
// Other pages
$routes->get('/credits', 'Page::credits', ['as' => 'credits']);
$routes->get('/(:slug)', 'Page/$1', ['as' => 'page']);
// interacting as an actor
$routes->post('interact-as-actor', 'Auth::attemptInteractAsActor', [
'as' => 'interact-as-actor',
]);
/**
* Overwriting ActivityPub routes file
*/
$routes->group('@(:podcastName)', function ($routes) {
$routes->post('notes/new', 'Note::attemptCreate/$1', [
'as' => 'note-attempt-create',
'filter' => 'permission:podcast-manage_publications',
]);
// Note
$routes->group('notes/(:uuid)', function ($routes) {
$routes->get('/', 'Note/$1/$2', [
'as' => 'note',
'alternate-content' => [
'application/activity+json' => [
'namespace' => 'ActivityPub\Controllers',
'controller-method' => 'NoteController/$2',
],
'application/ld+json; profile="https://www.w3.org/ns/activitystreams' => [
'namespace' => 'ActivityPub\Controllers',
'controller-method' => 'NoteController/$2',
],
],
]);
$routes->get('replies', 'Note/$1/$2', [
'as' => 'note-replies',
'alternate-content' => [
'application/activity+json' => [
'namespace' => 'ActivityPub\Controllers',
'controller-method' => 'NoteController::replies/$2',
],
'application/ld+json; profile="https://www.w3.org/ns/activitystreams' => [
'namespace' => 'ActivityPub\Controllers',
'controller-method' => 'NoteController::replies/$2',
],
],
]);
// Actions
$routes->post('action', 'Note::attemptAction/$1/$2', [
'as' => 'note-attempt-action',
'filter' => 'permission:podcast-interact_as',
]);
$routes->post('block-actor', 'Note::attemptBlockActor/$1/$2', [
'as' => 'note-attempt-block-actor',
'filter' => 'permission:fediverse-block_actors',
]);
$routes->post('block-domain', 'Note::attemptBlockDomain/$1/$2', [
'as' => 'note-attempt-block-domain',
'filter' => 'permission:fediverse-block_domains',
]);
$routes->post('delete', 'Note::attemptDelete/$1/$2', [
'as' => 'note-attempt-delete',
'filter' => 'permission:podcast-manage_publications',
]);
$routes->get('remote/(:noteAction)', 'Note::remoteAction/$1/$2/$3', [
'as' => 'note-remote-action',
]);
});
$routes->get('follow', 'Actor::follow/$1', [
'as' => 'follow',
]);
$routes->get('outbox', 'Actor::outbox/$1', [
'as' => 'outbox',
'filter' => 'activity-pub:verify-activitystream',
]);
});
/*
* --------------------------------------------------------------------
* Additional Routing
* --------------------------------------------------------------------
*
* There will often be times that you need additional routing and you
* need to it be able to override any defaults in this file. Environment
* need it to be able to override any defaults in this file. Environment
* based routes is one such time. require() additional route files here
* to make that happen.
*

92
app/Config/Security.php Normal file
View File

@ -0,0 +1,92 @@
<?php
namespace Config;
use CodeIgniter\Config\BaseConfig;
class Security extends BaseConfig
{
/**
* --------------------------------------------------------------------------
* CSRF Token Name
* --------------------------------------------------------------------------
*
* Token name for Cross Site Request Forgery protection cookie.
*
* @var string
*/
public $tokenName = 'csrf_test_name';
/**
* --------------------------------------------------------------------------
* CSRF Header Name
* --------------------------------------------------------------------------
*
* Token name for Cross Site Request Forgery protection cookie.
*
* @var string
*/
public $headerName = 'X-CSRF-TOKEN';
/**
* --------------------------------------------------------------------------
* CSRF Cookie Name
* --------------------------------------------------------------------------
*
* Cookie name for Cross Site Request Forgery protection cookie.
*
* @var string
*/
public $cookieName = 'csrf_cookie_name';
/**
* --------------------------------------------------------------------------
* CSRF Expires
* --------------------------------------------------------------------------
*
* Expiration time for Cross Site Request Forgery protection cookie.
*
* Defaults to two hours (in seconds).
*
* @var integer
*/
public $expires = 7200;
/**
* --------------------------------------------------------------------------
* CSRF Regenerate
* --------------------------------------------------------------------------
*
* Regenerate CSRF Token on every request.
*
* @var boolean
*/
public $regenerate = true;
/**
* --------------------------------------------------------------------------
* CSRF Redirect
* --------------------------------------------------------------------------
*
* Redirect to previous page with error on failure.
*
* @var boolean
*/
public $redirect = true;
/**
* --------------------------------------------------------------------------
* CSRF SameSite
* --------------------------------------------------------------------------
*
* Setting for CSRF SameSite cookie token.
*
* Allowed values are: None - Lax - Strict - ''.
*
* Defaults to `Lax` as recommended in this link:
* @see https://portswigger.net/web-security/csrf/samesite-cookies
*
* @var string 'Lax'|'None'|'Strict'
*/
public $samesite = 'Lax';
}

View File

@ -2,17 +2,20 @@
namespace Config;
use CodeIgniter\Config\Services as CoreServices;
use CodeIgniter\Config\BaseService;
use CodeIgniter\Model;
use App\Authorization\FlatAuthorization;
use App\Authorization\PermissionModel;
use App\Authorization\GroupModel;
use App\Libraries\Breadcrumb;
use App\Libraries\Negotiate;
use App\Libraries\Router;
use App\Models\UserModel;
use CodeIgniter\HTTP\Request;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\Router\RouteCollectionInterface;
use Myth\Auth\Models\LoginModel;
require_once SYSTEMPATH . 'Config/Services.php';
/**
* Services Configuration file.
*
@ -26,8 +29,56 @@ require_once SYSTEMPATH . 'Config/Services.php';
* method format you should use for your service methods. For more examples,
* see the core Services file at system/Config/Services.php.
*/
class Services extends CoreServices
class Services extends BaseService
{
/**
* The Router class uses a RouteCollection's array of routes, and determines
* the correct Controller and Method to execute.
*
* @param RouteCollectionInterface|null $routes
* @param Request|null $request
* @param boolean $getShared
*
* @return Router
*/
public static function router(
RouteCollectionInterface $routes = null,
Request $request = null,
bool $getShared = true
) {
if ($getShared) {
return static::getSharedInstance('router', $routes, $request);
}
$routes = $routes ?? static::routes();
$request = $request ?? static::request();
return new Router($routes, $request);
}
/**
* The Negotiate class provides the content negotiation features for
* working the request to determine correct language, encoding, charset,
* and more.
*
* @param RequestInterface|null $request
* @param boolean $getShared
*
* @return Negotiate
*/
public static function negotiator(
RequestInterface $request = null,
bool $getShared = true
) {
if ($getShared) {
return static::getSharedInstance('negotiator', $request);
}
$request = $request ?? static::request();
return new Negotiate($request);
}
public static function authentication(
string $lib = 'local',
Model $userModel = null,
@ -39,7 +90,7 @@ class Services extends CoreServices
'authentication',
$lib,
$userModel,
$loginModel
$loginModel,
);
}
@ -72,7 +123,7 @@ class Services extends CoreServices
'authorization',
$groupModel,
$permissionModel,
$userModel
$userModel,
);
}

View File

@ -3,69 +3,85 @@
namespace Config;
use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Debug\Toolbar\Collectors\Database;
use CodeIgniter\Debug\Toolbar\Collectors\Events;
use CodeIgniter\Debug\Toolbar\Collectors\Files;
use CodeIgniter\Debug\Toolbar\Collectors\Logs;
use CodeIgniter\Debug\Toolbar\Collectors\Routes;
use CodeIgniter\Debug\Toolbar\Collectors\Timers;
use CodeIgniter\Debug\Toolbar\Collectors\Views;
/**
* --------------------------------------------------------------------------
* Debug Toolbar
* --------------------------------------------------------------------------
*
* The Debug Toolbar provides a way to see information about the performance
* and state of your application during that page display. By default it will
* NOT be displayed under production environments, and will only display if
* `CI_DEBUG` is true, since if it's not, there's not much to display anyway.
*/
class Toolbar extends BaseConfig
{
/*
|--------------------------------------------------------------------------
| Debug Toolbar
|--------------------------------------------------------------------------
| The Debug Toolbar provides a way to see information about the performance
| and state of your application during that page display. By default it will
| NOT be displayed under production environments, and will only display if
| CI_DEBUG is true, since if it's not, there's not much to display anyway.
|
| toolbarMaxHistory = Number of history files, 0 for none or -1 for unlimited
|
*/
/**
* --------------------------------------------------------------------------
* Toolbar Collectors
* --------------------------------------------------------------------------
*
* List of toolbar collectors that will be called when Debug Toolbar
* fires up and collects data from.
*
* @var string[]
*/
public $collectors = [
\CodeIgniter\Debug\Toolbar\Collectors\Timers::class,
\CodeIgniter\Debug\Toolbar\Collectors\Database::class,
\CodeIgniter\Debug\Toolbar\Collectors\Logs::class,
\CodeIgniter\Debug\Toolbar\Collectors\Views::class,
\CodeIgniter\Debug\Toolbar\Collectors\Cache::class,
\CodeIgniter\Debug\Toolbar\Collectors\Files::class,
\CodeIgniter\Debug\Toolbar\Collectors\Routes::class,
\CodeIgniter\Debug\Toolbar\Collectors\Events::class,
\Myth\Auth\Collectors\Auth::class,
Timers::class,
Database::class,
Logs::class,
Views::class,
// Cache::class,
Files::class,
Routes::class,
Events::class,
];
/*
|--------------------------------------------------------------------------
| Max History
|--------------------------------------------------------------------------
| The Toolbar allows you to view recent requests that have been made to
| the application while the toolbar is active. This allows you to quickly
| view and compare multiple requests.
|
| $maxHistory sets a limit on the number of past requests that are stored,
| helping to conserve file space used to store them. You can set it to
| 0 (zero) to not have any history stored, or -1 for unlimited history.
|
*/
/**
* --------------------------------------------------------------------------
* Max History
* --------------------------------------------------------------------------
*
* `$maxHistory` sets a limit on the number of past requests that are stored,
* helping to conserve file space used to store them. You can set it to
* 0 (zero) to not have any history stored, or -1 for unlimited history.
*
* @var integer
*/
public $maxHistory = 20;
/*
|--------------------------------------------------------------------------
| Toolbar Views Path
|--------------------------------------------------------------------------
| The full path to the the views that are used by the toolbar.
| MUST have a trailing slash.
|
*/
/**
* --------------------------------------------------------------------------
* Toolbar Views Path
* --------------------------------------------------------------------------
*
* The full path to the the views that are used by the toolbar.
* This MUST have a trailing slash.
*
* @var string
*/
public $viewsPath = SYSTEMPATH . 'Debug/Toolbar/Views/';
/*
|--------------------------------------------------------------------------
| Max Queries
|--------------------------------------------------------------------------
| If the Database Collector is enabled, it will log every query that the
| the system generates so they can be displayed on the toolbar's timeline
| and in the query log. This can lead to memory issues in some instances
| with hundreds of queries.
|
| $maxQueries defines the maximum amount of queries that will be stored.
|
*/
/**
* --------------------------------------------------------------------------
* Max Queries
* --------------------------------------------------------------------------
*
* If the Database Collector is enabled, it will log every query that the
* the system generates so they can be displayed on the toolbar's timeline
* and in the query log. This can lead to memory issues in some instances
* with hundreds of queries.
*
* `$maxQueries` defines the maximum amount of queries that will be stored.
*
* @var integer
*/
public $maxQueries = 100;
}

View File

@ -1,20 +1,27 @@
<?php
namespace Config;
use CodeIgniter\Config\BaseConfig;
/**
* -------------------------------------------------------------------
* User Agents
* -------------------------------------------------------------------
*
* This file contains four arrays of user agent data. It is used by the
* User Agent Class to help identify browser, platform, robot, and
* mobile device data. The array keys are used to identify the device
* and the array values are used to set the actual name of the item.
*/
class UserAgents extends BaseConfig
{
/*
| -------------------------------------------------------------------
| USER AGENT TYPES
| -------------------------------------------------------------------
| This file contains four arrays of user agent data. It is used by the
| User Agent Class to help identify browser, platform, robot, and
| mobile device data. The array keys are used to identify the device
| and the array values are used to set the actual name of the item.
*/
/**
* -------------------------------------------------------------------
* OS Platforms
* -------------------------------------------------------------------
*
* @var array<string, string>
*/
public $platforms = [
'windows nt 10.0' => 'Windows 10',
'windows nt 6.3' => 'Windows 8.1',
@ -60,12 +67,21 @@ class UserAgents extends BaseConfig
'symbian' => 'Symbian OS',
];
// The order of this array should NOT be changed. Many browsers return
// multiple browser types so we want to identify the sub-type first.
/**
* -------------------------------------------------------------------
* Browsers
* -------------------------------------------------------------------
*
* The order of this array should NOT be changed. Many browsers return
* multiple browser types so we want to identify the subtype first.
*
* @var array<string, string>
*/
public $browsers = [
'OPR' => 'Opera',
'Flock' => 'Flock',
'Edge' => 'Spartan',
'Edg' => 'Edge',
'Chrome' => 'Chrome',
// Opera 10+ always reports Opera/9.80 and appends Version/<real version> to the user agent string
'Opera.*?Version' => 'Opera',
@ -95,6 +111,13 @@ class UserAgents extends BaseConfig
'Vivaldi' => 'Vivaldi',
];
/**
* -------------------------------------------------------------------
* Mobiles
* -------------------------------------------------------------------
*
* @var array<string, string>
*/
public $mobiles = [
// legacy array, old values commented out
'mobileexplorer' => 'Mobile Explorer',
@ -195,7 +218,15 @@ class UserAgents extends BaseConfig
'cellphone' => 'Generic Mobile',
];
// There are hundreds of bots but these are the most common.
/**
* -------------------------------------------------------------------
* Robots
* -------------------------------------------------------------------
*
* There are hundred of bots but these are the most common.
*
* @var array<string, string>
*/
public $robots = [
'googlebot' => 'Googlebot',
'msnbot' => 'MSNBot',

View File

@ -2,6 +2,14 @@
namespace Config;
use App\Validation\FileRules as AppFileRules;
use App\Validation\Rules as AppRules;
use CodeIgniter\Validation\CreditCardRules;
use CodeIgniter\Validation\FileRules;
use CodeIgniter\Validation\FormatRules;
use CodeIgniter\Validation\Rules;
use Myth\Auth\Authentication\Passwords\ValidationRules as PasswordRules;
class Validation
{
//--------------------------------------------------------------------
@ -12,22 +20,23 @@ class Validation
* Stores the classes that contain the
* rules that are available.
*
* @var array
* @var string[]
*/
public $ruleSets = [
\CodeIgniter\Validation\Rules::class,
\CodeIgniter\Validation\FormatRules::class,
\CodeIgniter\Validation\CreditCardRules::class,
\App\Validation\Rules::class,
\App\Validation\FileRules::class,
\Myth\Auth\Authentication\Passwords\ValidationRules::class,
Rules::class,
FormatRules::class,
FileRules::class,
CreditCardRules::class,
AppRules::class,
AppFileRules::class,
PasswordRules::class,
];
/**
* Specifies the views that are used to display the
* errors.
*
* @var array
* @var array<string, string>
*/
public $templates = [
'list' => 'CodeIgniter\Validation\Views\list',

View File

@ -2,7 +2,9 @@
namespace Config;
class View extends \CodeIgniter\Config\View
use CodeIgniter\Config\View as BaseView;
class View extends BaseView
{
/**
* When false, the view method will clear the data between each
@ -11,6 +13,8 @@ class View extends \CodeIgniter\Config\View
* to each view. You might prefer to have the data stick around between
* calls so that it is available to all views. If that is the case,
* set $saveData to true.
*
* @var boolean
*/
public $saveData = true;
@ -24,6 +28,8 @@ class View extends \CodeIgniter\Config\View
* Examples:
* { title|esc(js) }
* { created_on|date(Y-m-d)|esc(attr) }
*
* @var array
*/
public $filters = [];
@ -31,6 +37,8 @@ class View extends \CodeIgniter\Config\View
* Parser Plugins provide a way to extend the functionality provided
* by the core Parser by creating aliases that will be replaced with
* any callable. Can be single or tag pair.
*
* @var array
*/
public $plugins = [];
}

22
app/Controllers/Actor.php Normal file
View File

@ -0,0 +1,22 @@
<?php
/**
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Controllers;
class Actor extends \ActivityPub\Controllers\ActorController
{
public function follow()
{
helper(['form', 'components', 'svg']);
$data = [
'actor' => $this->actor,
];
return view('podcast/follow', $data);
}
}

View File

@ -2,6 +2,11 @@
namespace App\Controllers\Admin;
use CodeIgniter\Controller;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Psr\Log\LoggerInterface;
/**
* Class BaseController
*
@ -11,12 +16,8 @@ namespace App\Controllers\Admin;
* class Home extends BaseController
*
* For security be sure to declare any new methods as protected or private.
*
* @package CodeIgniter
*/
use CodeIgniter\Controller;
class BaseController extends Controller
{
/**
@ -30,11 +31,15 @@ class BaseController extends Controller
/**
* Constructor.
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @param LoggerInterface $logger
*/
public function initController(
\CodeIgniter\HTTP\RequestInterface $request,
\CodeIgniter\HTTP\ResponseInterface $response,
\Psr\Log\LoggerInterface $logger
RequestInterface $request,
ResponseInterface $response,
LoggerInterface $logger
) {
// Do Not Edit This Line
parent::initController($request, $response, $logger);
@ -42,7 +47,6 @@ class BaseController extends Controller
//--------------------------------------------------------------------
// Preload any models, libraries, etc, here.
//--------------------------------------------------------------------
// E.g.:
// $this->session = \Config\Services::session();
// E.g.: $this->session = \Config\Services::session();
}
}

View File

@ -8,7 +8,9 @@
namespace App\Controllers\Admin;
use App\Entities\Note;
use App\Models\EpisodeModel;
use App\Models\NoteModel;
use App\Models\PodcastModel;
use App\Models\SoundbiteModel;
use CodeIgniter\I18n\Time;
@ -32,7 +34,11 @@ class Episode extends BaseController
public function _remap($method, ...$params)
{
$this->podcast = (new PodcastModel())->getPodcastById($params[0]);
if (
!($this->podcast = (new PodcastModel())->getPodcastById($params[0]))
) {
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
}
if (count($params) > 1) {
if (
@ -107,7 +113,6 @@ class Episode extends BaseController
'is_image[image]|ext_in[image,jpg,png]|min_dims[image,1400,1400]|is_image_squared[image]',
'transcript' => 'ext_in[transcript,txt,html,srt,json]',
'chapters' => 'ext_in[chapters,json]',
'publication_date' => 'valid_date[Y-m-d H:i]|permit_empty',
];
if (!$this->validate($rules)) {
@ -117,7 +122,6 @@ class Episode extends BaseController
->with('errors', $this->validator->getErrors());
}
$publicationDate = $this->request->getPost('publication_date');
$newEpisode = new \App\Entities\Episode([
'podcast_id' => $this->podcast->id,
'title' => $this->request->getPost('title'),
@ -142,15 +146,9 @@ class Episode extends BaseController
'type' => $this->request->getPost('type'),
'is_blocked' => $this->request->getPost('block') == 'yes',
'custom_rss_string' => $this->request->getPost('custom_rss'),
'created_by' => user(),
'updated_by' => user(),
'published_at' => $publicationDate
? Time::createFromFormat(
'Y-m-d H:i',
$publicationDate,
$this->request->getPost('client_timezone')
)->setTimezone('UTC')
: null,
'created_by' => user()->id,
'updated_by' => user()->id,
'published_at' => null,
]);
$episodeModel = new EpisodeModel();
@ -167,7 +165,7 @@ class Episode extends BaseController
if ($this->podcast->hasChanged('episode_description_footer_markdown')) {
$this->podcast->episode_description_footer_markdown = $this->request->getPost(
'description_footer'
'description_footer',
);
if (!$podcastModel->update($this->podcast->id, $this->podcast)) {
@ -209,7 +207,6 @@ class Episode extends BaseController
'is_image[image]|ext_in[image,jpg,png]|min_dims[image,1400,1400]|is_image_squared[image]',
'transcript' => 'ext_in[transcript,txt,html,srt,json]',
'chapters' => 'ext_in[chapters,json]',
'publication_date' => 'valid_date[Y-m-d H:i]|permit_empty',
];
if (!$this->validate($rules)) {
@ -222,7 +219,7 @@ class Episode extends BaseController
$this->episode->title = $this->request->getPost('title');
$this->episode->slug = $this->request->getPost('slug');
$this->episode->description_markdown = $this->request->getPost(
'description'
'description',
);
$this->episode->location = $this->request->getPost('location_name');
$this->episode->parental_advisory =
@ -238,19 +235,10 @@ class Episode extends BaseController
$this->episode->type = $this->request->getPost('type');
$this->episode->is_blocked = $this->request->getPost('block') == 'yes';
$this->episode->custom_rss_string = $this->request->getPost(
'custom_rss'
'custom_rss',
);
$publicationDate = $this->request->getPost('publication_date');
$this->episode->published_at = $publicationDate
? Time::createFromFormat(
'Y-m-d H:i',
$publicationDate,
$this->request->getPost('client_timezone')
)->setTimezone('UTC')
: null;
$this->episode->updated_by = user();
$this->episode->updated_by = user()->id;
$enclosure = $this->request->getFile('enclosure');
if ($enclosure->isValid()) {
@ -280,7 +268,7 @@ class Episode extends BaseController
// update podcast's episode_description_footer_markdown if changed
$this->podcast->episode_description_footer_markdown = $this->request->getPost(
'description_footer'
'description_footer',
);
if ($this->podcast->hasChanged('episode_description_footer_markdown')) {
@ -333,6 +321,271 @@ class Episode extends BaseController
return redirect()->back();
}
public function publish()
{
if ($this->episode->publication_status === 'not_published') {
helper(['form']);
$data = [
'podcast' => $this->podcast,
'episode' => $this->episode,
];
replace_breadcrumb_params([
0 => $this->podcast->title,
1 => $this->episode->title,
]);
return view('admin/episode/publish', $data);
} else {
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
}
}
public function attemptPublish()
{
$rules = [
'publication_method' => 'required',
'scheduled_publication_date' =>
'valid_date[Y-m-d H:i]|permit_empty',
];
if (!$this->validate($rules)) {
return redirect()
->back()
->withInput()
->with('errors', $this->validator->getErrors());
}
$db = \Config\Database::connect();
$db->transStart();
$newNote = new Note([
'actor_id' => $this->podcast->actor_id,
'episode_id' => $this->episode->id,
'message' => $this->request->getPost('message'),
'created_by' => user_id(),
]);
$publishMethod = $this->request->getPost('publication_method');
if ($publishMethod === 'schedule') {
$scheduledPublicationDate = $this->request->getPost(
'scheduled_publication_date',
);
if ($scheduledPublicationDate) {
$scheduledDateUTC = Time::createFromFormat(
'Y-m-d H:i',
$scheduledPublicationDate,
$this->request->getPost('client_timezone'),
)->setTimezone('UTC');
$this->episode->published_at = $scheduledDateUTC;
$newNote->published_at = $scheduledDateUTC;
} else {
$db->transRollback();
return redirect()
->back()
->withInput()
->with('error', 'Schedule date must be set!');
}
} else {
$dateNow = Time::now();
$this->episode->published_at = $dateNow;
$newNote->published_at = $dateNow;
}
$noteModel = new NoteModel();
if (!$noteModel->addNote($newNote)) {
$db->transRollback();
return redirect()
->back()
->withInput()
->with('errors', $noteModel->errors());
}
$episodeModel = new EpisodeModel();
if (!$episodeModel->update($this->episode->id, $this->episode)) {
$db->transRollback();
return redirect()
->back()
->withInput()
->with('errors', $episodeModel->errors());
}
$db->transComplete();
return redirect()->route('episode-view', [
$this->podcast->id,
$this->episode->id,
]);
}
public function publishEdit()
{
if ($this->episode->publication_status === 'scheduled') {
helper(['form']);
$data = [
'podcast' => $this->podcast,
'episode' => $this->episode,
'note' => (new NoteModel())
->where([
'actor_id' => $this->podcast->actor_id,
'episode_id' => $this->episode->id,
])
->first(),
];
replace_breadcrumb_params([
0 => $this->podcast->title,
1 => $this->episode->title,
]);
return view('admin/episode/publish_edit', $data);
} else {
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
}
}
public function attemptPublishEdit()
{
$rules = [
'note_id' => 'required',
'publication_method' => 'required',
'scheduled_publication_date' =>
'valid_date[Y-m-d H:i]|permit_empty',
];
if (!$this->validate($rules)) {
return redirect()
->back()
->withInput()
->with('errors', $this->validator->getErrors());
}
$db = \Config\Database::connect();
$db->transStart();
$note = (new NoteModel())->getNoteById(
$this->request->getPost('note_id'),
);
$note->message = $this->request->getPost('message');
$publishMethod = $this->request->getPost('publication_method');
if ($publishMethod === 'schedule') {
$scheduledPublicationDate = $this->request->getPost(
'scheduled_publication_date',
);
if ($scheduledPublicationDate) {
$scheduledDateUTC = Time::createFromFormat(
'Y-m-d H:i',
$scheduledPublicationDate,
$this->request->getPost('client_timezone'),
)->setTimezone('UTC');
$this->episode->published_at = $scheduledDateUTC;
$note->published_at = $scheduledDateUTC;
} else {
$db->transRollback();
return redirect()
->back()
->withInput()
->with('error', 'Schedule date must be set!');
}
} else {
$dateNow = Time::now();
$this->episode->published_at = $dateNow;
$note->published_at = $dateNow;
}
$noteModel = new NoteModel();
if (!$noteModel->editNote($note)) {
$db->transRollback();
return redirect()
->back()
->withInput()
->with('errors', $noteModel->errors());
}
$episodeModel = new EpisodeModel();
if (!$episodeModel->update($this->episode->id, $this->episode)) {
$db->transRollback();
return redirect()
->back()
->withInput()
->with('errors', $episodeModel->errors());
}
$db->transComplete();
return redirect()->route('episode-view', [
$this->podcast->id,
$this->episode->id,
]);
}
public function unpublish()
{
if ($this->episode->publication_status === 'published') {
helper(['form']);
$data = [
'podcast' => $this->podcast,
'episode' => $this->episode,
];
replace_breadcrumb_params([
0 => $this->podcast->title,
1 => $this->episode->title,
]);
return view('admin/episode/unpublish', $data);
} else {
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
}
}
public function attemptUnpublish()
{
$rules = [
'understand' => 'required',
];
if (!$this->validate($rules)) {
return redirect()
->back()
->withInput()
->with('errors', $this->validator->getErrors());
}
$db = \Config\Database::connect();
$db->transStart();
$allNotesLinkedToEpisode = (new NoteModel())
->where([
'episode_id' => $this->episode->id,
])
->findAll();
foreach ($allNotesLinkedToEpisode as $note) {
(new NoteModel())->removeNote($note);
}
// set episode published_at to null to unpublish
$this->episode->published_at = null;
$episodeModel = new EpisodeModel();
if (!$episodeModel->update($this->episode->id, $this->episode)) {
$db->transRollback();
return redirect()
->back()
->withInput()
->with('errors', $episodeModel->errors());
}
$db->transComplete();
return redirect()->route('episode-view', [
$this->podcast->id,
$this->episode->id,
]);
}
public function delete()
{
(new EpisodeModel())->delete($this->episode->id);
@ -416,7 +669,7 @@ class Episode extends BaseController
(new SoundbiteModel())->deleteSoundbite(
$this->podcast->id,
$this->episode->id,
$soundbiteId
$soundbiteId,
);
return redirect()->route('soundbites-edit', [

View File

@ -0,0 +1,41 @@
<?php
/**
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Controllers\Admin;
use ActivityPub\Models\BlockedDomainModel;
class Fediverse extends BaseController
{
public function dashboard()
{
return view('admin/fediverse/dashboard');
}
public function blockedActors()
{
helper(['form']);
$blockedActors = model('ActorModel')->getBlockedActors();
return view('admin/fediverse/blocked_actors', [
'blockedActors' => $blockedActors,
]);
}
public function blockedDomains()
{
helper(['form']);
$blockedDomains = model('BlockedDomainModel')->getBlockedDomains();
return view('admin/fediverse/blocked_domains', [
'blockedDomains' => $blockedDomains,
]);
}
}

View File

@ -125,7 +125,7 @@ class Person extends BaseController
$this->person->image = $image;
}
$this->updated_by = user();
$this->updated_by = user()->id;
$personModel = new PersonModel();
if (!$personModel->update($this->person->id, $this->person)) {

View File

@ -26,7 +26,7 @@ class Podcast extends BaseController
if (count($params) > 0) {
if (
!($this->podcast = (new PodcastModel())->getPodcastById(
$params[0]
$params[0],
))
) {
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
@ -124,7 +124,7 @@ class Podcast extends BaseController
'languageOptions' => $languageOptions,
'categoryOptions' => $categoryOptions,
'browserLang' => get_browser_language(
$this->request->getServer('HTTP_ACCEPT_LANGUAGE')
$this->request->getServer('HTTP_ACCEPT_LANGUAGE'),
),
];
@ -170,8 +170,8 @@ class Podcast extends BaseController
'is_blocked' => $this->request->getPost('block') === 'yes',
'is_completed' => $this->request->getPost('complete') === 'yes',
'is_locked' => $this->request->getPost('lock') === 'yes',
'created_by' => user(),
'updated_by' => user(),
'created_by' => user()->id,
'updated_by' => user()->id,
]);
$podcastModel = new PodcastModel();
@ -193,15 +193,19 @@ class Podcast extends BaseController
$podcastModel->addPodcastContributor(
user()->id,
$newPodcastId,
$podcastAdminGroup->id
$podcastAdminGroup->id,
);
// set Podcast categories
(new CategoryModel())->setPodcastCategories(
$newPodcastId,
$this->request->getPost('other_categories')
$this->request->getPost('other_categories'),
);
// set interact as the newly created podcast actor
$createdPodcast = (new PodcastModel())->getPodcastById($newPodcastId);
set_interact_as_actor($createdPodcast->actor_id);
$db->transComplete();
return redirect()->route('podcast-view', [$newPodcastId]);
@ -239,9 +243,8 @@ class Podcast extends BaseController
}
$this->podcast->title = $this->request->getPost('title');
$this->podcast->name = $this->request->getPost('name');
$this->podcast->description_markdown = $this->request->getPost(
'description'
'description',
);
$image = $this->request->getFile('image');
@ -261,10 +264,10 @@ class Podcast extends BaseController
$this->podcast->copyright = $this->request->getPost('copyright');
$this->podcast->location = $this->request->getPost('location_name');
$this->podcast->payment_pointer = $this->request->getPost(
'payment_pointer'
'payment_pointer',
);
$this->podcast->custom_rss_string = $this->request->getPost(
'custom_rss'
'custom_rss',
);
$this->podcast->partner_id = $this->request->getPost('partner_id');
$this->podcast->partner_link_url = $this->request->getPost(
@ -277,7 +280,7 @@ class Podcast extends BaseController
$this->podcast->is_completed =
$this->request->getPost('complete') === 'yes';
$this->podcast->is_locked = $this->request->getPost('lock') === 'yes';
$this->updated_by = user();
$this->updated_by = user()->id;
$db = \Config\Database::connect();
$db->transStart();
@ -294,7 +297,7 @@ class Podcast extends BaseController
// set Podcast categories
(new CategoryModel())->setPodcastCategories(
$this->podcast->id,
$this->request->getPost('other_categories')
$this->request->getPost('other_categories'),
);
$db->transComplete();

View File

@ -31,7 +31,7 @@ class PodcastImport extends BaseController
if (count($params) > 0) {
if (
!($this->podcast = (new PodcastModel())->getPodcastById(
$params[0]
$params[0],
))
) {
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
@ -52,7 +52,7 @@ class PodcastImport extends BaseController
'languageOptions' => $languageOptions,
'categoryOptions' => $categoryOptions,
'browserLang' => get_browser_language(
$this->request->getServer('HTTP_ACCEPT_LANGUAGE')
$this->request->getServer('HTTP_ACCEPT_LANGUAGE'),
),
];
@ -78,7 +78,7 @@ class PodcastImport extends BaseController
try {
ini_set('user_agent', 'Castopod/' . CP_VERSION);
$feed = simplexml_load_file(
$this->request->getPost('imported_feed_url')
$this->request->getPost('imported_feed_url'),
);
} catch (\ErrorException $ex) {
return redirect()
@ -94,13 +94,13 @@ class PodcastImport extends BaseController
]);
}
$nsItunes = $feed->channel[0]->children(
'http://www.itunes.com/dtds/podcast-1.0.dtd'
'http://www.itunes.com/dtds/podcast-1.0.dtd',
);
$nsPodcast = $feed->channel[0]->children(
'https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md'
'https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md',
);
$nsContent = $feed->channel[0]->children(
'http://purl.org/rss/1.0/modules/content/'
'http://purl.org/rss/1.0/modules/content/',
);
if ((string) $nsPodcast->locked === 'yes') {
@ -112,28 +112,30 @@ class PodcastImport extends BaseController
$converter = new HtmlConverter();
$channelDescriptionHtml = $feed->channel[0]->description;
$channelDescriptionHtml = (string) $feed->channel[0]->description;
try {
$podcast = new \App\Entities\Podcast([
'name' => $this->request->getPost('name'),
'imported_feed_url' => $this->request->getPost(
'imported_feed_url'
'imported_feed_url',
),
'new_feed_url' => base_url(
route_to('podcast_feed', $this->request->getPost('name'))
route_to('podcast_feed', $this->request->getPost('name')),
),
'title' => $feed->channel[0]->title,
'title' => (string) $feed->channel[0]->title,
'description_markdown' => $converter->convert(
$channelDescriptionHtml
$channelDescriptionHtml,
),
'description_html' => $channelDescriptionHtml,
'image' =>
$nsItunes->image && !empty($nsItunes->image->attributes())
? download_file($nsItunes->image->attributes())
? download_file((string) $nsItunes->image->attributes())
: ($feed->channel[0]->image &&
!empty($feed->channel[0]->image->url)
? download_file($feed->channel[0]->image->url)
? download_file(
(string) $feed->channel[0]->image->url,
)
: null),
'language_code' => $this->request->getPost('language'),
'category_id' => $this->request->getPost('category'),
@ -144,11 +146,11 @@ class PodcastImport extends BaseController
: (in_array($nsItunes->explicit, ['no', 'false'])
? 'clean'
: null)),
'owner_name' => $nsItunes->owner->name,
'owner_email' => $nsItunes->owner->email,
'publisher' => $nsItunes->author,
'owner_name' => (string) $nsItunes->owner->name,
'owner_email' => (string) $nsItunes->owner->email,
'publisher' => (string) $nsItunes->author,
'type' => empty($nsItunes->type) ? 'episodic' : $nsItunes->type,
'copyright' => $feed->channel[0]->copyright,
'copyright' => (string) $feed->channel[0]->copyright,
'is_blocked' => empty($nsItunes->block)
? false
: $nsItunes->block === 'yes',
@ -157,19 +159,19 @@ class PodcastImport extends BaseController
: $nsItunes->complete === 'yes',
'location_name' => !$nsPodcast->location
? null
: $nsPodcast->location,
: (string) $nsPodcast->location,
'location_geo' =>
!$nsPodcast->location ||
empty($nsPodcast->location->attributes()['geo'])
? null
: $nsPodcast->location->attributes()['geo'],
: (string) $nsPodcast->location->attributes()['geo'],
'location_osmid' =>
!$nsPodcast->location ||
empty($nsPodcast->location->attributes()['osm'])
? null
: $nsPodcast->location->attributes()['osm'],
'created_by' => user(),
'updated_by' => user(),
: (string) $nsPodcast->location->attributes()['osm'],
'created_by' => user()->id,
'updated_by' => user()->id,
]);
} catch (\ErrorException $ex) {
return redirect()
@ -204,7 +206,7 @@ class PodcastImport extends BaseController
$podcastModel->addPodcastContributor(
user()->id,
$newPodcastId,
$podcastAdminGroup->id
$podcastAdminGroup->id,
);
$podcastsPlatformsData = [];
@ -218,34 +220,21 @@ class PodcastImport extends BaseController
foreach ($platformType['elements'] as $platform) {
$platformLabel = $platform->attributes()['platform'];
$platformSlug = slugify($platformLabel);
if (!$platformModel->getPlatform($platformSlug)) {
if (
!$platformModel->createPlatform(
$platformSlug,
$platformType['name'],
$platformLabel,
''
)
) {
return redirect()
->back()
->withInput()
->with('errors', $platformModel->errors());
}
if ($platformModel->getPlatform($platformSlug)) {
array_push($podcastsPlatformsData, [
'platform_slug' => $platformSlug,
'podcast_id' => $newPodcastId,
'link_url' => $platform->attributes()['url'],
'link_content' => $platform->attributes()['id'],
'is_visible' => false,
]);
}
array_push($podcastsPlatformsData, [
'platform_slug' => $platformSlug,
'podcast_id' => $newPodcastId,
'link_url' => $platform->attributes()['url'],
'link_content' => $platform->attributes()['id'],
'is_visible' => false,
]);
}
}
if (count($podcastsPlatformsData) > 1) {
$platformModel->createPodcastPlatforms(
$newPodcastId,
$podcastsPlatformsData
$podcastsPlatformsData,
);
}
@ -259,7 +248,7 @@ class PodcastImport extends BaseController
!($newPersonId = $personModel->createPerson(
$podcastPerson,
$podcastPerson->attributes()['href'],
$podcastPerson->attributes()['img']
$podcastPerson->attributes()['img'],
))
) {
return redirect()
@ -312,19 +301,19 @@ class PodcastImport extends BaseController
$item = $feed->channel[0]->item[$numberItems - $itemNumber];
$nsItunes = $item->children(
'http://www.itunes.com/dtds/podcast-1.0.dtd'
'http://www.itunes.com/dtds/podcast-1.0.dtd',
);
$nsPodcast = $item->children(
'https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md'
'https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md',
);
$nsContent = $item->children(
'http://purl.org/rss/1.0/modules/content/'
'http://purl.org/rss/1.0/modules/content/',
);
$slug = slugify(
$this->request->getPost('slug_field') === 'title'
? $item->title
: basename($item->link)
: basename($item->link),
);
if (in_array($slug, $slugs)) {
$slugNumber = 2;
@ -358,13 +347,15 @@ class PodcastImport extends BaseController
'slug' => $slug,
'enclosure' => download_file($item->enclosure->attributes()),
'description_markdown' => $converter->convert(
$itemDescriptionHtml
$itemDescriptionHtml,
),
'description_html' => $itemDescriptionHtml,
'image' =>
!$nsItunes->image || empty($nsItunes->image->attributes())
? null
: download_file($nsItunes->image->attributes()),
: download_file(
(string) $nsItunes->image->attributes(),
),
'parental_advisory' => empty($nsItunes->explicit)
? null
: (in_array($nsItunes->explicit, ['yes', 'true'])
@ -404,8 +395,8 @@ class PodcastImport extends BaseController
empty($nsPodcast->location->attributes()['osm'])
? null
: $nsPodcast->location->attributes()['osm'],
'created_by' => user(),
'updated_by' => user(),
'created_by' => user()->id,
'updated_by' => user()->id,
'published_at' => strtotime($item->pubDate),
]);
@ -429,7 +420,7 @@ class PodcastImport extends BaseController
!($newPersonId = $personModel->createPerson(
$episodePerson,
$episodePerson->attributes()['href'],
$episodePerson->attributes()['img']
$episodePerson->attributes()['img'],
))
) {
return redirect()
@ -458,8 +449,8 @@ class PodcastImport extends BaseController
'person_group' => $personGroup['slug'],
'person_role' => $personRole['slug'],
]);
$episodePersonModel = new EpisodePersonModel();
$episodePersonModel = new EpisodePersonModel();
if (!$episodePersonModel->insert($newEpisodePerson)) {
return redirect()
->back()
@ -469,6 +460,10 @@ class PodcastImport extends BaseController
}
}
// set interact as the newly imported podcast actor
$importedPodcast = (new PodcastModel())->getPodcastById($newPodcastId);
set_interact_as_actor($importedPodcast->actor_id);
$db->transComplete();
return redirect()->route('podcast-view', [$newPodcastId]);

View File

@ -55,7 +55,7 @@ class Auth extends \Myth\Auth\Controllers\AuthController
$allowedPostFields = array_merge(
['password'],
$this->config->validFields,
$this->config->personalFields
$this->config->personalFields,
);
$user = new User($this->request->getPost($allowedPostFields));
@ -85,7 +85,7 @@ class Auth extends \Myth\Auth\Controllers\AuthController
->withInput()
->with(
'error',
$activator->error() ?? lang('Auth.unknownError')
$activator->error() ?? lang('Auth.unknownError'),
);
}
@ -122,7 +122,7 @@ class Auth extends \Myth\Auth\Controllers\AuthController
$this->request->getPost('email'),
$this->request->getPost('token'),
$this->request->getIPAddress(),
(string) $this->request->getUserAgent()
(string) $this->request->getUserAgent(),
);
$rules = [
@ -172,4 +172,24 @@ class Auth extends \Myth\Auth\Controllers\AuthController
->route('login')
->with('message', lang('Auth.resetSuccess'));
}
public function attemptInteractAsActor()
{
$rules = [
'actor_id' => 'required|numeric',
];
if (!$this->validate($rules)) {
return redirect()
->back()
->withInput()
->with('errors', service('validation')->getErrors());
}
helper('auth');
set_interact_as_actor($this->request->getPost('actor_id'));
return redirect()->back();
}
}

View File

@ -1,5 +1,12 @@
<?php
namespace App\Controllers;
use CodeIgniter\Controller;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Psr\Log\LoggerInterface;
/**
* Class BaseController
*
@ -9,14 +16,7 @@
* class Home extends BaseController
*
* For security be sure to declare any new methods as protected or private.
*
* @package CodeIgniter
*/
namespace App\Controllers;
use CodeIgniter\Controller;
class BaseController extends Controller
{
/**
@ -26,15 +26,19 @@ class BaseController extends Controller
*
* @var array
*/
protected $helpers = ['analytics', 'svg', 'components', 'misc'];
protected $helpers = ['auth', 'analytics', 'svg', 'components', 'misc'];
/**
* Constructor.
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @param LoggerInterface $logger
*/
public function initController(
\CodeIgniter\HTTP\RequestInterface $request,
\CodeIgniter\HTTP\ResponseInterface $response,
\Psr\Log\LoggerInterface $logger
RequestInterface $request,
ResponseInterface $response,
LoggerInterface $logger
) {
// Do Not Edit This Line
parent::initController($request, $response, $logger);
@ -42,8 +46,7 @@ class BaseController extends Controller
//--------------------------------------------------------------------
// Preload any models, libraries, etc, here.
//--------------------------------------------------------------------
// E.g.:
// $this->session = \Config\Services::session();
// E.g.: $this->session = \Config\Services::session();
set_user_session_deny_list_ip();
set_user_session_browser();

View File

@ -10,6 +10,7 @@ namespace App\Controllers;
use App\Models\EpisodeModel;
use App\Models\PodcastModel;
use SimpleXMLElement;
class Episode extends BaseController
{
@ -31,7 +32,7 @@ class Episode extends BaseController
count($params) > 1 &&
!($this->episode = (new EpisodeModel())->getEpisodeBySlug(
$this->podcast->id,
$params[1]
$params[1],
))
) {
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
@ -43,44 +44,49 @@ class Episode extends BaseController
public function index()
{
self::triggerWebpageHit($this->episode->podcast_id);
$episodeModel = new EpisodeModel();
self::triggerWebpageHit($this->podcast->id);
$locale = service('request')->getLocale();
$cacheName = "page_podcast{$this->episode->podcast_id}_episode{$this->episode->id}_{$locale}";
if (!($cachedView = cache($cacheName))) {
$episodeModel = new EpisodeModel();
$previousNextEpisodes = $episodeModel->getPreviousNextEpisodes(
$this->episode,
$this->podcast->type
);
helper(['persons']);
$persons = [];
construct_episode_person_array(
$this->episode->episode_persons,
$persons
);
helper('persons');
$episodePersons = [];
construct_person_array($this->episode->persons, $episodePersons);
$podcastPersons = [];
construct_person_array($this->podcast->persons, $podcastPersons);
$data = [
'previousEpisode' => $previousNextEpisodes['previous'],
'nextEpisode' => $previousNextEpisodes['next'],
'podcast' => $this->podcast,
'episode' => $this->episode,
'persons' => $persons,
'episodePersons' => $episodePersons,
'persons' => $podcastPersons,
];
$secondsToNextUnpublishedEpisode = $episodeModel->getSecondsToNextUnpublishedEpisode(
$this->podcast->id
$this->podcast->id,
);
// The page cache is set to a decade so it is deleted manually upon podcast update
return view('episode', $data, [
'cache' => $secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE,
'cache_name' => $cacheName,
]);
if (can_user_interact()) {
helper('form');
// The page cache is set to a decade so it is deleted manually upon podcast update
return view('podcast/episode_authenticated', $data, [
'cache' => $secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE,
'cache_name' => $cacheName . '_authenticated',
]);
} else {
// The page cache is set to a decade so it is deleted manually upon podcast update
return view('podcast/episode', $data, [
'cache' => $secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE,
'cache_name' => $cacheName,
]);
}
}
return $cachedView;
@ -97,7 +103,7 @@ class Episode extends BaseController
if (isset($_SERVER['HTTP_REFERER'])) {
$session->set(
'embeddable_player_domain',
parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST)
parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST),
);
}
@ -108,26 +114,15 @@ class Episode extends BaseController
if (!($cachedView = cache($cacheName))) {
$episodeModel = new EpisodeModel();
$theme = EpisodeModel::$themes[$theme];
helper(['persons']);
$persons = [];
construct_episode_person_array(
$this->episode->episode_persons,
$persons
);
constructs_podcast_person_array(
$this->podcast->podcast_persons,
$persons
);
$data = [
'podcast' => $this->podcast,
'episode' => $this->episode,
'persons' => $persons,
'theme' => $theme,
];
$secondsToNextUnpublishedEpisode = $episodeModel->getSecondsToNextUnpublishedEpisode(
$this->podcast->id
$this->podcast->id,
);
// The page cache is set to a decade so it is deleted manually upon podcast update
@ -141,4 +136,56 @@ class Episode extends BaseController
return $cachedView;
}
public function oembedJSON()
{
return $this->response->setJSON([
'type' => 'rich',
'version' => '1.0',
'title' => $this->episode->title,
'provider_name' => $this->podcast->title,
'provider_url' => $this->podcast->link,
'author_name' => $this->podcast->title,
'author_url' => $this->podcast->link,
'html' =>
'<iframe src="' .
$this->episode->embeddable_player .
'" width="100%" height="200" frameborder="0" scrolling="no"></iframe>',
'width' => 600,
'height' => 200,
'thumbnail_url' => $this->episode->image->large_url,
'thumbnail_width' => config('Images')->largeSize,
'thumbnail_height' => config('Images')->largeSize,
]);
}
public function oembedXML()
{
$oembed = new SimpleXMLElement(
"<?xml version='1.0' encoding='utf-8' standalone='yes'?><oembed></oembed>",
);
$oembed->addChild('type', 'rich');
$oembed->addChild('version', '1.0');
$oembed->addChild('title', $this->episode->title);
$oembed->addChild('provider_name', $this->podcast->title);
$oembed->addChild('provider_url', $this->podcast->link);
$oembed->addChild('author_name', $this->podcast->title);
$oembed->addChild('author_url', $this->podcast->link);
$oembed->addChild('thumbnail', $this->episode->image->large_url);
$oembed->addChild('thumbnail_width', config('Images')->largeSize);
$oembed->addChild('thumbnail_height', config('Images')->largeSize);
$oembed->addChild(
'html',
htmlentities(
'<iframe src="' .
$this->episode->embeddable_player .
'" width="100%" height="200" frameborder="0" scrolling="no"></iframe>',
),
);
$oembed->addChild('width', 600);
$oembed->addChild('height', 200);
return $this->response->setXML($oembed);
}
}

View File

@ -20,7 +20,9 @@ class Home extends BaseController
// check if there's only one podcast to redirect user to it
if (count($allPodcasts) == 1) {
return redirect()->route('podcast', [$allPodcasts[0]->name]);
return redirect()->route('podcast-activity', [
$allPodcasts[0]->name,
]);
}
// default behavior: list all podcasts on home page

View File

@ -257,6 +257,7 @@ class Install extends Controller
$migrations = \Config\Services::migrations();
!$migrations->setNamespace('Myth\Auth')->latest();
!$migrations->setNamespace('ActivityPub')->latest();
!$migrations->setNamespace(APP_NAMESPACE)->latest();
}

212
app/Controllers/Note.php Normal file
View File

@ -0,0 +1,212 @@
<?php
/**
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Controllers;
use App\Models\EpisodeModel;
use App\Models\PodcastModel;
use CodeIgniter\HTTP\URI;
use CodeIgniter\I18n\Time;
class Note extends \ActivityPub\Controllers\NoteController
{
/**
* @var \App\Entities\Podcast
*/
protected $podcast;
protected $helpers = ['auth', 'activitypub', 'svg', 'components', 'misc'];
public function _remap($method, ...$params)
{
if (
!($this->podcast = (new PodcastModel())->getPodcastByName(
$params[0],
))
) {
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
}
$this->actor = $this->podcast->actor;
if (count($params) > 1) {
if (!($this->note = model('NoteModel')->getNoteById($params[1]))) {
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
}
}
unset($params[0]);
unset($params[1]);
return $this->$method(...$params);
}
public function index()
{
helper('persons');
$persons = [];
construct_person_array($this->podcast->persons, $persons);
$data = [
'podcast' => $this->podcast,
'actor' => $this->actor,
'note' => $this->note,
'persons' => $persons,
];
// if user is logged in then send to the authenticated activity view
if (can_user_interact()) {
helper('form');
return view('podcast/note_authenticated', $data);
} else {
return view('podcast/note', $data);
}
}
public function attemptCreate()
{
$rules = [
'message' => 'required|max_length[500]',
'episode_url' => 'valid_url|permit_empty',
];
if (!$this->validate($rules)) {
return redirect()
->back()
->withInput()
->with('errors', $this->validator->getErrors());
}
$message = $this->request->getPost('message');
$newNote = new \App\Entities\Note([
'actor_id' => interact_as_actor_id(),
'published_at' => Time::now(),
'created_by' => user_id(),
]);
// get episode if episodeUrl has been set
$episodeUri = $this->request->getPost('episode_url');
if (
$episodeUri &&
($params = extract_params_from_episode_uri(new URI($episodeUri)))
) {
if (
$episode = (new EpisodeModel())->getEpisodeBySlug(
$params['podcastName'],
$params['episodeSlug'],
)
) {
$newNote->episode_id = $episode->id;
}
}
$newNote->message = $message;
if (
!model('NoteModel')->addNote(
$newNote,
$newNote->episode_id ? false : true,
true,
)
) {
return redirect()
->back()
->withInput()
->with('errors', model('NoteModel')->errors());
}
// Note has been successfully created
return redirect()->back();
}
public function attemptReply()
{
$rules = [
'message' => 'required|max_length[500]',
];
if (!$this->validate($rules)) {
return redirect()
->back()
->withInput()
->with('errors', $this->validator->getErrors());
}
$newNote = new \ActivityPub\Entities\Note([
'actor_id' => interact_as_actor_id(),
'in_reply_to_id' => $this->note->id,
'message' => $this->request->getPost('message'),
'published_at' => Time::now(),
'created_by' => user_id(),
]);
if (!model('NoteModel')->addReply($newNote)) {
return redirect()
->back()
->withInput()
->with('errors', model('NoteModel')->errors());
}
// Reply note without preview card has been successfully created
return redirect()->back();
}
public function attemptFavourite()
{
model('FavouriteModel')->toggleFavourite(
interact_as_actor(),
$this->note,
);
return redirect()->back();
}
public function attemptReblog()
{
model('NoteModel')->toggleReblog(interact_as_actor(), $this->note);
return redirect()->back();
}
public function attemptAction()
{
$rules = [
'action' => 'required|in_list[favourite,reblog,reply]',
];
if (!$this->validate($rules)) {
return redirect()
->back()
->withInput()
->with('errors', $this->validator->getErrors());
}
switch ($this->request->getPost('action')) {
case 'favourite':
return $this->attemptFavourite();
case 'reblog':
return $this->attemptReblog();
case 'reply':
return $this->attemptReply();
}
}
public function remoteAction($action)
{
$data = [
'podcast' => $this->podcast,
'actor' => $this->actor,
'note' => $this->note,
'action' => $action,
];
helper('form');
return view('podcast/note_remote_action', $data);
}
}

View File

@ -85,14 +85,23 @@ class Page extends BaseController
'role_label' => $credit->role_label,
'is_in' => [
[
'link' => $credit->episode
'link' => $credit->episode_id
? $credit->episode->link
: $credit->podcast->link,
'title' => $credit->episode
'title' => $credit->episode_id
? (count($allPodcasts) > 1
? "{$credit->podcast->title}"
: '') .
"(S{$credit->episode->season_number}E{$credit->episode->number}) {$credit->episode->title}"
$credit->episode
->title .
episode_numbering(
$credit->episode
->number,
$credit->episode
->season_number,
'text-xs ml-2',
true,
)
: $credit->podcast->title,
],
],
@ -114,14 +123,21 @@ class Page extends BaseController
'role_label' => $credit->role_label,
'is_in' => [
[
'link' => $credit->episode
'link' => $credit->episode_id
? $credit->episode->link
: $credit->podcast->link,
'title' => $credit->episode
'title' => $credit->episode_id
? (count($allPodcasts) > 1
? "{$credit->podcast->title}"
: '') .
"(S{$credit->episode->season_number}E{$credit->episode->number}) {$credit->episode->title}"
$credit->episode->title .
episode_numbering(
$credit->episode->number,
$credit->episode
->season_number,
'text-xs ml-2',
true,
)
: $credit->podcast->title,
],
],
@ -143,7 +159,13 @@ class Page extends BaseController
? (count($allPodcasts) > 1
? "{$credit->podcast->title}"
: '') .
"(S{$credit->episode->season_number}E{$credit->episode->number}) {$credit->episode->title}"
$credit->episode->title .
episode_numbering(
$credit->episode->number,
$credit->episode->season_number,
'text-xs ml-2',
true,
)
: $credit->podcast->title,
],
],
@ -159,7 +181,13 @@ class Page extends BaseController
? (count($allPodcasts) > 1
? "{$credit->podcast->title}"
: '') .
"(S{$credit->episode->season_number}E{$credit->episode->number}) {$credit->episode->title}"
$credit->episode->title .
episode_numbering(
$credit->episode->number,
$credit->episode->season_number,
'text-xs ml-2',
true,
)
: $credit->podcast->title,
];
}

View File

@ -10,6 +10,7 @@ namespace App\Controllers;
use App\Models\EpisodeModel;
use App\Models\PodcastModel;
use App\Models\NoteModel;
class Podcast extends BaseController
{
@ -23,17 +24,41 @@ class Podcast extends BaseController
if (count($params) > 0) {
if (
!($this->podcast = (new PodcastModel())->getPodcastByName(
$params[0]
$params[0],
))
) {
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
}
unset($params[0]);
}
return $this->$method();
return $this->$method(...$params);
}
public function index()
public function activity()
{
helper('persons');
$persons = [];
construct_person_array($this->podcast->persons, $persons);
$data = [
'podcast' => $this->podcast,
'notes' => (new NoteModel())->getActorNotes(
$this->podcast->actor_id,
),
'persons' => $persons,
];
// if user is logged in then send to the authenticated activity view
if (can_user_interact()) {
helper('form');
return view('podcast/activity_authenticated', $data);
} else {
return view('podcast/activity', $data);
}
}
public function episodes()
{
self::triggerWebpageHit($this->podcast->id);
@ -42,7 +67,7 @@ class Podcast extends BaseController
if (!$yearQuery and !$seasonQuery) {
$defaultQuery = (new EpisodeModel())->getDefaultQuery(
$this->podcast->id
$this->podcast->id,
);
if ($defaultQuery['type'] == 'season') {
$seasonQuery = $defaultQuery['data']['season_number'];
@ -59,7 +84,7 @@ class Podcast extends BaseController
$yearQuery,
$seasonQuery ? 'season' . $seasonQuery : null,
service('request')->getLocale(),
])
]),
);
if (!($found = cache($cacheName))) {
@ -73,14 +98,19 @@ class Podcast extends BaseController
foreach ($years as $year) {
$isActive = $yearQuery == $year['year'];
if ($isActive) {
$activeQuery = ['type' => 'year', 'value' => $year['year']];
$activeQuery = [
'type' => 'year',
'value' => $year['year'],
'label' => $year['year'],
'number_of_episodes' => $year['number_of_episodes'],
];
}
array_push($episodesNavigation, [
'label' => $year['year'],
'number_of_episodes' => $year['number_of_episodes'],
'route' =>
route_to('podcast', $this->podcast->name) .
route_to('podcast-episodes', $this->podcast->name) .
'?year=' .
$year['year'],
'is_active' => $isActive,
@ -93,6 +123,10 @@ class Podcast extends BaseController
$activeQuery = [
'type' => 'season',
'value' => $season['season_number'],
'label' => lang('Podcast.season', [
'seasonNumber' => $season['season_number'],
]),
'number_of_episodes' => $season['number_of_episodes'],
];
}
@ -102,19 +136,16 @@ class Podcast extends BaseController
]),
'number_of_episodes' => $season['number_of_episodes'],
'route' =>
route_to('podcast', $this->podcast->name) .
route_to('podcast-episodes', $this->podcast->name) .
'?season=' .
$season['season_number'],
'is_active' => $isActive,
]);
}
helper(['persons']);
helper('persons');
$persons = [];
constructs_podcast_person_array(
$this->podcast->podcast_persons,
$persons
);
construct_person_array($this->podcast->persons, $persons);
$data = [
'podcast' => $this->podcast,
@ -124,21 +155,31 @@ class Podcast extends BaseController
$this->podcast->id,
$this->podcast->type,
$yearQuery,
$seasonQuery
$seasonQuery,
),
'personArray' => $persons,
'persons' => $persons,
];
$secondsToNextUnpublishedEpisode = $episodeModel->getSecondsToNextUnpublishedEpisode(
$this->podcast->id
$this->podcast->id,
);
return view('podcast', $data, [
'cache' => $secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE,
'cache_name' => $cacheName,
]);
// if user is logged in then send to the authenticated episodes view
if (can_user_interact()) {
return view('podcast/episodes_authenticated', $data, [
'cache' => $secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE,
'cache_name' => $cacheName . '_authenticated',
]);
} else {
return view('podcast/episodes', $data, [
'cache' => $secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE,
'cache_name' => $cacheName,
]);
}
}
return $found;

View File

@ -39,7 +39,7 @@ class AddCategories extends Migration
'constraint' => 32,
],
]);
$this->forge->addKey('id', true);
$this->forge->addPrimaryKey('id');
$this->forge->addUniqueKey('code');
$this->forge->addForeignKey('parent_id', 'categories', 'id');
$this->forge->createTable('categories');

View File

@ -28,7 +28,7 @@ class AddLanguages extends Migration
'constraint' => 128,
],
]);
$this->forge->addKey('code', true);
$this->forge->addPrimaryKey('code');
$this->forge->createTable('languages');
}

View File

@ -23,14 +23,17 @@ class AddPodcasts extends Migration
'unsigned' => true,
'auto_increment' => true,
],
'title' => [
'type' => 'VARCHAR',
'constraint' => 128,
'actor_id' => [
'type' => 'INT',
'unsigned' => true,
],
'name' => [
'type' => 'VARCHAR',
'constraint' => 32,
'unique' => true,
],
'title' => [
'type' => 'VARCHAR',
'constraint' => 128,
],
'description_markdown' => [
'type' => 'TEXT',
@ -42,6 +45,12 @@ class AddPodcasts extends Migration
'type' => 'VARCHAR',
'constraint' => 255,
],
// constraint is 13 because the longest safe mimetype for images is image/svg+xml,
// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#image_types
'image_mimetype' => [
'type' => 'VARCHAR',
'constraint' => 13,
],
'language_code' => [
'type' => 'VARCHAR',
'constraint' => 2,
@ -140,6 +149,7 @@ class AddPodcasts extends Migration
],
'custom_rss' => [
'type' => 'JSON',
'null' => true,
],
'partner_id' => [
'type' => 'VARCHAR',
@ -176,7 +186,15 @@ class AddPodcasts extends Migration
],
]);
$this->forge->addKey('id', true);
$this->forge->addPrimaryKey('id');
$this->forge->addUniqueKey('name');
$this->forge->addForeignKey(
'actor_id',
'activitypub_actors',
'id',
false,
'CASCADE',
);
$this->forge->addForeignKey('category_id', 'categories', 'id');
$this->forge->addForeignKey('language_code', 'languages', 'code');
$this->forge->addForeignKey('created_by', 'users', 'id');

View File

@ -73,6 +73,13 @@ class AddEpisodes extends Migration
'constraint' => 255,
'null' => true,
],
// constraint is 13 because the longest safe mimetype for images is image/svg+xml,
// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#image_types
'image_mimetype' => [
'type' => 'VARCHAR',
'constraint' => 13,
'null' => true,
],
'transcript_uri' => [
'type' => 'VARCHAR',
'constraint' => 255,
@ -128,6 +135,21 @@ class AddEpisodes extends Migration
'type' => 'JSON',
'null' => true,
],
'favourites_total' => [
'type' => 'INT',
'unsigned' => true,
'default' => 0,
],
'reblogs_total' => [
'type' => 'INT',
'unsigned' => true,
'default' => 0,
],
'notes_total' => [
'type' => 'INT',
'unsigned' => true,
'default' => 0,
],
'created_by' => [
'type' => 'INT',
'unsigned' => true,
@ -151,9 +173,15 @@ class AddEpisodes extends Migration
'null' => true,
],
]);
$this->forge->addKey('id', true);
$this->forge->addPrimaryKey('id');
$this->forge->addUniqueKey(['podcast_id', 'slug']);
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id');
$this->forge->addForeignKey(
'podcast_id',
'podcasts',
'id',
false,
'CASCADE',
);
$this->forge->addForeignKey('created_by', 'users', 'id');
$this->forge->addForeignKey('updated_by', 'users', 'id');
$this->forge->createTable('episodes');

View File

@ -63,8 +63,20 @@ class AddSoundbites extends Migration
]);
$this->forge->addKey('id', true);
$this->forge->addUniqueKey(['episode_id', 'start_time', 'duration']);
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id');
$this->forge->addForeignKey('episode_id', 'episodes', 'id');
$this->forge->addForeignKey(
'podcast_id',
'podcasts',
'id',
false,
'CASCADE',
);
$this->forge->addForeignKey(
'episode_id',
'episodes',
'id',
false,
'CASCADE',
);
$this->forge->addForeignKey('created_by', 'users', 'id');
$this->forge->addForeignKey('updated_by', 'users', 'id');
$this->forge->createTable('soundbites');

View File

@ -45,7 +45,7 @@ class AddPlatforms extends Migration
$this->forge->addField(
'`updated_at` timestamp NOT NULL DEFAULT NOW() ON UPDATE NOW()'
);
$this->forge->addKey('slug', true);
$this->forge->addPrimaryKey('slug');
$this->forge->createTable('platforms');
}

View File

@ -3,6 +3,7 @@
/**
* Class AddAnalyticsPodcasts
* Creates analytics_podcasts table in database
*
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
@ -45,12 +46,11 @@ class AddAnalyticsPodcasts extends Migration
]);
$this->forge->addPrimaryKey(['podcast_id', 'date']);
$this->forge->addField(
'`created_at` timestamp NOT NULL DEFAULT current_timestamp()'
'`created_at` timestamp NOT NULL DEFAULT current_timestamp()',
);
$this->forge->addField(
'`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()'
'`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()',
);
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id');
$this->forge->createTable('analytics_podcasts');
}

View File

@ -3,6 +3,7 @@
/**
* Class AddAnalyticsPodcastsByEpisode
* Creates analytics_episodes_by_episode table in database
*
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
@ -41,13 +42,11 @@ class AddAnalyticsPodcastsByEpisode extends Migration
]);
$this->forge->addPrimaryKey(['podcast_id', 'date', 'episode_id']);
$this->forge->addField(
'`created_at` timestamp NOT NULL DEFAULT current_timestamp()'
'`created_at` timestamp NOT NULL DEFAULT current_timestamp()',
);
$this->forge->addField(
'`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()'
'`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()',
);
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id');
$this->forge->addForeignKey('episode_id', 'episodes', 'id');
$this->forge->createTable('analytics_podcasts_by_episode');
}

View File

@ -3,6 +3,7 @@
/**
* Class AddAnalyticsPodcastsByHour
* Creates analytics_podcasts_by_hour table in database
*
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
@ -36,12 +37,11 @@ class AddAnalyticsPodcastsByHour extends Migration
]);
$this->forge->addPrimaryKey(['podcast_id', 'date', 'hour']);
$this->forge->addField(
'`created_at` timestamp NOT NULL DEFAULT current_timestamp()'
'`created_at` timestamp NOT NULL DEFAULT current_timestamp()',
);
$this->forge->addField(
'`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()'
'`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()',
);
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id');
$this->forge->createTable('analytics_podcasts_by_hour');
}

View File

@ -3,6 +3,7 @@
/**
* Class AddAnalyticsPodcastsByPlayer
* Creates analytics_podcasts_by_player table in database
*
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
@ -61,12 +62,11 @@ class AddAnalyticsPodcastsByPlayer extends Migration
'is_bot',
]);
$this->forge->addField(
'`created_at` timestamp NOT NULL DEFAULT current_timestamp()'
'`created_at` timestamp NOT NULL DEFAULT current_timestamp()',
);
$this->forge->addField(
'`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()'
'`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()',
);
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id');
$this->forge->createTable('analytics_podcasts_by_player');
}

View File

@ -3,6 +3,7 @@
/**
* Class AddAnalyticsPodcastsByCountry
* Creates analytics_podcasts_by_country table in database
*
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
@ -37,12 +38,11 @@ class AddAnalyticsPodcastsByCountry extends Migration
]);
$this->forge->addPrimaryKey(['podcast_id', 'date', 'country_code']);
$this->forge->addField(
'`created_at` timestamp NOT NULL DEFAULT current_timestamp()'
'`created_at` timestamp NOT NULL DEFAULT current_timestamp()',
);
$this->forge->addField(
'`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()'
'`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()',
);
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id');
$this->forge->createTable('analytics_podcasts_by_country');
}

View File

@ -3,6 +3,7 @@
/**
* Class AddAnalyticsPodcastsByRegion
* Creates analytics_podcasts_by_region table in database
*
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
@ -55,12 +56,11 @@ class AddAnalyticsPodcastsByRegion extends Migration
'region_code',
]);
$this->forge->addField(
'`created_at` timestamp NOT NULL DEFAULT current_timestamp()'
'`created_at` timestamp NOT NULL DEFAULT current_timestamp()',
);
$this->forge->addField(
'`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()'
'`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()',
);
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id');
$this->forge->createTable('analytics_podcasts_by_region');
}

View File

@ -48,8 +48,6 @@ class AddPodcastsPlatforms extends Migration
]);
$this->forge->addPrimaryKey(['podcast_id', 'platform_slug']);
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id');
$this->forge->addForeignKey('platform_slug', 'platforms', 'slug');
$this->forge->createTable('podcasts_platforms');
}

View File

@ -3,6 +3,7 @@
/**
* Class AddAnalyticsWebsiteByBrowser
* Creates analytics_website_by_browser table in database
*
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
@ -37,12 +38,11 @@ class AddAnalyticsWebsiteByBrowser extends Migration
$this->forge->addPrimaryKey(['podcast_id', 'date', 'browser']);
$this->forge->addField(
'`created_at` timestamp NOT NULL DEFAULT current_timestamp()'
'`created_at` timestamp NOT NULL DEFAULT current_timestamp()',
);
$this->forge->addField(
'`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()'
'`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()',
);
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id');
$this->forge->createTable('analytics_website_by_browser');
}

View File

@ -3,6 +3,7 @@
/**
* Class AddAnalyticsWebsiteByReferer
* Creates analytics_website_by_referer table in database
*
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
@ -46,12 +47,11 @@ class AddAnalyticsWebsiteByReferer extends Migration
]);
$this->forge->addPrimaryKey(['podcast_id', 'date', 'referer_url']);
$this->forge->addField(
'`created_at` timestamp NOT NULL DEFAULT current_timestamp()'
'`created_at` timestamp NOT NULL DEFAULT current_timestamp()',
);
$this->forge->addField(
'`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()'
'`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()',
);
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id');
$this->forge->createTable('analytics_website_by_referer');
}

View File

@ -3,6 +3,7 @@
/**
* Class AddAnalyticsWebsiteByEntryPage
* Creates analytics_website_by_entry_page table in database
*
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
@ -36,12 +37,11 @@ class AddAnalyticsWebsiteByEntryPage extends Migration
]);
$this->forge->addPrimaryKey(['podcast_id', 'date', 'entry_page_url']);
$this->forge->addField(
'`created_at` timestamp NOT NULL DEFAULT current_timestamp()'
'`created_at` timestamp NOT NULL DEFAULT current_timestamp()',
);
$this->forge->addField(
'`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()'
'`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()',
);
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id');
$this->forge->createTable('analytics_website_by_entry_page');
}

View File

@ -3,6 +3,7 @@
/**
* Class AddAnalyticsUnknownUseragents
* Creates analytics_unknown_useragents table in database
*
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
@ -33,7 +34,8 @@ class AddAnalyticsUnknownUseragents extends Migration
'default' => 1,
],
]);
$this->forge->addKey('id', true);
$this->forge->addPrimaryKey('id');
// `created_at` and `updated_at` are created with SQL because Model class wont be used for insertion (Procedure will be used instead)
$this->forge->addField(
'`created_at` timestamp NOT NULL DEFAULT current_timestamp()'

View File

@ -3,6 +3,7 @@
/**
* Class AddAnalyticsPodcastsProcedure
* Creates analytics_podcasts procedure in database
*
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
@ -21,58 +22,58 @@ class AddAnalyticsPodcastsProcedure extends Migration
$prefix = $this->db->getPrefix();
$createQuery = <<<EOD
CREATE PROCEDURE `{$prefix}analytics_podcasts` (
IN `p_podcast_id` INT UNSIGNED,
IN `p_episode_id` INT UNSIGNED,
IN `p_country_code` VARCHAR(3) CHARSET utf8mb4,
IN `p_region_code` VARCHAR(3) CHARSET utf8mb4,
IN `p_latitude` FLOAT,
IN `p_longitude` FLOAT,
IN `p_service` VARCHAR(128) CHARSET utf8mb4,
IN `p_app` VARCHAR(128) CHARSET utf8mb4,
IN `p_device` VARCHAR(32) CHARSET utf8mb4,
IN `p_os` VARCHAR(32) CHARSET utf8mb4,
IN `p_bot` TINYINT(1) UNSIGNED,
IN `p_filesize` INT UNSIGNED,
IN `p_duration` INT UNSIGNED,
IN `p_age` INT UNSIGNED,
IN `p_new_listener` TINYINT(1) UNSIGNED
) MODIFIES SQL DATA
DETERMINISTIC
SQL SECURITY INVOKER
COMMENT 'Add one hit in podcast logs tables.'
BEGIN
CREATE PROCEDURE `{$prefix}analytics_podcasts` (
IN `p_podcast_id` INT UNSIGNED,
IN `p_episode_id` INT UNSIGNED,
IN `p_country_code` VARCHAR(3) CHARSET utf8mb4,
IN `p_region_code` VARCHAR(3) CHARSET utf8mb4,
IN `p_latitude` FLOAT,
IN `p_longitude` FLOAT,
IN `p_service` VARCHAR(128) CHARSET utf8mb4,
IN `p_app` VARCHAR(128) CHARSET utf8mb4,
IN `p_device` VARCHAR(32) CHARSET utf8mb4,
IN `p_os` VARCHAR(32) CHARSET utf8mb4,
IN `p_bot` TINYINT(1) UNSIGNED,
IN `p_filesize` INT UNSIGNED,
IN `p_duration` INT UNSIGNED,
IN `p_age` INT UNSIGNED,
IN `p_new_listener` TINYINT(1) UNSIGNED
) MODIFIES SQL DATA
DETERMINISTIC
SQL SECURITY INVOKER
COMMENT 'Add one hit in podcast logs tables.'
BEGIN
SET @current_datetime = NOW();
SET @current_date = DATE(@current_datetime);
SET @current_hour = HOUR(@current_datetime);
SET @current_datetime = NOW();
SET @current_date = DATE(@current_datetime);
SET @current_hour = HOUR(@current_datetime);
IF NOT `p_bot` THEN
INSERT INTO `{$prefix}analytics_podcasts`(`podcast_id`, `date`)
VALUES (p_podcast_id, @current_date)
ON DUPLICATE KEY UPDATE
`duration`=`duration`+`p_duration`,
`bandwidth`=`bandwidth`+`p_filesize`,
`hits`=`hits`+1,
`unique_listeners`=`unique_listeners`+`p_new_listener`;
INSERT INTO `{$prefix}analytics_podcasts_by_hour`(`podcast_id`, `date`, `hour`)
VALUES (p_podcast_id, @current_date, @current_hour)
ON DUPLICATE KEY UPDATE `hits`=`hits`+1;
INSERT INTO `{$prefix}analytics_podcasts_by_episode`(`podcast_id`, `episode_id`, `date`, `age`)
VALUES (p_podcast_id, p_episode_id, @current_date, p_age)
ON DUPLICATE KEY UPDATE `hits`=`hits`+1;
INSERT INTO `{$prefix}analytics_podcasts_by_country`(`podcast_id`, `country_code`, `date`)
VALUES (p_podcast_id, p_country_code, @current_date)
ON DUPLICATE KEY UPDATE `hits`=`hits`+1;
INSERT INTO `{$prefix}analytics_podcasts_by_region`(`podcast_id`, `country_code`, `region_code`, `latitude`, `longitude`, `date`)
VALUES (p_podcast_id, p_country_code, p_region_code, p_latitude, p_longitude, @current_date)
ON DUPLICATE KEY UPDATE `hits`=`hits`+1;
END IF;
INSERT INTO `{$prefix}analytics_podcasts_by_player`(`podcast_id`, `service`, `app`, `device`, `os`, `is_bot`, `date`)
VALUES (p_podcast_id, p_service, p_app, p_device, p_os, p_bot, @current_date)
ON DUPLICATE KEY UPDATE `hits`=`hits`+1;
END
EOD;
IF NOT `p_bot` THEN
INSERT INTO `{$prefix}analytics_podcasts`(`podcast_id`, `date`)
VALUES (p_podcast_id, @current_date)
ON DUPLICATE KEY UPDATE
`duration`=`duration`+`p_duration`,
`bandwidth`=`bandwidth`+`p_filesize`,
`hits`=`hits`+1,
`unique_listeners`=`unique_listeners`+`p_new_listener`;
INSERT INTO `{$prefix}analytics_podcasts_by_hour`(`podcast_id`, `date`, `hour`)
VALUES (p_podcast_id, @current_date, @current_hour)
ON DUPLICATE KEY UPDATE `hits`=`hits`+1;
INSERT INTO `{$prefix}analytics_podcasts_by_episode`(`podcast_id`, `episode_id`, `date`, `age`)
VALUES (p_podcast_id, p_episode_id, @current_date, p_age)
ON DUPLICATE KEY UPDATE `hits`=`hits`+1;
INSERT INTO `{$prefix}analytics_podcasts_by_country`(`podcast_id`, `country_code`, `date`)
VALUES (p_podcast_id, p_country_code, @current_date)
ON DUPLICATE KEY UPDATE `hits`=`hits`+1;
INSERT INTO `{$prefix}analytics_podcasts_by_region`(`podcast_id`, `country_code`, `region_code`, `latitude`, `longitude`, `date`)
VALUES (p_podcast_id, p_country_code, p_region_code, p_latitude, p_longitude, @current_date)
ON DUPLICATE KEY UPDATE `hits`=`hits`+1;
END IF;
INSERT INTO `{$prefix}analytics_podcasts_by_player`(`podcast_id`, `service`, `app`, `device`, `os`, `is_bot`, `date`)
VALUES (p_podcast_id, p_service, p_app, p_device, p_os, p_bot, @current_date)
ON DUPLICATE KEY UPDATE `hits`=`hits`+1;
END
EOD;
$this->db->query($createQuery);
}
@ -80,7 +81,7 @@ EOD;
{
$prefix = $this->db->getPrefix();
$this->db->query(
"DROP PROCEDURE IF EXISTS `{$prefix}analytics_podcasts`"
"DROP PROCEDURE IF EXISTS `{$prefix}analytics_podcasts`",
);
}
}

View File

@ -3,6 +3,7 @@
/**
* Class AddAnalyticsUnknownUseragentsProcedure
* Creates analytics_unknown_useragents procedure in database
*
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
@ -20,14 +21,14 @@ class AddAnalyticsUnknownUseragentsProcedure extends Migration
// Example: CALL analytics_unknown_useragents('Podcasts/1430.46 CFNetwork/1125.2 Darwin/19.4.0');
$procedureName = $this->db->prefixTable('analytics_unknown_useragents');
$createQuery = <<<EOD
CREATE PROCEDURE `$procedureName` (IN `p_useragent` VARCHAR(191) CHARSET utf8mb4) MODIFIES SQL DATA
DETERMINISTIC
SQL SECURITY INVOKER
COMMENT 'Add an unknown useragent to table $procedureName.'
INSERT INTO `$procedureName`(`useragent`)
VALUES (p_useragent)
ON DUPLICATE KEY UPDATE `hits`=`hits`+1
EOD;
CREATE PROCEDURE `$procedureName` (IN `p_useragent` VARCHAR(191) CHARSET utf8mb4) MODIFIES SQL DATA
DETERMINISTIC
SQL SECURITY INVOKER
COMMENT 'Add an unknown useragent to table $procedureName.'
INSERT INTO `$procedureName`(`useragent`)
VALUES (p_useragent)
ON DUPLICATE KEY UPDATE `hits`=`hits`+1
EOD;
$this->db->query($createQuery);
}

View File

@ -3,6 +3,7 @@
/**
* Class AddAnalyticsWebsiteProcedure
* Creates analytics_website stored procedure in database
*
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
@ -20,25 +21,25 @@ class AddAnalyticsWebsiteProcedure extends Migration
// Example: CALL analytics_website(1,'FR','Firefox');
$procedureName = $this->db->prefixTable('analytics_website');
$createQuery = <<<EOD
CREATE PROCEDURE `$procedureName` (IN `p_podcast_id` INT UNSIGNED, IN `p_browser` VARCHAR(191) CHARSET utf8mb4, IN `p_entry_page` VARCHAR(512) CHARSET utf8mb4, IN `p_referer_url` VARCHAR(512) CHARSET utf8mb4, IN `p_domain` VARCHAR(128) CHARSET utf8mb4, IN `p_keywords` VARCHAR(384) CHARSET utf8mb4) MODIFIES SQL DATA
DETERMINISTIC
SQL SECURITY INVOKER
COMMENT 'Add one hit in website logs tables.'
BEGIN
CREATE PROCEDURE `$procedureName` (IN `p_podcast_id` INT UNSIGNED, IN `p_browser` VARCHAR(191) CHARSET utf8mb4, IN `p_entry_page` VARCHAR(512) CHARSET utf8mb4, IN `p_referer_url` VARCHAR(512) CHARSET utf8mb4, IN `p_domain` VARCHAR(128) CHARSET utf8mb4, IN `p_keywords` VARCHAR(384) CHARSET utf8mb4) MODIFIES SQL DATA
DETERMINISTIC
SQL SECURITY INVOKER
COMMENT 'Add one hit in website logs tables.'
BEGIN
SET @current_date = DATE(NOW());
SET @current_date = DATE(NOW());
INSERT INTO {$procedureName}_by_browser(`podcast_id`, `browser`, `date`)
VALUES (p_podcast_id, p_browser, @current_date)
ON DUPLICATE KEY UPDATE `hits`=`hits`+1;
INSERT INTO {$procedureName}_by_referer(`podcast_id`, `referer_url`, `domain`, `keywords`, `date`)
VALUES (p_podcast_id, p_referer_url, p_domain, p_keywords, @current_date)
ON DUPLICATE KEY UPDATE `hits`=`hits`+1;
INSERT INTO {$procedureName}_by_entry_page(`podcast_id`, `entry_page_url`, `date`)
VALUES (p_podcast_id, p_entry_page, @current_date)
ON DUPLICATE KEY UPDATE `hits`=`hits`+1;
END
EOD;
INSERT INTO {$procedureName}_by_browser(`podcast_id`, `browser`, `date`)
VALUES (p_podcast_id, p_browser, @current_date)
ON DUPLICATE KEY UPDATE `hits`=`hits`+1;
INSERT INTO {$procedureName}_by_referer(`podcast_id`, `referer_url`, `domain`, `keywords`, `date`)
VALUES (p_podcast_id, p_referer_url, p_domain, p_keywords, @current_date)
ON DUPLICATE KEY UPDATE `hits`=`hits`+1;
INSERT INTO {$procedureName}_by_entry_page(`podcast_id`, `entry_page_url`, `date`)
VALUES (p_podcast_id, p_entry_page, @current_date)
ON DUPLICATE KEY UPDATE `hits`=`hits`+1;
END
EOD;
$this->db->query($createQuery);
}

View File

@ -1,8 +1,8 @@
<?php
/**
* Class AddLanguages
* Creates languages table in database
* Class AddPodcastUsers
* Creates podcast_users table in database
*
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
@ -32,9 +32,21 @@ class AddPodcastsUsers extends Migration
],
]);
$this->forge->addPrimaryKey(['user_id', 'podcast_id']);
$this->forge->addForeignKey('user_id', 'users', 'id');
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id');
$this->forge->addForeignKey('group_id', 'auth_groups', 'id');
$this->forge->addForeignKey('user_id', 'users', 'id', false, 'CASCADE');
$this->forge->addForeignKey(
'podcast_id',
'podcasts',
'id',
false,
'CASCADE',
);
$this->forge->addForeignKey(
'group_id',
'auth_groups',
'id',
false,
'CASCADE',
);
$this->forge->createTable('podcasts_users');
}

View File

@ -1,8 +1,8 @@
<?php
/**
* Class AddLanguages
* Creates languages table in database
* Class AddPages
* Creates pages table in database
*
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
@ -46,7 +46,7 @@ class AddPages extends Migration
'null' => true,
],
]);
$this->forge->addKey('id', true);
$this->forge->addPrimaryKey('id');
$this->forge->createTable('pages');
}

View File

@ -28,8 +28,20 @@ class AddPodcastsCategories extends Migration
],
]);
$this->forge->addPrimaryKey(['podcast_id', 'category_id']);
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id');
$this->forge->addForeignKey('category_id', 'categories', 'id');
$this->forge->addForeignKey(
'podcast_id',
'podcasts',
'id',
false,
'CASCADE',
);
$this->forge->addForeignKey(
'category_id',
'categories',
'id',
false,
'CASCADE',
);
$this->forge->createTable('podcasts_categories');
}

View File

@ -45,6 +45,12 @@ class AddPersons extends Migration
'type' => 'VARCHAR',
'constraint' => 255,
],
// constraint is 13 because the longest safe mimetype for images is image/svg+xml,
// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#image_types
'image_mimetype' => [
'type' => 'VARCHAR',
'constraint' => 13,
],
'created_by' => [
'type' => 'INT',
'unsigned' => true,

View File

@ -47,8 +47,20 @@ class AddPodcastsPersons extends Migration
'person_group',
'person_role',
]);
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id');
$this->forge->addForeignKey('person_id', 'persons', 'id');
$this->forge->addForeignKey(
'podcast_id',
'podcasts',
'id',
false,
'CASCADE',
);
$this->forge->addForeignKey(
'person_id',
'persons',
'id',
false,
'CASCADE',
);
$this->forge->createTable('podcasts_persons');
}

View File

@ -52,9 +52,27 @@ class AddEpisodesPersons extends Migration
'person_group',
'person_role',
]);
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id');
$this->forge->addForeignKey('episode_id', 'episodes', 'id');
$this->forge->addForeignKey('person_id', 'persons', 'id');
$this->forge->addForeignKey(
'podcast_id',
'podcasts',
'id',
false,
'CASCADE',
);
$this->forge->addForeignKey(
'episode_id',
'episodes',
'id',
false,
'CASCADE',
);
$this->forge->addForeignKey(
'person_id',
'persons',
'id',
false,
'CASCADE',
);
$this->forge->createTable('episodes_persons');
}

View File

@ -22,16 +22,16 @@ class AddCreditView extends Migration
$podcastPersonTable = $this->db->prefixTable('podcasts_persons');
$episodePersonTable = $this->db->prefixTable('episodes_persons');
$createQuery = <<<EOD
CREATE VIEW `$viewName` AS
SELECT `person_group`, `person_id`, `full_name`, `person_role`, `podcast_id`, NULL AS `episode_id` FROM `$podcastPersonTable`
INNER JOIN `$personTable`
ON (`person_id`=`$personTable`.`id`)
UNION
SELECT `person_group`, `person_id`, `full_name`, `person_role`, `podcast_id`, `episode_id` FROM `$episodePersonTable`
INNER JOIN `$personTable`
ON (`person_id`=`$personTable`.`id`)
ORDER BY `person_group`, `full_name`, `person_role`, `podcast_id`, `episode_id`;
EOD;
CREATE VIEW `$viewName` AS
SELECT `person_group`, `person_id`, `full_name`, `person_role`, `podcast_id`, NULL AS `episode_id` FROM `$podcastPersonTable`
INNER JOIN `$personTable`
ON (`person_id`=`$personTable`.`id`)
UNION
SELECT `person_group`, `person_id`, `full_name`, `person_role`, `podcast_id`, `episode_id` FROM `$episodePersonTable`
INNER JOIN `$personTable`
ON (`person_id`=`$personTable`.`id`)
ORDER BY `person_group`, `full_name`, `person_role`, `podcast_id`, `episode_id`;
EOD;
$this->db->query($createQuery);
}

View File

@ -0,0 +1,38 @@
<?php
/**
* Class AddEpisodeIdToNotes
* Adds episode_id field to activitypub_notes table in database
*
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class AddEpisodeIdToNotes extends Migration
{
public function up()
{
$prefix = $this->db->getPrefix();
$createQuery = <<<SQL
ALTER TABLE ${prefix}activitypub_notes
ADD COLUMN `episode_id` INT UNSIGNED NULL AFTER `replies_count`,
ADD FOREIGN KEY ${prefix}activitypub_notes_episode_id_foreign(episode_id) REFERENCES ${prefix}episodes(id) ON DELETE CASCADE;
SQL;
$this->db->query($createQuery);
}
public function down()
{
$this->forge->dropForeignKey(
'activitypub_notes',
'activitypub_notes_episode_id_foreign',
);
$this->forge->dropColumn('activitypub_notes', 'episode_id');
}
}

View File

@ -0,0 +1,38 @@
<?php
/**
* Class AddCreatedByToNotes
* Adds created_by field to activitypub_notes table in database
*
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class AddCreatedByToNotes extends Migration
{
public function up()
{
$prefix = $this->db->getPrefix();
$createQuery = <<<SQL
ALTER TABLE ${prefix}activitypub_notes
ADD COLUMN `created_by` INT UNSIGNED AFTER `episode_id`,
ADD FOREIGN KEY ${prefix}activitypub_notes_created_by_foreign(created_by) REFERENCES ${prefix}users(id) ON DELETE CASCADE;
SQL;
$this->db->query($createQuery);
}
public function down()
{
$this->forge->dropForeignKey(
'activitypub_notes',
'activitypub_notes_created_by_foreign',
);
$this->forge->dropColumn('activitypub_notes', 'created_by');
}
}

View File

@ -158,6 +158,18 @@ class AuthSeeder extends Seeder
'description' => 'Set / remove platform links of a podcast',
'has_permission' => ['podcast_admin'],
],
[
'name' => 'manage_publications',
'description' =>
'Publish / unpublish episodes & notes of a podcast',
'has_permission' => ['podcast_admin'],
],
[
'name' => 'interact_as',
'description' =>
'Interact as the podcast to favourite / share or reply to notes.',
'has_permission' => ['podcast_admin'],
],
],
'podcast_episodes' => [
[
@ -192,11 +204,6 @@ class AuthSeeder extends Seeder
'Delete all occurrences of an episode of a podcast from the database',
'has_permission' => ['podcast_admin'],
],
[
'name' => 'manage_publications',
'description' => 'Publish / unpublish episodes of a podcast',
'has_permission' => ['podcast_admin'],
],
],
'person' => [
[
@ -220,8 +227,23 @@ class AuthSeeder extends Seeder
'has_permission' => ['superadmin'],
],
[
'name' => 'delete_permanently',
'description' => 'Delete any person from the database',
'name' => 'delete',
'description' =>
'Delete permanently any person from the database',
'has_permission' => ['superadmin'],
],
],
'fediverse' => [
[
'name' => 'block_actors',
'description' =>
'Block an activitypub actors from interacting with the instance.',
'has_permission' => ['superadmin'],
],
[
'name' => 'block_domains',
'description' =>
'Block an activitypub domains from interacting with the instance.',
'has_permission' => ['superadmin'],
],
],
@ -266,7 +288,7 @@ class AuthSeeder extends Seeder
array_push($dataGroupsPermissions, [
'group_id' => $this->getGroupIdByName(
$role,
$dataGroups
$dataGroups,
),
'permission_id' => $permissionId,
]);

View File

@ -98,7 +98,6 @@ class PlatformSeeder extends Seeder
'home_url' => 'https://fyyd.de/',
'submit_url' => 'https://fyyd.de/add-feed',
],
[
'slug' => 'google',
'type' => 'podcasting',
@ -249,7 +248,6 @@ class PlatformSeeder extends Seeder
'submit_url' =>
'https://help.tunein.com/contact/add-podcast-S19TR3Sdf',
],
[
'slug' => 'paypal',
'type' => 'funding',
@ -257,7 +255,6 @@ class PlatformSeeder extends Seeder
'home_url' => 'https://www.paypal.com/',
'submit_url' => 'https://www.paypal.com/paypalme/my/grab',
],
[
'slug' => 'gofundme',
'type' => 'funding',
@ -322,7 +319,6 @@ class PlatformSeeder extends Seeder
'home_url' => 'https://www.ulule.com/',
'submit_url' => 'https://www.ulule.com/projects/create/#/',
],
[
'slug' => 'discord',
'type' => 'social',
@ -431,6 +427,7 @@ class PlatformSeeder extends Seeder
'submit_url' => 'https://creatoracademy.youtube.com/page/home',
],
];
$this->db
->table('platforms')
->ignore(true)

View File

@ -27,7 +27,7 @@ class Credit extends Entity
protected $podcast;
/**
* @var \App\Entities\Episode
* @var \App\Entities\Episode|null
*/
protected $episode;
@ -44,50 +44,61 @@ class Credit extends Entity
public function getPodcast()
{
return (new PodcastModel())->getPodcastById(
$this->attributes['podcast_id']
$this->attributes['podcast_id'],
);
}
public function getEpisode()
{
if (empty($this->attributes['episode_id'])) {
return null;
} else {
return (new EpisodeModel())->getEpisodeById(
$this->attributes['podcast_id'],
$this->attributes['episode_id']
if (empty($this->episode_id)) {
throw new \RuntimeException(
'Credit must have episode_id before getting episode.',
);
}
if (empty($this->episode)) {
$this->episode = (new EpisodeModel())->getPublishedEpisodeById(
$this->episode_id,
$this->podcast_id,
);
}
return $this->episode;
}
public function getPerson()
{
return (new PersonModel())->getPersonById(
$this->attributes['person_id']
);
if (empty($this->person_id)) {
throw new \RuntimeException(
'Credit must have person_id before getting person.',
);
}
if (empty($this->person)) {
$this->person = (new PersonModel())->getPersonById(
$this->person_id,
);
}
return $this->person;
}
public function getGroupLabel()
{
if (empty($this->attributes['person_group'])) {
if (empty($this->person_group)) {
return null;
} else {
return lang(
"PersonsTaxonomy.persons.{$this->attributes['person_group']}.label"
);
return lang("PersonsTaxonomy.persons.{$this->person_group}.label");
}
}
public function getRoleLabel()
{
if (
empty($this->attributes['person_group']) ||
empty($this->attributes['person_role'])
) {
if (empty($this->person_group) || empty($this->person_role)) {
return null;
} else {
return lang(
"PersonsTaxonomy.persons.{$this->attributes['person_group']}.roles.{$this->attributes['person_role']}.label"
"PersonsTaxonomy.persons.{$this->person_group}.roles.{$this->person_role}.label",
);
}
}

View File

@ -11,6 +11,7 @@ namespace App\Entities;
use App\Models\PodcastModel;
use App\Models\SoundbiteModel;
use App\Models\EpisodePersonModel;
use App\Models\NoteModel;
use CodeIgniter\Entity;
use CodeIgniter\I18n\Time;
use League\CommonMark\CommonMarkConverter;
@ -28,7 +29,7 @@ class Episode extends Entity
protected $link;
/**
* @var \App\Entities\Image
* @var \App\Libraries\Image
*/
protected $image;
@ -80,13 +81,18 @@ class Episode extends Entity
/**
* @var \App\Entities\EpisodePerson[]
*/
protected $episode_persons;
protected $persons;
/**
* @var \App\Entities\Soundbite[]
*/
protected $soundbites;
/**
* @var \App\Entities\Note[]
*/
protected $notes;
/**
* Holds text only description, striped of any markdown or html special characters
*
@ -122,6 +128,7 @@ class Episode extends Entity
protected $casts = [
'id' => 'integer',
'podcast_id' => 'integer',
'guid' => 'string',
'slug' => 'string',
'title' => 'string',
@ -133,6 +140,7 @@ class Episode extends Entity
'description_markdown' => 'string',
'description_html' => 'string',
'image_uri' => '?string',
'image_mimetype' => '?string',
'transcript_uri' => '?string',
'chapters_uri' => '?string',
'parental_advisory' => '?string',
@ -144,6 +152,9 @@ class Episode extends Entity
'location_geo' => '?string',
'location_osmid' => '?string',
'custom_rss' => '?json-array',
'favourites_total' => 'integer',
'reblogs_total' => 'integer',
'notes_total' => 'integer',
'created_by' => 'integer',
'updated_by' => 'integer',
];
@ -163,15 +174,16 @@ class Episode extends Entity
) {
helper('media');
// check whether the user has inputted an image and store it
$this->attributes['image_uri'] = save_podcast_media(
// check whether the user has inputted an image and store
$this->attributes['image_mimetype'] = $image->getMimeType();
$this->attributes['image_uri'] = save_media(
$image,
$this->getPodcast()->name,
$this->attributes['slug']
'podcasts/' . $this->getPodcast()->name,
$this->attributes['slug'],
);
$this->image = new \App\Entities\Image(
$this->attributes['image_uri']
$this->image = new \App\Libraries\Image(
$this->attributes['image_uri'],
$this->attributes['image_mimetype'],
);
$this->image->saveSizes();
}
@ -179,10 +191,13 @@ class Episode extends Entity
return $this;
}
public function getImage(): \App\Entities\Image
public function getImage(): \App\Libraries\Image
{
if ($image_uri = $this->attributes['image_uri']) {
return new \App\Entities\Image($image_uri);
return new \App\Libraries\Image(
$image_uri,
$this->attributes['image_mimetype'],
);
}
return $this->getPodcast()->image;
}
@ -204,13 +219,13 @@ class Episode extends Entity
$enclosure_metadata = get_file_tags($enclosure);
$this->attributes['enclosure_uri'] = save_podcast_media(
$this->attributes['enclosure_uri'] = save_media(
$enclosure,
$this->getPodcast()->name,
$this->attributes['slug']
'podcasts/' . $this->getPodcast()->name,
$this->attributes['slug'],
);
$this->attributes['enclosure_duration'] = round(
$enclosure_metadata['playtime_seconds']
$enclosure_metadata['playtime_seconds'],
);
$this->attributes['enclosure_mimetype'] =
$enclosure_metadata['mime_type'];
@ -238,10 +253,10 @@ class Episode extends Entity
) {
helper('media');
$this->attributes['transcript_uri'] = save_podcast_media(
$this->attributes['transcript_uri'] = save_media(
$transcript,
$this->getPodcast()->name,
$this->attributes['slug'] . '-transcript'
$this->attributes['slug'] . '-transcript',
);
}
@ -263,10 +278,10 @@ class Episode extends Entity
) {
helper('media');
$this->attributes['chapters_uri'] = save_podcast_media(
$this->attributes['chapters_uri'] = save_media(
$chapters,
$this->getPodcast()->name,
$this->attributes['slug'] . '-chapters'
$this->attributes['slug'] . '-chapters',
);
}
@ -343,15 +358,15 @@ class Episode extends Entity
$this->attributes[
'enclosure_duration'
]) *
60
60,
),
$this->attributes['enclosure_filesize'],
$this->attributes['enclosure_duration'],
strtotime($this->attributes['published_at'])
)
strtotime($this->attributes['published_at']),
),
),
$this->attributes['enclosure_uri']
)
$this->attributes['enclosure_uri'],
),
);
}
@ -384,22 +399,22 @@ class Episode extends Entity
*
* @return \App\Entities\EpisodePerson[]
*/
public function getEpisodePersons()
public function getPersons()
{
if (empty($this->id)) {
throw new \RuntimeException(
'Episode must be created before getting persons.'
'Episode must be created before getting persons.',
);
}
if (empty($this->episode_persons)) {
$this->episode_persons = (new EpisodePersonModel())->getPersonsByEpisodeId(
if (empty($this->persons)) {
$this->persons = (new EpisodePersonModel())->getPersonsByEpisodeId(
$this->podcast_id,
$this->id
$this->id,
);
}
return $this->episode_persons;
return $this->persons;
}
/**
@ -411,28 +426,43 @@ class Episode extends Entity
{
if (empty($this->id)) {
throw new \RuntimeException(
'Episode must be created before getting soundbites.'
'Episode must be created before getting soundbites.',
);
}
if (empty($this->soundbites)) {
$this->soundbites = (new SoundbiteModel())->getEpisodeSoundbites(
$this->getPodcast()->id,
$this->id
$this->id,
);
}
return $this->soundbites;
}
public function getNotes()
{
if (empty($this->id)) {
throw new \RuntimeException(
'Episode must be created before getting soundbites.',
);
}
if (empty($this->notes)) {
$this->notes = (new NoteModel())->getEpisodeNotes($this->id);
}
return $this->notes;
}
public function getLink()
{
return base_url(
route_to(
'episode',
$this->getPodcast()->name,
$this->attributes['slug']
)
$this->attributes['slug'],
),
);
}
@ -444,13 +474,13 @@ class Episode extends Entity
'embeddable-player-theme',
$this->getPodcast()->name,
$this->attributes['slug'],
$theme
$theme,
)
: route_to(
'embeddable-player',
$this->getPodcast()->name,
$this->attributes['slug']
)
$this->attributes['slug'],
),
);
}
@ -464,7 +494,7 @@ class Episode extends Entity
public function getPodcast()
{
return (new PodcastModel())->getPodcastById(
$this->attributes['podcast_id']
$this->attributes['podcast_id'],
);
}
@ -477,7 +507,7 @@ class Episode extends Entity
$this->attributes['description_markdown'] = $descriptionMarkdown;
$this->attributes['description_html'] = $converter->convertToHtml(
$descriptionMarkdown
$descriptionMarkdown,
);
return $this;
@ -510,25 +540,11 @@ class Episode extends Entity
preg_replace(
'/\s+/',
' ',
strip_tags($this->attributes['description_html'])
)
strip_tags($this->attributes['description_html']),
),
);
}
public function setCreatedBy(\App\Entities\User $user)
{
$this->attributes['created_by'] = $user->id;
return $this;
}
public function setUpdatedBy(\App\Entities\User $user)
{
$this->attributes['updated_by'] = $user->id;
return $this;
}
public function getPublicationStatus()
{
if ($this->publication_status) {
@ -588,7 +604,7 @@ class Episode extends Entity
return '';
} else {
$xmlNode = (new \App\Libraries\SimpleRSSElement(
'<?xml version="1.0" encoding="utf-8"?><rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:podcast="https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0"/>'
'<?xml version="1.0" encoding="utf-8"?><rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:podcast="https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0"/>',
))
->addChild('channel')
->addChild('item');
@ -596,7 +612,7 @@ class Episode extends Entity
[
'elements' => $this->custom_rss,
],
$xmlNode
$xmlNode,
);
return str_replace(['<item>', '</item>'], '', $xmlNode->asXML());
}
@ -615,12 +631,12 @@ class Episode extends Entity
simplexml_load_string(
'<?xml version="1.0" encoding="utf-8"?><rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:podcast="https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0"><channel><item>' .
$customRssString .
'</item></channel></rss>'
)
'</item></channel></rss>',
),
)['elements'][0]['elements'][0];
if (array_key_exists('elements', $customRssArray)) {
$this->attributes['custom_rss'] = json_encode(
$customRssArray['elements']
$customRssArray['elements'],
);
} else {
$this->attributes['custom_rss'] = null;

View File

@ -1,151 +0,0 @@
<?php
/**
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Entities;
use CodeIgniter\Entity;
class Image extends Entity
{
/**
* @var string
*/
protected $original_path;
/**
* @var string
*/
protected $original_url;
/**
* @var string
*/
protected $thumbnail_path;
/**
* @var string
*/
protected $thumbnail_url;
/**
* @var string
*/
protected $medium_path;
/**
* @var string
*/
protected $medium_url;
/**
* @var string
*/
protected $large_path;
/**
* @var string
*/
protected $large_url;
/**
* @var string
*/
protected $feed_path;
/**
* @var string
*/
protected $feed_url;
/**
* @var string
*/
protected $id3_path;
public function __construct($originalUri)
{
helper('media');
$originalPath = media_path($originalUri);
[
'filename' => $filename,
'dirname' => $dirname,
'extension' => $extension,
] = pathinfo($originalPath);
// load images extensions from config
$imageConfig = config('Images');
$thumbnailExtension = $imageConfig->thumbnailExtension;
$mediumExtension = $imageConfig->mediumExtension;
$largeExtension = $imageConfig->largeExtension;
$feedExtension = $imageConfig->feedExtension;
$id3Extension = $imageConfig->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;
parent::__construct([
'original_path' => $originalPath,
'original_url' => media_url($originalUri),
'thumbnail_path' => $thumbnail,
'thumbnail_url' => base_url($thumbnail),
'medium_path' => $medium,
'medium_url' => base_url($medium),
'large_path' => $large,
'large_url' => base_url($large),
'feed_path' => $feed,
'feed_url' => base_url($feed),
'id3_path' => $id3,
]);
}
public function saveSizes()
{
// load images sizes from config
$imageConfig = config('Images');
$thumbnailSize = $imageConfig->thumbnailSize;
$mediumSize = $imageConfig->mediumSize;
$largeSize = $imageConfig->largeSize;
$feedSize = $imageConfig->feedSize;
$id3Size = $imageConfig->id3Size;
$imageService = \Config\Services::image();
$imageService
->withFile($this->attributes['original_path'])
->resize($thumbnailSize, $thumbnailSize)
->save($this->attributes['thumbnail_path']);
$imageService
->withFile($this->attributes['original_path'])
->resize($mediumSize, $mediumSize)
->save($this->attributes['medium_path']);
$imageService
->withFile($this->attributes['original_path'])
->resize($largeSize, $largeSize)
->save($this->attributes['large_path']);
$imageService
->withFile($this->attributes['original_path'])
->resize($feedSize, $feedSize)
->save($this->attributes['feed_path']);
$imageService
->withFile($this->attributes['original_path'])
->resize($id3Size, $id3Size)
->save($this->attributes['id3_path']);
}
}

56
app/Entities/Note.php Normal file
View File

@ -0,0 +1,56 @@
<?php
/**
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Entities;
use App\Models\EpisodeModel;
class Note extends \ActivityPub\Entities\Note
{
/**
* @var \App\Entities\Episode|null
*/
protected $episode;
protected $casts = [
'id' => 'string',
'uri' => 'string',
'actor_id' => 'integer',
'in_reply_to_id' => '?string',
'reblog_of_id' => '?string',
'episode_id' => '?integer',
'message' => 'string',
'message_html' => 'string',
'favourites_count' => 'integer',
'reblogs_count' => 'integer',
'replies_count' => 'integer',
'created_by' => 'integer',
];
/**
* Returns the note's attached episode
*
* @return \App\Entities\Episode
*/
public function getEpisode()
{
if (empty($this->episode_id)) {
throw new \RuntimeException(
'Note must have an episode_id before getting episode.',
);
}
if (empty($this->episode)) {
$this->episode = (new EpisodeModel())->getEpisodeById(
$this->episode_id,
);
}
return $this->episode;
}
}

View File

@ -13,7 +13,7 @@ use CodeIgniter\Entity;
class Person extends Entity
{
/**
* @var \App\Entities\Image
* @var \App\Libraries\Image
*/
protected $image;
@ -23,12 +23,13 @@ class Person extends Entity
'unique_name' => 'string',
'information_url' => '?string',
'image_uri' => 'string',
'image_mimetype' => 'string',
'created_by' => 'integer',
'updated_by' => 'integer',
];
/**
* Saves a picture in `public/media/~person/`
* Saves a picture in `public/media/persons/`
*
* @param \CodeIgniter\HTTP\Files\UploadedFile|\CodeIgniter\Files\File $image
*
@ -38,13 +39,15 @@ class Person extends Entity
if ($image) {
helper('media');
$this->attributes['image_uri'] = save_podcast_media(
$this->attributes['image_mimetype'] = $image->getMimeType();
$this->attributes['image_uri'] = save_media(
$image,
'~person',
$this->attributes['unique_name']
'persons',
$this->attributes['unique_name'],
);
$this->image = new \App\Entities\Image(
$this->attributes['image_uri']
$this->image = new \App\Libraries\Image(
$this->attributes['image_uri'],
$this->attributes['image_mimetype'],
);
$this->image->saveSizes();
}
@ -54,6 +57,9 @@ class Person extends Entity
public function getImage()
{
return new \App\Entities\Image($this->attributes['image_uri']);
return new \App\Libraries\Image(
$this->attributes['image_uri'],
$this->attributes['image_mimetype'],
);
}
}

Some files were not shown because too many files have changed in this diff Show More