├── VERSION
├── thanks.md
├── .dockerignore
├── src
├── .gitignore
├── index.php
├── resources
│ ├── svg
│ │ ├── faridoon.png
│ │ └── faridoon.svg
│ ├── javascript
│ │ └── main.js
│ └── stylesheets
│ │ ├── app.css
│ │ └── theme.css
├── includes
│ ├── templates
│ │ ├── list.tpl
│ │ ├── quoteEdited.tpl
│ │ ├── quoteApproved.tpl
│ │ ├── register.tpl
│ │ ├── logout.tpl
│ │ ├── quoteAdded.tpl
│ │ ├── loggedin.tpl
│ │ ├── approveHeader.tpl
│ │ ├── quoteDeleted.tpl
│ │ ├── account.tpl
│ │ ├── form.tpl
│ │ ├── formElements.tpl
│ │ ├── quote.tpl
│ │ ├── users.tpl
│ │ └── header.tpl
│ ├── widgets
│ │ ├── quote.php
│ │ ├── header.php
│ │ └── footer.php
│ ├── classes
│ │ ├── FormUsergroupCreate.php
│ │ ├── FormAddUserToGroup.php
│ │ ├── FormUsergroupGrant.php
│ │ ├── Quote.php
│ │ └── FormQuote.php
│ ├── startup.php
│ ├── common.php
│ └── functionality.php
├── account.php
├── logout.php
├── user-edit.php
├── usergroup-create.php
├── usergroup-grant.php
├── delete.php
├── add.php
├── show.php
├── edit.php
├── login.php
├── approvals.php
├── register.php
├── list.php
├── users.php
└── vote.php
├── var
├── container-include-path.ini
├── screenshot.png
├── mockupLaptop.png
├── mockupLaptop.xcf
├── socialBanner.png
├── screenshot_edit.png
├── mockupMobilePhone.png
├── mockupMobilePhone.xcf
├── screenshot_approvals.png
├── svg-sources
│ ├── add.svg
│ ├── delete.svg
│ ├── edit.svg
│ └── approve.svg
└── faridoon-fedora.spec
├── .env.dev
├── logo.png
├── .gitignore
├── docs
├── faridoon.png
├── security
│ ├── moderators.png
│ └── index.md
├── installation
│ ├── index.md
│ ├── docker.md
│ ├── docker-compose.md
│ ├── docker-compose.yml
│ └── migrations.md
├── contact-support.md
├── index.md
└── configuration
│ └── index.md
├── config.dist.ini
├── database
├── dbconfig.yml
└── migrations
│ ├── 1.groups.sql
│ ├── 2.permissions.sql
│ └── 0.base.sql
├── phpstan.neon
├── .releaserc.yaml
├── SECURITY.md
├── phpcs.xml
├── .pre-commit-config.yaml
├── .github
├── ISSUE_TEMPLATE
│ ├── support_request.md
│ ├── bug_report.md
│ └── feature_request.md
├── workflows
│ ├── docs.yml
│ ├── composer-jobs.yml
│ └── release-pipeline.yml
└── PULL_REQUEST_TEMPLATE.md
├── composer.json
├── Dockerfile
├── mkdocs.yml
├── CONTRIBUTING.md
├── tests
└── TestHighlightUsernames.php
├── Makefile
├── README.md
├── logo.svg
├── CODE_OF_CONDUCT.md
└── LICENSE
/VERSION:
--------------------------------------------------------------------------------
1 | 2.0.0
2 |
--------------------------------------------------------------------------------
/thanks.md:
--------------------------------------------------------------------------------
1 | www.minimalmockups.com
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | src/includes/settings.php
2 |
--------------------------------------------------------------------------------
/src/.gitignore:
--------------------------------------------------------------------------------
1 | includes/settings.php
2 |
--------------------------------------------------------------------------------
/src/index.php:
--------------------------------------------------------------------------------
1 | more random quotes...
2 |
--------------------------------------------------------------------------------
/config.dist.ini:
--------------------------------------------------------------------------------
1 | DB_HOST=mysql
2 | DB_NAME=faridoon
3 | DB_USER=user
4 | DB_PASS=password
5 | ADMIN_PASSWORD=admin
6 | SITE_TITLE=My Quotes Page
7 |
--------------------------------------------------------------------------------
/src/includes/templates/quoteEdited.tpl:
--------------------------------------------------------------------------------
1 |
2 | Quote edited
3 | Your freshly updated quote is below.
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/includes/widgets/quote.php:
--------------------------------------------------------------------------------
1 | assign('quote', $quote);
4 | $tpl->assign('isVotingEnabled', $cfg->getBool('ENABLE_VOTING'));
5 | $tpl->display('quote.tpl');
6 |
--------------------------------------------------------------------------------
/database/dbconfig.yml:
--------------------------------------------------------------------------------
1 | development:
2 | dialect: mysql
3 | datasource: ${DB_USER}:${DB_PASS}@tcp(${DB_HOST})/${DB_NAME}?parseTime=true
4 | dir: migrations
5 | table: migrations
6 |
--------------------------------------------------------------------------------
/src/includes/templates/quoteApproved.tpl:
--------------------------------------------------------------------------------
1 |
2 | Quote Approved
3 |
4 | You probably just made somebody very happy.
5 |
6 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | level: 5
3 | paths:
4 | - src/
5 |
6 | ignoreErrors:
7 | - '#might not be defined#'
8 | - '#Path in require_once#'
9 | - '#Path in include_once#'
10 |
--------------------------------------------------------------------------------
/src/includes/templates/register.tpl:
--------------------------------------------------------------------------------
1 |
2 | Register as a new user
3 |
4 | Register as a new user to access the site.
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/includes/templates/logout.tpl:
--------------------------------------------------------------------------------
1 |
2 | Logged out
3 | You have been logged out. Hope to see you again soon, that would be really nice.
4 |
5 | Log back in again
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/account.php:
--------------------------------------------------------------------------------
1 | assign('isAdmin', isAdmin());
6 | $tpl->assign('isAddEnabled', isAddEnabled());
7 | $tpl->display('account.tpl');
8 |
9 | require_once 'includes/widgets/footer.php';
10 |
--------------------------------------------------------------------------------
/docs/installation/index.md:
--------------------------------------------------------------------------------
1 | # Installation options
2 |
3 | This section includes information on the various different installation options available for Faridoon. At the moment, it is strongly recommended to use the [Docker Compose](docker-compose.md) installation method.
4 |
--------------------------------------------------------------------------------
/src/includes/templates/quoteAdded.tpl:
--------------------------------------------------------------------------------
1 |
2 | Quote Added
3 | Oh goodie. Another quote!
4 |
5 | {if !isAdmin()}
6 | Your quote needs approval before it shows up in the list.
7 | {/if}
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/includes/templates/loggedin.tpl:
--------------------------------------------------------------------------------
1 |
2 | Logged In
3 |
4 | You have been logged in.
5 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/src/includes/templates/approveHeader.tpl:
--------------------------------------------------------------------------------
1 |
2 | {if $count == 0}
3 | Nothing to approve
4 | Perhaps you would like a crumpet instead?
5 | {else}
6 | Approval queue
7 | There are {$count} quote(s) to approve.
8 | {/if}
9 |
10 |
--------------------------------------------------------------------------------
/var/svg-sources/add.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/logout.php:
--------------------------------------------------------------------------------
1 | display('logout.tpl');
12 |
13 | require_once 'includes/widgets/footer.php';
14 |
--------------------------------------------------------------------------------
/src/includes/templates/quoteDeleted.tpl:
--------------------------------------------------------------------------------
1 |
2 | Quote Deleted
3 |
4 | Easy come, easy go.
5 |
6 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/database/migrations/1.groups.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 | INSERT INTO `groups` (id, title) VALUES (1, 'Admins');
3 | INSERT INTO `groups` (id, title) VALUES (2, 'Users');
4 | INSERT INTO permissions (id, `key`) VALUES (1, 'SUPERUSER');
5 | INSERT INTO privileges_g (`group`, `permission`) VALUES (1, 1);
6 |
7 | -- +migrate Down
8 |
--------------------------------------------------------------------------------
/.releaserc.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | branches:
3 | - main
4 | plugins:
5 | - '@semantic-release/commit-analyzer'
6 | - '@semantic-release/github'
7 | - '@semantic-release/git'
8 | - - "@semantic-release/exec"
9 | - publishCmd: |
10 | RELEASE_VERSION=${nextRelease.version} make release
11 |
12 | tagFormat: '${version}'
13 |
--------------------------------------------------------------------------------
/src/user-edit.php:
--------------------------------------------------------------------------------
1 | validate()) {
10 | $f->process();
11 |
12 | redirect('users.php');
13 | }
14 |
15 | require_once 'includes/widgets/header.php';
16 |
17 | $tpl->displayForm($f);
18 |
--------------------------------------------------------------------------------
/src/usergroup-create.php:
--------------------------------------------------------------------------------
1 | validate()) {
10 | $f->process();
11 |
12 | redirect('users.php');
13 | }
14 |
15 | require_once 'includes/widgets/header.php';
16 |
17 | $tpl->displayForm($f);
18 |
--------------------------------------------------------------------------------
/src/usergroup-grant.php:
--------------------------------------------------------------------------------
1 | validate()) {
10 | $f->process();
11 |
12 | redirect('users.php');
13 | }
14 |
15 | require_once 'includes/widgets/header.php';
16 |
17 | $tpl->displayForm($f);
18 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | | Version | Supported |
6 | | ------- | ------------------ |
7 | | latest | :white_check_mark: |
8 | | other | :x: |
9 |
10 | ## Reporting a Vulnerability
11 |
12 | Please report security issues via GitHub issues, or contacting jamesread via http://jread.com.
13 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | src/
4 | **/*.js
5 |
6 |
7 | src/*
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/database/migrations/2.permissions.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | UPDATE permissions SET `description` = "Has all permissions" WHERE `key` = "SUPERUSER";
4 | INSERT INTO permissions (`key`, `description`) VALUES ('BYPASS_APPROVAL', 'New quotes will be auto-approved');
5 | INSERT INTO permissions (`key`, `description`) VALUES ('APPROVE_QUOTES', 'Approve quotes in the approval queue');
6 |
7 | -- +migrate Down
8 |
--------------------------------------------------------------------------------
/docs/contact-support.md:
--------------------------------------------------------------------------------
1 | # Contact & Support
2 |
3 | Faridoon is an open source project and is maintained by [jamesread](https://jread.com). However, please use the [GitHub issue tracker](https://github.com/jamesread/Faridoon/issues) to report bugs and request features. You can also use it for support tickets.
4 |
5 | You can also get in touch via the [OliveTin Discord server](https://discord.gg/jhYWWpNJ3v).
6 |
--------------------------------------------------------------------------------
/src/delete.php:
--------------------------------------------------------------------------------
1 | prepare($sql);
11 | $stmt->bindValue(':id', filter('id'));
12 | $stmt->execute();
13 |
14 | $tpl->display('quoteDeleted.tpl');
15 |
16 | require_once 'includes/widgets/footer.php';
17 |
--------------------------------------------------------------------------------
/var/svg-sources/delete.svg:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | repos:
3 | - repo: https://github.com/pre-commit/pre-commit-hooks
4 | rev: v3.2.0
5 | hooks:
6 | - id: trailing-whitespace
7 | - id: end-of-file-fixer
8 | - id: check-yaml
9 | - id: check-added-large-files
10 |
11 | - repo: https://github.com/compilerla/conventional-pre-commit
12 | rev: v4.0.0
13 | hooks:
14 | - id: conventional-pre-commit
15 | stages: [commit-msg]
16 | args: []
17 |
--------------------------------------------------------------------------------
/src/add.php:
--------------------------------------------------------------------------------
1 | validate()) {
12 | $f->process();
13 |
14 | $tpl->display('quoteAdded.tpl');
15 |
16 | include_once 'includes/widgets/footer.php';
17 | }
18 |
19 | $tpl->displayForm($f);
20 |
21 | require_once 'includes/widgets/footer.php';
22 |
--------------------------------------------------------------------------------
/var/svg-sources/edit.svg:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/var/faridoon-fedora.spec:
--------------------------------------------------------------------------------
1 | Name: faridoon
2 | Version: 1.0.0
3 | Release: 1%{?dist}
4 | Summary: A really simple PHP based quotes system.
5 |
6 | Group: Web
7 | License: GPL
8 | URL: http://github.com/faridoon
9 | Source0: ../build/distributions/faridoon.zip
10 |
11 | BuildRequires: make
12 | Requires: php5
13 |
14 | %description
15 | A really simple PHP based quotes system.
16 |
17 | %prep
18 | %setup -q
19 |
20 |
21 | %build
22 | unzip faridoon.zip
23 |
24 | %install
25 |
26 |
27 | %files
28 | %doc
29 |
30 |
31 |
32 | %changelog
33 |
34 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # Faridoon
2 |
3 | Welcome! Faridoon is a web app to publish your favourite chat quotes. This is the documentation site that should tell you how to set it up and use it.
4 |
5 | You can learn more about Faridoon on it's GitHub site; [jamesread/Faridoon](https://github.com/jamesread/Faridoon).
6 |
7 | * [Docker Compose](installation/docker-compose.md) - this is the preferred way to run Faridoon.
8 |
9 | If you need help understanding something that is not documented, or just need other help with Faridoon, please check the [contact & support](contact-support.md) page.
10 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/support_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Support request
3 | about: Need some help? Got an error message?
4 | title: ""
5 | labels:
6 | - "type: support"
7 | - "waiting-on-developer"
8 | assignees: ''
9 | ---
10 |
11 | **What seems to be the problem?!**
12 |
13 | If you are getting an error message, then please copy/paste, or better, provide
14 | a screenshot to show us exactly what is wrong.
15 |
16 | **How did you install?**
17 |
18 | eg: Container image with compose, version 1.0.0
19 |
20 | **Anything else?**
21 |
22 | Add any other context about the problem here.
23 |
--------------------------------------------------------------------------------
/docs/configuration/index.md:
--------------------------------------------------------------------------------
1 | # Configuration
2 |
3 | ## Database Settings
4 |
5 | - `DB_HOST`: Database host
6 | - `DB_USER`: Database user
7 | - `DB_PASS`: Database password
8 | - `DB_NAME`: Database name
9 |
10 | ## Feature Flags
11 |
12 | - `ENABLE_VOTING`: Enable voting feature, set to "1" to enable voting. (default: 0).
13 | - `ENABLE_SYNTAX_HIGHLIGHTING`: Enable the ability to set a code style for syntax highlighting (admin only).
14 |
15 | ## Guest settings
16 |
17 | - `GUESTS_DISABLE_ADD`: Set to "true" to disable guests from adding new quotes (default: unset - guests can submit quotes).
18 |
--------------------------------------------------------------------------------
/docs/security/index.md:
--------------------------------------------------------------------------------
1 | # Security
2 |
3 | # Superusers
4 |
5 | The first registered user in Faridoon is granted superuser permissions. All registrations after that are given standard permissions (which can later be upgraded).
6 |
7 | ## Setting up a moderators usergroup
8 |
9 | You can create a usergroup for moderators, it should look something like this;
10 |
11 | 
12 |
13 | ## Guests
14 |
15 | Guests exist ourside of the users and permissions system. All guest permissions must be set through environment variables. See the [configuration](../configuration/index.md) section for more information.
16 |
--------------------------------------------------------------------------------
/docs/installation/docker.md:
--------------------------------------------------------------------------------
1 | # Install Faridoon with Docker
2 |
3 | The container image for Faridoon can be found on GitHub Container Registry, and can be pulled using the following commands:
4 |
5 | ```bash
6 | docker pull ghcr.io/jamesread/faridoon:latest
7 | ```
8 |
9 | Faridoon container images are build for the **amd64** and **arm64** architectures.
10 |
11 | The container can be run using the following command:
12 |
13 | ```bash
14 | docker run -it --name faridoon --port 8080:8080 -e DB_HOST=mysql -e DB_PASS=hunter2 -e DB_USER=faridoon ghcr.io/jamesread/faridoon:latest
15 | ```
16 |
17 | Consider using [Docker Compose](docker-compose.md) instead though, it's a lot easier.
18 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ""
5 | labels:
6 | - "type: bug"
7 | - "waiting-on-developer"
8 | assignees: ''
9 |
10 | ---
11 |
12 | **Describe the bug**
13 | A clear and concise description of what the bug is.
14 |
15 | **To Reproduce**
16 | Steps to reproduce the behavior:
17 | 1. Go to '...'
18 | 2. Click on '....'
19 | 3. Scroll down to '....'
20 | 4. See error
21 |
22 | **Expected behavior**
23 | A clear and concise description of what you expected to happen.
24 |
25 | **Screenshots**
26 | If applicable, add screenshots to help explain your problem.
27 |
28 | **Additional context**
29 | Add any other context about the problem here.
30 |
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: build docs
3 |
4 | on:
5 | push:
6 | branches: ["main"]
7 | paths:
8 | - "docs/**"
9 | - "mkdocs.yml"
10 |
11 | jobs:
12 | docs:
13 | runs-on: ubuntu-latest
14 | name: Build docs to pretty HTML!
15 | steps:
16 | - name: Checkout code
17 | uses: actions/checkout@v4
18 |
19 | - name: Deploy docs
20 | uses: mhausenblas/mkdocs-deploy-gh-pages@master
21 | env:
22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
23 | # CUSTOM_DOMAIN: optionaldomain.com
24 | CONFIG_FILE: mkdocs.yml
25 | # EXTRA_PACKAGES: build-base
26 | # GITHUB_DOMAIN: github.myenterprise.com
27 | # REQUIREMENTS: folder/requirements.txt
28 |
--------------------------------------------------------------------------------
/src/includes/templates/account.tpl:
--------------------------------------------------------------------------------
1 |
2 | {if $isLoggedIn}
3 | Welcome, {$username}!
4 | Hopefully you are having a marvelous day.
5 |
6 |
24 |
25 | {else}
26 | You are not logged in.
27 | Please log in to view your account.
28 | {/if}
29 |
30 |
--------------------------------------------------------------------------------
/src/includes/classes/FormUsergroupCreate.php:
--------------------------------------------------------------------------------
1 | addElement(new ElementInput('title', 'Title'));
16 |
17 | $this->addDefaultButtons('Create');
18 | }
19 |
20 | public function process()
21 | {
22 | $stmt = DatabaseFactory::getInstance()->prepare('INSERT INTO `groups` (title) VALUES (:title); ');
23 | $stmt->bindValue(':title', $this->getElementValue('title'));
24 | $stmt->execute();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jamesread/faridoon",
3 | "description": "Easily save and publish your favourite chat quotes for others to see.",
4 | "license": "AGPL-3.0-only",
5 | "type": "project",
6 | "require": {
7 | "jwread/lib-allure": "^8",
8 | "smarty/smarty": "^4.0"
9 | },
10 | "autoload": {
11 | "psr-4": {
12 | "faridoon\\": "src/includes/classes/"
13 | }
14 | },
15 | "authors": [
16 | {
17 | "name": "jamesread",
18 | "email": "contact@jread.com"
19 | }
20 | ],
21 | "require-dev": {
22 | "friendsofphp/php-cs-fixer": "^3.66",
23 | "squizlabs/php_codesniffer": "^3.11",
24 | "phpstan/phpstan": "^2.1",
25 | "phpunit/phpunit": "^11"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/includes/templates/form.tpl:
--------------------------------------------------------------------------------
1 | {assign var = "excludeBox" value = $excludeBox|default:false}
2 |
3 | {if $excludeBox eq true}
4 | {if !empty($form->getTitle)}
5 |
{$form->getTitle()}
6 | {/if}
7 | {else}
8 |
9 | {$form->getTitle()}
10 | {/if}
11 |
12 |
13 |
14 |
25 |
26 | {if not $excludeBox eq true}
27 |
28 | {/if}
29 |
30 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | description: Suggest an idea for something new
4 | title: 'Give your feature request a title'
5 | labels: ["type: feature-request", "waiting-on-developer"]
6 | labels:
7 | - "type: feature-request"
8 | - "waiting-on-developer"
9 | assignees: ''
10 | ---
11 |
12 | **Is your feature request related to a problem? Please describe.**
13 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
14 |
15 | **Describe the solution you'd like**
16 | A clear and concise description of what you want to happen.
17 |
18 | **Describe alternatives you've considered**
19 | A clear and concise description of any alternative solutions or features you've considered.
20 |
21 | **Additional context**
22 | Add any other context or screenshots about the feature request here.
23 |
--------------------------------------------------------------------------------
/docs/installation/docker-compose.md:
--------------------------------------------------------------------------------
1 | # Install Faridoon with Docker Compose
2 |
3 | Docker compose is the recommended way to run Faridoon. This page assumes that you have a working understanding of Docker and Docker Compose. If you are new to Docker, please refer to the [Docker documentation](https://docs.docker.com/get-started/).
4 |
5 | You can use the following `docker-compose.yml` file to run Faridoon with Docker Compose:
6 |
7 | ```yaml title="docker-compose.yml"
8 | --8<--
9 | docs/installation/docker-compose.yml
10 | --8<--
11 | ```
12 |
13 | Change your environment variables as necessary to set your passwords (`DB_PASS` should be the same as `MYSQL_PASSWORD`. `DB_USER` should be the same as `MYSQL_USER`, etc).
14 |
15 | Save this file as `docker-compose.yml` and run `docker-compose up -d` in the same directory. Faridoon will be available at `http://localhost:8080`.
16 |
--------------------------------------------------------------------------------
/var/svg-sources/approve.svg:
--------------------------------------------------------------------------------
1 |
9 |
10 |
--------------------------------------------------------------------------------
/src/includes/widgets/header.php:
--------------------------------------------------------------------------------
1 | assign('isLoggedIn', Session::isLoggedIn());
11 | $tpl->assign('countApprovals', 0);
12 | $tpl->assign('hasApprovalPermissions', false);
13 |
14 | if (Session::isLoggedIn()) {
15 | $tpl->assign('username', Session::getUser()->getUsername());
16 |
17 | if (Session::getUser()->hasPriv('APPROVE_QUOTES')) {
18 | $tpl->assign('hasApprovalPermissions', true);
19 | $tpl->assign('countApprovals', getCountApprovals());
20 | }
21 | }
22 |
23 | $tpl->assign('isVotingEnabled', $cfg->get('ENABLE_VOTING'));
24 | $tpl->assign('siteTitle', $cfg->get('SITE_TITLE'));
25 | $tpl->assign('inlineCss', getCustomCss());
26 | $tpl->assign('isRegistrationEnabled', !$cfg->getBool('DISABLE_REGISTRATION'));
27 | $tpl->assign('isAddEnabled', isAddEnabled());
28 | $tpl->display('header.tpl');
29 |
--------------------------------------------------------------------------------
/src/show.php:
--------------------------------------------------------------------------------
1 | prepare($sql);
10 | $stmt->bindValue(':id', $id);
11 | $stmt->execute();
12 |
13 | if ($stmt->numRows() == 0) {
14 | echo 'That quote does not exist.
';
15 | } else {
16 | $dbquote = $stmt->fetchRow();
17 |
18 | $quote = new Quote();
19 | $quote->unmarshalFromDatabase($dbquote);
20 |
21 | include_once 'includes/widgets/quote.php';
22 |
23 | echo 'More quotes... There are many more quotes , just in case this was not as exciting as you expected.
';
24 | }
25 |
26 | require_once 'includes/widgets/footer.php';
27 |
--------------------------------------------------------------------------------
/src/edit.php:
--------------------------------------------------------------------------------
1 | prepare($sql);
9 | $stmt->bindValue('itemId', filter('id'));
10 | $stmt->execute();
11 | $quote = $stmt->fetch();
12 |
13 | if (empty($quote)) {
14 | echo 'Oh dear, I cannot find that quote. Ah, for that matter, I dont think I can find my marbles!
';
15 |
16 | include_once 'includes/widgets/footer.php';
17 | } else {
18 | $f = new FormQuote($quote);
19 |
20 | if ($f->validate()) {
21 | $f->process();
22 |
23 | $tpl->assign('quoteId', $f->getElementValue('id'));
24 | $tpl->display('quoteEdited.tpl');
25 |
26 | require_once 'show.php';
27 |
28 | include_once 'includes/widgets/footer.php';
29 | }
30 |
31 | $tpl->displayForm($f);
32 | }
33 |
34 | require_once 'includes/widgets/footer.php';
35 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM docker.io/php:8.3-apache AS base
2 |
3 | RUN apt-get update && apt-get install sql-migrate unzip -y --no-install-recommends && rm -rf /var/lib/apt/lists/*
4 |
5 | COPY --from=docker.io/composer:2 /usr/bin/composer /usr/bin/composer
6 |
7 | RUN sed -i 's/Listen 80/Listen 8080/' /etc/apache2/ports.conf && a2enmod rewrite
8 |
9 | RUN docker-php-ext-configure pdo_mysql \
10 | && docker-php-ext-install pdo_mysql \
11 | && docker-php-ext-enable pdo_mysql
12 |
13 | EXPOSE 8080
14 |
15 | COPY database/ /var/faridoon/database/
16 | COPY src/ /var/faridoon/src/
17 | COPY composer.json /var/faridoon/
18 |
19 | WORKDIR /var/faridoon/
20 |
21 | RUN composer install --no-dev --no-suggest
22 | RUN rm -rf /var/www/html && ln -s /var/faridoon/src/ /var/www/html
23 |
24 | RUN sed -i '3i cd /var/faridoon/database/ && sql-migrate up' /usr/local/bin/docker-php-entrypoint
25 |
26 | #COPY config.dist.ini /config/config.ini
27 |
28 | VOLUME ["/config"]
29 |
30 | USER www-data
31 |
32 |
--------------------------------------------------------------------------------
/src/login.php:
--------------------------------------------------------------------------------
1 | display('loggedin.tpl');
11 |
12 | include_once 'includes/widgets/footer.php';
13 | die();
14 | }
15 |
16 | if ($f->validate()) {
17 | try {
18 | $f->process();
19 |
20 | include_once 'includes/widgets/header.php';
21 |
22 | $tpl->display('loggedin.tpl');
23 |
24 | include_once 'includes/widgets/footer.php';
25 | } catch (Exception $e) {
26 | include_once 'includes/widgets/header.php';
27 | var_dump($e);
28 | echo 'Wrong password. ';
29 | }
30 | } else {
31 | include_once 'includes/widgets/header.php';
32 |
33 | $tpl->displayForm($f);
34 |
35 | if (!$cfg->getBool('DISABLE_REGISTRATION')) {
36 | $tpl->display('register.tpl');
37 | }
38 | }
39 |
40 | require_once 'includes/widgets/footer.php';
41 |
--------------------------------------------------------------------------------
/src/approvals.php:
--------------------------------------------------------------------------------
1 | prepare($sql);
12 | $stmt->bindValue(':itemId', $approveId);
13 | $stmt->execute();
14 |
15 | $tpl->display('quoteApproved.tpl');
16 | }
17 |
18 | $sql = 'SELECT id, "?" as voteCount, content, approval as approved, date_format(created, "%Y-%m-%d") AS created FROM quotes WHERE approval = 0';
19 | $stmt = $db->prepare($sql);
20 | $stmt->execute();
21 | $quotes = $stmt->fetchAll();
22 |
23 | $tpl->assign('count', count($quotes));
24 | $tpl->display('approveHeader.tpl');
25 |
26 | foreach ($quotes as $dbquote) {
27 | $quote = new faridoon\Quote();
28 | $quote->unmarshalFromDatabase($dbquote);
29 |
30 | include 'includes/widgets/quote.php';
31 | }
32 |
33 | require_once 'includes/widgets/footer.php';
34 |
--------------------------------------------------------------------------------
/docs/installation/docker-compose.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: faridoon
3 |
4 | services:
5 | faridoon:
6 | container_name: faridoon
7 | image: ghcr.io/jamesread/faridoon
8 | volumes:
9 | - faridoon-config:/config
10 | ports:
11 | - "8080:8080"
12 | environment:
13 | DB_HOST: faridoon-mysql
14 | DB_NAME: faridoon
15 | DB_USER: faridoon
16 | DB_PASS: toomanysecrets
17 | restart: unless-stopped
18 | networks:
19 | - faridoon-network
20 | depends_on:
21 | faridoon-mysql:
22 | condition: service_healthy
23 |
24 | faridoon-mysql:
25 | container_name: faridoon-mysql
26 | image: mysql
27 | volumes:
28 | - faridoon-mysql:/var/lib/mysql
29 | environment:
30 | MYSQL_ROOT_PASSWORD: hunter2
31 | MYSQL_DATABASE: faridoon
32 | MYSQL_USER: faridoon
33 | MYSQL_PASSWORD: toomanysecrets
34 | restart: unless-stopped
35 | networks:
36 | - faridoon-network
37 | healthcheck:
38 | test: ["CMD-SHELL", "mysqladmin ping -h localhost"]
39 | interval: 20s
40 | timeout: 5s
41 | retries: 10
42 |
43 | volumes:
44 | faridoon-config:
45 | faridoon-mysql:
46 |
47 | networks:
48 | faridoon-network:
49 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # PR Introduction
2 |
3 |
14 |
15 | # Checklist
16 | Please put a X in the boxes as evidence of reading through the checklist.
17 |
18 | - [ ] I have forked the project, and raised this PR on a feature branch.
19 | - [ ] I have read the [CONTRIBUTING](CONTRIBUTING.md) guide and understand the 3-line change suggestion.
20 | - [ ] The default `make` lint tasks run without any issues.
21 | - [ ] I understand and accept the [AGPL-3.0 license](LICENSE) and [code of conduct](CODE_OF_CONDUCT.md), and my contributions fall under these.
22 |
--------------------------------------------------------------------------------
/docs/installation/migrations.md:
--------------------------------------------------------------------------------
1 | ## Database Migrations
2 |
3 | Faridoon automatically applies database upgrades (called "migrations" in database terminology) every time the container starts. If there are no changes to be made, the startup just continues.
4 |
5 | Therefore, it should not be necessary to run migrations manually. However, if you would like to do so, the instructions are below for how to do this.
6 |
7 | However, running migrations is easy. You will need to get a shell on the `faridoon` container. You can do this like this;
8 |
9 | ``` bash
10 | docker exec -it faridoon /bin/bash
11 | ```
12 |
13 | This will give you a command prompt like this;
14 |
15 | ```bash
16 | www-data@70fc8f2b445c:/var/faridoon$
17 | ```
18 |
19 | Change to the database directory, and you should be able to run `sql-migrate up` without any problems - as the database username, password, and database name are all set in the environment variables.
20 |
21 | ```bash
22 | www-data@70fc8f2b445c:/var/faridoon$ cd database
23 | www-data@70fc8f2b445c:/var/faridoon$ sql-migrate up
24 | ```
25 |
26 | This will run all the available migrations, and you should see output like this;
27 |
28 | ```bash
29 | www-data@70fc8f2b445c:/var/faridoon/database$ sql-migrate up
30 | Applied 1 migration
31 | ```
32 |
33 | If you see this, then the migrations have been applied successfully.
34 |
35 | You can exit the container by typing `exit` at the command prompt.
36 |
--------------------------------------------------------------------------------
/src/includes/startup.php:
--------------------------------------------------------------------------------
1 |
8 | Faridoon startup error
9 |
16 |
17 |
18 | Faridoon startup error
19 | $message
20 |
21 | HTML;
22 | echo $message;
23 |
24 | exit;
25 | }
26 |
27 | function requireDatabaseVersion(string $requiredMigration)
28 | {
29 | try {
30 | $sql = 'SELECT id FROM migrations';
31 | $stmt = libAllure\DatabaseFactory::getInstance()->query($sql);
32 | $versionRows = array_column($stmt->fetchAll(), 'id');
33 | } catch (Exception $e) {
34 | startupError('Faridoon connected to the database, but the migrations table could not be queried.');
35 | }
36 |
37 | natsort($versionRows);
38 | $latestVersion = end($versionRows);
39 |
40 | if ($latestVersion != $requiredMigration) {
41 | if ($latestVersion == '') {
42 | $latestVersion = 'null';
43 | }
44 |
45 | startupError('Faridoon requires database version ' . $requiredMigration . ' but the database is at version ' . $latestVersion . '. Please run database migrations .');
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/.github/workflows/composer-jobs.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: PHP CodeStyle
3 |
4 | on:
5 | push:
6 | branches: ["main"]
7 | paths:
8 | - "src/**"
9 | - "/*.xml"
10 | - "composer.json"
11 |
12 | pull_request:
13 | branches: ["main"]
14 | paths:
15 | - "src/**"
16 | - "/*.xml"
17 | - "composer.json"
18 |
19 |
20 | permissions:
21 | contents: read
22 |
23 | jobs:
24 | build:
25 | runs-on: ubuntu-latest
26 |
27 | steps:
28 | - uses: actions/checkout@v4
29 |
30 | - name: Setup PHP Action
31 | uses: shivammathur/setup-php@2.32.0
32 | with:
33 | php-version: '8.3'
34 |
35 | - name: Validate composer.json and composer.lock
36 | run: composer validate --strict
37 |
38 | - name: Cache Composer packages
39 | id: composer-cache
40 | uses: actions/cache@v4.2.3
41 | with:
42 | path: vendor
43 | key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
44 | restore-keys: |
45 | ${{ runner.os }}-php-
46 |
47 | - name: Install dependencies
48 | run: composer install --prefer-dist --no-progress
49 |
50 | - name: add bin to $PATH
51 | run: |
52 | echo "$GITHUB_WORKSPACE/src/private/libraries/bin/" >> $GITHUB_PATH
53 |
54 | - name: Run phpcs
55 | run: make phpcs
56 |
57 | - name: Run phpstan
58 | run: make phpstan
59 |
--------------------------------------------------------------------------------
/src/includes/common.php:
--------------------------------------------------------------------------------
1 | beGreedy();
10 |
11 | $cfg = new \libAllure\ConfigFile();
12 | $cfg->set(
13 | [
14 | 'DB_NAME' => 'faridoon',
15 | 'DB_HOST' => 'mysql',
16 | 'SITE_TITLE' => 'Faridoon',
17 | ]
18 | );
19 | $cfg->loadFromPaths(
20 | [
21 | '/config/',
22 | '/var/www/html/faridoon/',
23 | '/etc/faridoon/config.ini',
24 | ]
25 | );
26 | $cfg->loadFromEnv();
27 |
28 | use libAllure\Database;
29 | use libAllure\DatabaseFactory;
30 |
31 | $db = new Database($cfg->getDsn(), $cfg->get('DB_USER'), $cfg->get('DB_PASS'));
32 | DatabaseFactory::registerInstance($db);
33 |
34 | require_once 'includes/startup.php';
35 |
36 | requireDatabaseVersion('2.permissions.sql');
37 |
38 | require_once 'includes/functionality.php';
39 |
40 | \libAllure\Sanitizer::getInstance()->enableSearchingPrefixKeys();
41 |
42 | use libAllure\AuthBackend;
43 | use libAllure\AuthBackendDatabase;
44 |
45 | $backend = new AuthBackendDatabase($db);
46 | $backend->register();
47 |
48 | use libAllure\Session;
49 |
50 | Session::setSessionName('faridoon');
51 | Session::start();
52 |
53 | use libAllure\Template;
54 |
55 | $tpl = new Template(sys_get_temp_dir() . '/faridoon/' . 'includes/templates/');
56 | $tpl->registerModifier('isAdmin', 'isAdmin');
57 |
--------------------------------------------------------------------------------
/src/includes/classes/FormAddUserToGroup.php:
--------------------------------------------------------------------------------
1 | filterUint('uid');
19 |
20 | $this->addElementReadOnly('User', $uid, 'uid');
21 | $this->addElementUsergroup();
22 |
23 | $this->addDefaultButtons('Change group');
24 | }
25 |
26 | private function addElementUsergroup()
27 | {
28 | $stmt = DatabaseFactory::getInstance()->prepare('SELECT g.id, g.title FROM `groups` g');
29 | $stmt->execute();
30 |
31 | $el = new ElementSelect('gid', 'Group');
32 |
33 | foreach ($stmt->fetchAll() as $group) {
34 | $el->addOption($group['title'], $group['id']);
35 | }
36 |
37 | $this->addElement($el);
38 | }
39 |
40 | public function process()
41 | {
42 | $stmt = DatabaseFactory::getInstance()->prepare('UPDATE users u SET u.`group` = :gid WHERE u.id = :uid');
43 | $stmt->bindValue(':uid', $this->getElementValue('uid'));
44 | $stmt->bindValue(':gid', $this->getElementValue('gid'));
45 | $stmt->execute();
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/.github/workflows/release-pipeline.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: "Release Pipeline"
3 |
4 | on: [push, pull_request]
5 |
6 | jobs:
7 | snapshot:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Checkout
11 | uses: actions/checkout@v4
12 | with:
13 | fetch-depth: 0
14 |
15 | - name: Set up QEMU
16 | uses: docker/setup-qemu-action@v3
17 | with:
18 | image: tonistiigi/binfmt:latest
19 | platforms: arm64
20 |
21 | - name: Setup PHP Action
22 | uses: shivammathur/setup-php@2.33.0
23 | with:
24 | php-version: '8.3'
25 |
26 | - name: Login to ghcr
27 | uses: docker/login-action@v3.1.0
28 | with:
29 | registry: ghcr.io
30 | username: ${{ github.actor }}
31 | password: ${{ secrets.GITHUB_TOKEN }}
32 |
33 | - name: release
34 | if: github.ref_type != 'tag'
35 | uses: cycjimmy/semantic-release-action@v4
36 | with:
37 | extra_plugins: |
38 | @semantic-release/commit-analyzer
39 | @semantic-release/git
40 | @semantic-release/exec
41 | @semantic-release/github
42 | semantic_version: 24.2.3 # https://github.com/cycjimmy/semantic-release-action/issues/243
43 |
44 | env:
45 | GH_TOKEN: ${{ secrets.CONTAINER_TOKEN }}
46 | GITHUB_TOKEN: ${{ secrets.CONTAINER_TOKEN }}
47 | GITHUB_REF_NAME: ${{ github.ref_name }}
48 |
--------------------------------------------------------------------------------
/src/includes/templates/formElements.tpl:
--------------------------------------------------------------------------------
1 | {foreach from = $elements item = "element"}
2 | {if is_array($element)}
3 | {include file = "formElements.tpl" elements=$element}
4 | {else}
5 | {if $element->getType() eq 'ElementHidden'}
6 |
7 | {elseif $element->getType() eq 'ElementButton'}
8 | {$element->getCaption()}
9 | {else}
10 | {$element->getCaption()}
11 |
12 |
13 | {$element->render()}
14 |
15 |
16 |
17 |
{$element->description}
18 |
19 |
20 |
21 | {if !empty($suggestedValues)}
22 | {foreach from = $suggestedValues key = sv item = caption}
23 | {$caption} ';
24 | {/foreach}
25 | {/if}
26 |
27 |
28 |
29 |
{$element->getValidationError()}
30 |
31 | {/if}
32 | {/if}
33 | {/foreach}
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/register.php:
--------------------------------------------------------------------------------
1 | getBool('DISABLE_REGISTRATION')) {
6 | require_once 'includes/widgets/header.php';
7 |
8 | echo 'Registration is disabled. ';
9 |
10 | include_once 'includes/widgets/footer.php';
11 | exit;
12 | }
13 |
14 | use libAllure\util\FormRegister;
15 |
16 | $f = new FormRegister();
17 | $f->setTitle('Register as a new user');
18 | $f->getElement('submit')->setCaption('Register user');
19 |
20 | if ($f->validate()) {
21 | $f->process();
22 |
23 | $uid = $db->lastInsertId();
24 |
25 | $sql = 'SELECT * FROM users';
26 | $stmt = $db->prepare($sql);
27 | $stmt->execute();
28 |
29 |
30 | require_once 'includes/widgets/header.php';
31 |
32 | echo '';
33 | echo '
You have been registered. You can now login.
';
34 |
35 | $sql = 'UPDATE users SET `group` = :gid WHERE id = :uid LIMIT 1';
36 | $stmt = $db->prepare($sql);
37 | $stmt->bindValue('uid', $uid);
38 |
39 |
40 | if ($stmt->numRows() == 1) {
41 | $gid = 1;
42 |
43 | echo 'You have been promoted to admin as you are the first registered user.
';
44 | } else {
45 | $gid = 2;
46 | }
47 |
48 | $stmt->bindValue('gid', $gid);
49 | $stmt->execute();
50 |
51 | echo 'Login
';
52 | echo ' ';
53 | } else {
54 | include_once 'includes/widgets/header.php';
55 |
56 | $tpl->displayForm($f);
57 |
58 | include_once 'includes/widgets/footer.php';
59 | }
60 |
--------------------------------------------------------------------------------
/src/list.php:
--------------------------------------------------------------------------------
1 | prepare($sql);
32 | $stmt->execute();
33 | $quotes = $stmt->fetchAll();
34 |
35 | $foundRows = intval($db->prepare('SELECT found_rows() AS count')->executeRet()->fetchColumn());
36 | $numPages = ceil($foundRows / $limit);
37 |
38 | $navigable ? pagingLinks($start, $page, $numPages) : null;
39 |
40 | if (count($quotes) == 0) {
41 | echo 'This page intentionally left blank...? There are no quotes in the database... yet. Click "Add" in the navigation to be the first!
';
42 | } else {
43 | foreach ($quotes as $dbquote) {
44 | $quote = new Quote();
45 | $quote->unmarshalFromDatabase($dbquote);
46 |
47 | include 'includes/widgets/quote.php';
48 | }
49 | }
50 |
51 | $navigable ? pagingLinks($start, $page, $numPages) : null;
52 |
53 | require_once 'includes/widgets/footer.php';
54 |
--------------------------------------------------------------------------------
/src/includes/classes/FormUsergroupGrant.php:
--------------------------------------------------------------------------------
1 | prepare($sql);
19 | $stmt->bindValue(':group', Shortcuts::san()->filterUint('gid'));
20 | $stmt->execute();
21 |
22 | // var_dump(Shortcuts::san()->filterUint('gid')); exit;
23 | $group = $stmt->fetchRowNotNull();
24 |
25 | $this->addElementReadOnly('Usergroup', $group['id'], 'gid');
26 |
27 | $this->addElementPermission();
28 | $this->addDefaultButtons('Grant');
29 | }
30 |
31 | public function addElementPermission()
32 | {
33 | global $db;
34 |
35 | $el = new ElementSelect('permission', 'Permission');
36 |
37 | $sql = 'SELECT p.key, p.id FROM permissions p ORDER BY p.key ASC';
38 | $stmt = $db->prepare($sql);
39 | $stmt->execute();
40 |
41 | foreach ($stmt->fetchAll() as $perm) {
42 | $el->addOption($perm['key'], $perm['id']);
43 | }
44 |
45 | $this->addElement($el);
46 | }
47 |
48 | public function process()
49 | {
50 | global $db;
51 | $stmt = $db->prepare('INSERT INTO privileges_g (permission, `group`) values (:permission, :group) ');
52 | $stmt->bindValue(':permission', $this->getElementValue('permission'));
53 | $stmt->bindValue(':group', $this->getElementValue('gid'));
54 | $stmt->execute();
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | ---
2 | site_url: https://jamesread.github.io/Faridoon/
3 | site_name: Faridoon Docs
4 | site_description: Publish your favourite chat quotes.
5 | repo_url: https://github.com/jamesread/Faridoon
6 | repo_name: jamesread/Faridoon
7 | edit_uri: edit/main/docs
8 | strict: true
9 |
10 | markdown_extensions:
11 | - codehilite
12 | - pymdownx.highlight:
13 | anchor_linenums: true
14 | line_spans: __span
15 | pygments_lang_class: true
16 | - pymdownx.inlinehilite
17 | - pymdownx.snippets
18 | - pymdownx.superfences
19 | - toc:
20 | permalink: true
21 |
22 | theme:
23 | name: material
24 | logo: faridoon.png
25 | favicon: faridoon.png
26 | language: en
27 | include_search_page: true
28 | search_index_only: true
29 | palette:
30 | primary: deep purple
31 | features:
32 | - search.suggest
33 | - search.highlight
34 | - search.share
35 | - content.code.copy
36 | - content.action.edit
37 | icon:
38 | repo: fontawesome/brands/github
39 |
40 |
41 | plugins:
42 | - search:
43 | - social:
44 | - minify:
45 | minify_html: true
46 | - tags:
47 |
48 | extra:
49 | social:
50 | - icon: fontawesome/brands/github
51 | link: https://github.com/jamesread/faridoon
52 |
53 | - icon: fontawesome/brands/mastodon
54 | link: https://mastodon.social/@jamesread
55 |
56 | - icon: fontawesome/brands/x-twitter
57 | link: https://twitter.com/jamesreadtweets
58 |
59 | nav:
60 | - Welcome: index.md
61 | - Installation:
62 | - Introduction: installation/index.md
63 | - Docker Compose (recommended): installation/docker-compose.md
64 | - Docker standalone: installation/docker.md
65 | - Run database migrations: installation/migrations.md
66 | - Configuration: configuration/index.md
67 | - "Security": security/index.md
68 | - "Contact & Support": contact-support.md
69 |
--------------------------------------------------------------------------------
/src/includes/templates/quote.tpl:
--------------------------------------------------------------------------------
1 |
2 | {if $isVotingEnabled}
3 |
4 | ▲
5 | {$quote->voteCount}
6 | ▼
7 |
8 | {/if}
9 |
10 |
11 |
43 |
44 |
45 |
46 | {foreach $quote->lines as $line}
47 |
48 | {if isset($line.username)}
49 | {$line.username} :
50 | {/if}
51 | {$line.content}
52 |
53 | {/foreach}
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributions
2 |
3 | Contributions are very welcome - code, docs, whatever they might be! If this is
4 | your first contribution to an Open Source project or you're a core maintainer
5 | of multiple projects, your time and interest in contributing is most welcome.
6 |
7 | If you're not sure where to get started, raise an issue in the project.
8 |
9 | Ideas may be discussed, purely on their merits and issues. Our Code of Conduct
10 | (CoC) is straightforward - it's important that contributors feel comfortable in
11 | discussion throughout the whole process. This project has a
12 | [Code of Conduct](CODE_OF_CONDUCT.md).
13 |
14 | ## More than 3 lines - talk to someone first
15 |
16 | If you're planning on making a change that's more than a 3 lines, please talk to someone first. This is so that you don't waste your time on something that might not be accepted. It's also a good way to get some feedback on your idea and make sure you're on the right track.
17 |
18 | ## A PR should be one logical change
19 |
20 | Please try to keep your pull requests small and focused. It's almost impossible to review PRs that change lots of files for lots of different reasons. If you have a big change, it's probably best to break it down into smaller, more manageable chunks, otherwise it's likely to be rejected.
21 |
22 | ## If you're not sure, ask!
23 |
24 | Don't be afraid to ask for advice before working on a
25 | contribution. If you're thinking about a bigger change, especially that might
26 | affect the core working or architecture, it's almost essential to talk and ask
27 | about what you're planning might affect things. Some of the larger future plans may not be
28 | documented well so it's difficult to understand how your change might affect
29 | the general direction and roadmap of this project without asking.
30 |
31 | The preferred way to communicate is probably via Discord or GitHub issues.
32 |
33 | ## Mechanics of submitting a pull request
34 |
35 | When you are ready for a PR, please see the [pull request template](.github/PULL_REQUEST_TEMPLATE.md).
36 |
--------------------------------------------------------------------------------
/src/resources/javascript/main.js:
--------------------------------------------------------------------------------
1 | function toggleFullscreen () {
2 | if (document.fullscreenElement) {
3 | document.exitFullscreen()
4 | } else {
5 | document.documentElement.requestFullscreen()
6 | }
7 | }
8 |
9 | window.logoClicks = 0
10 |
11 | function clickLogo () {
12 | window.logoClicks++
13 | if (window.logoClicks >= 5) {
14 | document.getElementById('developer-links').hidden = false
15 |
16 | window.alert('You found the hidden developer links!')
17 |
18 | }
19 | }
20 |
21 | function voteUp (id) {
22 | return vote(id, 'up')
23 | }
24 |
25 | function voteDown (id) {
26 | return vote(id, 'down')
27 | }
28 |
29 | function vote (id, dir) {
30 | window.fetch('vote.php', {
31 | method: 'POST',
32 | headers: {
33 | 'Content-Type': 'application/json'
34 | },
35 | body: JSON.stringify({
36 | id: id,
37 | direction: dir
38 | })
39 | })
40 | .then(response => response.json())
41 | .then(onVoteReply)
42 | .catch(onError)
43 |
44 | return false // prevent default
45 | }
46 |
47 | function onError (res) {
48 | console.log('err', res)
49 |
50 | document.querySelectorAll('p.error').forEach(function (el) {
51 | el.remove()
52 | })
53 |
54 | if (typeof res.message !== 'undefined') {
55 | const p = document.createElement('p')
56 | p.classList.add('error')
57 | p.textContent = 'Error: ' + res.message
58 | p.addEventListener('click', function () {
59 | p.remove()
60 | })
61 |
62 | document.body.appendChild(p)
63 | }
64 | }
65 |
66 | function onVoteReply (json) {
67 | if (json.type === 'error') {
68 | if (json.cause === 'needsLogin') {
69 | window.location = 'login.php'
70 | } else {
71 | onError(json)
72 | }
73 | } else {
74 | const voteCount = document.getElementById('quote' + json.id).querySelector('.voteCount')
75 |
76 | if (json.newVal === 0) {
77 | voteCount.classList.add('novotes')
78 | } else {
79 | voteCount.classList.remove('novotes')
80 | }
81 |
82 | voteCount.innerText = json.newVal
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/tests/TestHighlightUsernames.php:
--------------------------------------------------------------------------------
1 | unmarshalFromText($text);
14 |
15 | $this->assertEquals(count($quote->lines), 1);
16 | $this->assertEquals($quote->lines[0]['username'], 'james');
17 | }
18 |
19 | public function testUsernamesInAngularBrackets()
20 | {
21 | $text = << hi
23 | EOQ;
24 |
25 | $quote = new faridoon\Quote();
26 | $quote->unmarshalFromText($text);
27 |
28 | $this->assertEquals(count($quote->lines), 1);
29 | $this->assertEquals($quote->lines[0]['username'], 'James');
30 | }
31 |
32 | public function testBashHunter2() {
33 | $text = << hey, if you type in your pw, it will show as stars
35 | ********* see!
36 | hunter2
37 | doesnt look like stars to me
38 | *******
39 | thats what I see
40 | oh, really?
41 | Absolutely
42 | you can go hunter2 my hunter2-ing hunter2
43 | haha, does that look funny to you?
44 | lol, yes. See, when YOU type hunter2, it shows to us
45 | as *******
46 | thats neat, I didnt know IRC did that
47 | yep, no matter how many times you type hunter2, it
48 | will show to us as *******
49 | awesome!
50 | wait, how do you know my pw?
51 | er, I just copy pasted YOUR ******'s and it appears
52 | to YOU as hunter2 cause its your pw
53 | oh, ok.
54 | EOQ;
55 |
56 | $quote = new faridoon\Quote();
57 | $quote->unmarshalFromText($text);
58 |
59 | $this->assertEquals(count($quote->lines), 20);
60 | $this->assertEquals($quote->lines[0]['username'], 'Cthon98');
61 | $this->assertEquals($quote->lines[2]['username'], 'AzureDiamond');
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/users.php:
--------------------------------------------------------------------------------
1 | prepare($sql);
13 | $stmt->bindParam(':id', $id);
14 | $stmt->execute();
15 |
16 | redirect('users.php');
17 | }
18 |
19 | if (isset($_GET['deleteGroup'])) {
20 | $id = intval($_GET['deleteGroup']);
21 |
22 | if ($id <= 2) {
23 | simpleFatalError('You cannot delete the default groups.');
24 | }
25 |
26 | $sql = "DELETE FROM `groups` WHERE id = :id LIMIT 1";
27 | $stmt = $db->prepare($sql);
28 | $stmt->bindParam(':id', $id);
29 | $stmt->execute();
30 |
31 | redirect('users.php');
32 | }
33 |
34 | if (isset($_GET['revokePermission'])) {
35 | $pid = intval($_GET['revokePermission']);
36 | $gid = intval($_GET['gid']);
37 |
38 | $sql = "DELETE FROM privileges_g WHERE `permission` = :pid AND `group` = :gid";
39 | $stmt = $db->prepare($sql);
40 | $stmt->bindParam(':pid', $pid);
41 | $stmt->bindParam(':gid', $gid);
42 | $stmt->execute();
43 |
44 | redirect('users.php');
45 | }
46 |
47 | require_once 'includes/widgets/header.php';
48 |
49 | $sql = "SELECT u.id, u.username, u.`group`, g.title AS groupTitle FROM users u LEFT JOIN `groups` g ON u.`group` = g.id";
50 | $stmt = $db->prepare($sql);
51 | $stmt->execute();
52 |
53 | $users = $stmt->fetchAll();
54 |
55 | $tpl->assign('currentUid', Session::getUser()->getId());
56 | $tpl->assign('users', $users);
57 |
58 | $sql = 'SELECT g.id, g.title FROM `groups` g';
59 | $stmt = $db->prepare($sql);
60 | $stmt->execute();
61 |
62 | $groups = array();
63 |
64 | foreach ($stmt->fetchAll() as $usergroup) {
65 | $sql = 'SELECT gp.permission AS pid, p.`key`, p.description FROM privileges_g gp LEFT JOIN permissions p ON gp.permission = p.id WHERE gp.group = :gid';
66 | $stmt = $db->prepare($sql);
67 | $stmt->bindParam(':gid', $usergroup['id']);
68 | $stmt->execute();
69 |
70 |
71 | $groups[$usergroup['id']] = array (
72 | 'id' => $usergroup['id'],
73 | 'title' => $usergroup['title'],
74 | 'permissions' => $stmt->fetchAll(),
75 | );
76 | }
77 |
78 | $tpl->assign('usergroups', $groups);
79 | $tpl->display('users.tpl');
80 |
81 | require_once 'includes/widgets/footer.php';
82 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | RELEASE_VERSION ?= development
2 |
3 | lint: phpcs phpcbf phpstan
4 |
5 | phpcs:
6 | vendor/bin/phpcs src/
7 |
8 | phpcbf:
9 | vendor/bin/phpcbf src/
10 |
11 | phpstan:
12 | vendor/bin/phpstan analyse src/
13 |
14 | phpunit:
15 | vendor/bin/phpunit tests/*
16 |
17 | clean:
18 | rm -rf build
19 |
20 | container-image:
21 | docker kill faridoon || true
22 | docker rm faridoon && docker rmi faridoon || true
23 | docker build -t faridoon:latest .
24 |
25 | container: container-image
26 | docker create --name faridoon -p 8080:8080 --env-file=.env.dev faridoon:latest
27 | docker start faridoon
28 |
29 | docker-amd64:
30 | docker buildx build --platform linux/amd64 -t ghcr.io/jamesread/faridoon:${RELEASE_VERSION}-amd64 -f Dockerfile --output type=docker --load .
31 | docker push ghcr.io/jamesread/faridoon:${RELEASE_VERSION}-amd64
32 |
33 | docker-arm64:
34 | docker buildx build --platform linux/arm64 -t ghcr.io/jamesread/faridoon:${RELEASE_VERSION}-arm64 -f Dockerfile --output type=docker --load .
35 | docker push ghcr.io/jamesread/faridoon:${RELEASE_VERSION}-arm64
36 |
37 | docker-manifest-latest:
38 | docker manifest create ghcr.io/jamesread/faridoon:latest \
39 | ghcr.io/jamesread/faridoon:${RELEASE_VERSION}-arm64 \
40 | ghcr.io/jamesread/faridoon:${RELEASE_VERSION}-amd64
41 | docker manifest annotate ghcr.io/jamesread/faridoon:latest \
42 | ghcr.io/jamesread/faridoon:${RELEASE_VERSION}-amd64 --os linux --arch amd64
43 | docker manifest annotate ghcr.io/jamesread/faridoon:latest \
44 | ghcr.io/jamesread/faridoon:${RELEASE_VERSION}-arm64 --os linux --arch arm64
45 | docker manifest push ghcr.io/jamesread/faridoon:latest
46 |
47 | docker-manifest-release-version:
48 | docker manifest create ghcr.io/jamesread/faridoon:${RELEASE_VERSION} \
49 | ghcr.io/jamesread/faridoon:${RELEASE_VERSION}-amd64 \
50 | ghcr.io/jamesread/faridoon:${RELEASE_VERSION}-arm64
51 | docker manifest annotate ghcr.io/jamesread/faridoon:${RELEASE_VERSION} \
52 | ghcr.io/jamesread/faridoon:${RELEASE_VERSION}-amd64 --os linux --arch amd64
53 | docker manifest annotate ghcr.io/jamesread/faridoon:${RELEASE_VERSION} \
54 | ghcr.io/jamesread/faridoon:${RELEASE_VERSION}-arm64 --os linux --arch arm64
55 | docker manifest push ghcr.io/jamesread/faridoon:${RELEASE_VERSION}
56 |
57 | docker-manifest: docker-manifest-latest docker-manifest-release-version
58 |
59 | release: docker-amd64 docker-arm64 docker-manifest
60 |
61 | .PHONY: dist clean docker-container-image container
62 |
--------------------------------------------------------------------------------
/src/includes/widgets/footer.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |