# Quaeriva

Quaeriva is an open-source PHP/MySQL question-and-answer application for multilingual communities. It is designed for shared hosting, ships Arabic and English base language packs, supports additional administrator-registered languages, and renders RTL/LTR from one codebase without creating duplicate language URLs.

**Status: release candidate.** Database-independent verification is green; MySQL-backed tests are required in CI and a clean install/upgrade still has to be rehearsed on the target shared host before a stable tag. The release gates are listed in [`docs/08-operations/RELEASE-CHECKLIST.md`](docs/08-operations/RELEASE-CHECKLIST.md).

- **Install:** [`docs/01-getting-started/INSTALLATION.md`](docs/01-getting-started/INSTALLATION.md)
- **Upgrade:** [`docs/01-getting-started/UPGRADE.md`](docs/01-getting-started/UPGRADE.md) · **Back up:** [`docs/01-getting-started/BACKUP-RESTORE.md`](docs/01-getting-started/BACKUP-RESTORE.md)
- **Themes:** [`docs/04-themes/THEMES-OVERVIEW.md`](docs/04-themes/THEMES-OVERVIEW.md) · **Advertising:** [`docs/02-administration/ADS-CUSTOM-CODE.md`](docs/02-administration/ADS-CUSTOM-CODE.md)

The release number has one source of truth: [`VERSION`](VERSION). Public pages, administration, installer, diagnostics, and API response headers read it through `app_version()`.


## One source tree, every environment

Quaeriva does not maintain a separate "local" codebase and "hosting" codebase. The Git repository is the single source of truth. Local development and production run the same PHP files; only machine-specific configuration changes (`config/database.local.php`, optional `config/site.local.php`, or `.env`), and those files are ignored by Git.

For contributors, clone the repository and keep `tests/`, `docs/`, and `.github/`. For shared-hosting releases, build `php bin/package.php`; it creates a smaller archive from the **same `VERSION` and commit** while excluding tests, documentation, local credentials, runtime data, and Git metadata. This is packaging, not a fork of the application.

## Runtime requirements

- PHP 8.1+
- PDO MySQL
- MySQL 8+ or compatible MariaDB
- `utf8mb4`
- PHP sessions and JSON
- `mbstring` recommended
- no Composer, Node.js, Redis, WebSocket, or build step required at runtime

Optional integrations are PHP `mail()` and Telegram Bot API notifications. The application remains usable when both are disabled.

## Installation

Full guide: **[docs/01-getting-started/INSTALLATION.md](docs/01-getting-started/INSTALLATION.md)**.

In short — no command line is required:

1. Create an empty MySQL/MariaDB database with `utf8mb4_unicode_ci`. Import nothing.
2. Upload the release archive. Point the document root at `public/` if your host allows it; otherwise upload everything into `public_html` and rely on the shipped root `.htaccess`.
3. Open `/install` in a browser and complete the form.
4. If you used the second layout, **verify the file protections** — `docs/01-getting-started/INSTALLATION.md` §5. In that layout `.htaccess` is a security control, not an optimisation.
5. Configure SMTP under Administration → Settings → Mail and send the test email. On shared hosting PHP `mail()` often fails silently, and a password reset that never arrives locks you out of your own site.

### Subdirectory deployment

Quaeriva assumes no installation folder. To serve from `example.com/community/`, set it once:

```env
APP_BASE_URL=/community
```

Every page, asset, API, canonical and redirect URL then uses that base path. Do not hard-code the folder anywhere else.

The installer also requires the trusted public origin (for example `https://example.com`) and writes it as `APP_URL`. Password-reset and verification links use that configured origin and never trust the incoming request `Host` header.

## Upgrading

Full guide: **[docs/01-getting-started/UPGRADE.md](docs/01-getting-started/UPGRADE.md)**. Back up first (**[docs/01-getting-started/BACKUP-RESTORE.md](docs/01-getting-started/BACKUP-RESTORE.md)**), replace the files, then use the **Upgrade database** action in the admin panel, or `php bin/migrate.php` if you have shell access.

Migrations are forward-only and never require re-importing a database. Never edit a migration that has already run.

## What it does

**Lightweight multilingual Q&A with lightweight discussions.** Questions, answers, threaded comments on answers,
voting with accepted answers, following, reporting, and duplicate-question
suggestions, plus small multilingual topics for announcements and community discussions that work across scripts rather than only in English.

**Multilingual by construction.** Arabic and English ship as base packs; an
administrator can register more from the panel. RTL and LTR render from one
stylesheet using logical CSS properties — there is no `rtl.css` flip and no
duplicate URL per language. Enable exactly one language and the switcher,
browser detection and per-question language choice disappear together.

**Editing.** A WYSIWYG editor that stores Markdown, so what is written is what
is published. The server is the only renderer, so nothing can drift between the
preview and the post.

**Administration.** Eighteen screens grouped into four collapsible sections:
content, community, appearance, system. Pagination and filters on every list.

**Appearance and themes.** Nine colours, fonts, sizes, logo and favicon from the
panel with no file editing. Nine page regions accept 20 structured widget types,
with drag ordering. Managed navigation menus. Optional theme directories can
override templates and declare the writing directions and minimum Core version
they support.

**Advertising.** Several advertisements per placement with weighted rotation,
scheduling, and targeting by device, language and audience — device targeting
decided on the server, so an excluded advertisement is never delivered.

**SEO.** Canonical URLs, per-question metadata and robots policy, Open Graph and
Twitter cards, QAPage/Question/Answer/Person/BreadcrumbList structured data, and
a sitemap generated live from the database that needs no scheduling.

See [`CHANGELOG.md`](CHANGELOG.md) for what changed in each release.

## What it deliberately is not

- **A deliberately small trusted plugin/hook system.** PHP plugins are installed
  over the filesystem, activated from Administration, and execute only when
  explicitly active. Disabled plugins and Safe Mode do not execute plugin PHP.
  There is no browser upload or marketplace.
- **No browser upload of themes.** A theme is PHP; FTP is already the boundary
  that decides who may run code on your server.
- **No build step, Composer, npm, Redis, queue or cron.** Upload files, fill in
  a form.
- **No ad impression or click tracking.** Your ad network counts more accurately
  than a PHP counter can.

## Local development

Keep machine-specific credentials in untracked `config/database.local.php`:

```powershell
Copy-Item config\database.local.example.php config\database.local.php
php bin/migrate.php
php bin/doctor.php
php -S 127.0.0.1:8003 -t public public/router.php
```

Open:

```text
http://127.0.0.1:8003/
http://127.0.0.1:8003/login
http://127.0.0.1:8003/admin?page=overview
```

To simulate a subdirectory in PowerShell:

```powershell
$env:APP_BASE_URL="/community"
php -S 127.0.0.1:8003 -t public public/router.php
```

Stop the server with `Ctrl+C`. PDO connections are request-scoped; no separate disconnect command is needed.

## Search and SEO

Public questions use one canonical route: `/q/{slug}`. Legacy `/questions/{slug}` requests permanently redirect to it. `/questions` does not exist as a duplicate indexable home page.

Quaeriva provides:

- canonical URLs;
- robots metadata;
- `robots.txt`;
- `sitemap.xml`;
- Open Graph and Twitter/X metadata;
- per-question and taxonomy SEO controls;
- conditional `QAPage` JSON-LD only when an eligible published question has visible answers.

Internal `/search` results are `noindex,follow` and provide filters rather than creating indexable URL duplicates.

## API v1

Read operations:

```text
GET /api/v1/questions
GET /api/v1/questions/{slug-or-id}
GET /api/v1/notifications
GET /api/v1/me
```

Question listing supports `category`, `tag`, `search`, `answered=yes|no`, `date=7d|30d|365d`, `member`, `locale`, `page`, and `per_page`.

Authenticated/scoped mutations include:

```text
POST /api/v1/questions
POST /api/v1/questions/{id}/answers
POST /api/v1/questions/{id}/comments
POST /api/v1/answers/{id}/comments
POST /api/v1/votes
POST /api/v1/questions/{id}/accept
POST /api/v1/follows
POST /api/v1/reports
```

Browser sessions may authorize same-origin API requests. External clients use Bearer tokens created from the member's API Tokens screen. Tokens are stored hashed and raw values are displayed once.

The `/api/v1/auth/*` endpoints are intentionally **not** part of the 0.7.0 API contract; they remain explicit reserved endpoints rather than pretending to provide authentication flows. Website login/registration/recovery remains the supported account-authentication surface.

Every JSON response uses the `{data, meta, error}` envelope and includes baseline security headers and `X-Quaeriva-Version`.

## Security model

Important protections include:

- PDO prepared statements;
- CSRF on browser mutations;
- server-side authorization;
- password hashing and one-time hashed account tokens;
- hashed API tokens with scopes;
- rate limits for login, registration, recovery, question, answer, comment, vote, duplicate suggestions, contact, and API writes;
- output escaping and HTML sanitization where HTML widgets are permitted;
- maintenance and administration isolation;
- noindex administration pages;
- X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy, and HSTS when served over HTTPS;
- audit/moderation history for sensitive administrative changes.

See [`SECURITY.md`](SECURITY.md).

## Tests and CI

Run dependency-free suites individually from `tests/`, or all of them through GitHub Actions. MySQL-backed tests require PDO MySQL and a disposable test database.

Useful local commands:

```text
php bin/migrate.php
php bin/doctor.php
php bin/admin-smoke.php
php bin/admin-smoke.php --write
```

`--write` runs administrative write checks inside a transaction and rolls back the smoke-test data.

`.github/workflows/ci.yml` runs PHP lint, all test files, migrations, and write smoke tests against MySQL 8 on the configured PHP matrix. Require a green CI run before publishing a GitHub release.

## Project documentation

The documentation hub is **[`docs/README.md`](docs/README.md)**. It indexes every guide below.

**Running a site**

- [`docs/01-getting-started/INSTALLATION.md`](docs/01-getting-started/INSTALLATION.md) — requirements, the two hosting layouts, the installer, verifying file protections
- [`docs/01-getting-started/FIRST-RUN.md`](docs/01-getting-started/FIRST-RUN.md) — what to configure immediately after installing
- [`docs/01-getting-started/UPGRADE.md`](docs/01-getting-started/UPGRADE.md) — replacing files and bringing the database forward
- [`docs/01-getting-started/BACKUP-RESTORE.md`](docs/01-getting-started/BACKUP-RESTORE.md) — what to back up, how to restore, and what is not in the database
- [`docs/02-administration/ADMIN-OVERVIEW.md`](docs/02-administration/ADMIN-OVERVIEW.md) — the admin panel, page by page
- [`docs/03-configuration/SITE-SETTINGS.md`](docs/03-configuration/SITE-SETTINGS.md) — site identity, locales, content policy
- [`docs/03-configuration/SMTP-EMAIL.md`](docs/03-configuration/SMTP-EMAIL.md) · [`docs/03-configuration/TELEGRAM.md`](docs/03-configuration/TELEGRAM.md) — delivery channels
- [`docs/03-configuration/LANGUAGES-RTL.md`](docs/03-configuration/LANGUAGES-RTL.md) — adding languages and translation overrides
- [`docs/03-configuration/SEO.md`](docs/03-configuration/SEO.md) — canonicals, structured data, sitemaps, robots
- [`docs/04-themes/THEMES-OVERVIEW.md`](docs/04-themes/THEMES-OVERVIEW.md) — appearance tokens, layout presets, theme profiles
- [`docs/02-administration/ADS-CUSTOM-CODE.md`](docs/02-administration/ADS-CUSTOM-CODE.md) — placements, rotation and weights, targeting

**Operating**

- [`docs/08-operations/SHARED-HOSTING.md`](docs/08-operations/SHARED-HOSTING.md) · [`docs/08-operations/CPANEL-DEPLOYMENT.md`](docs/08-operations/CPANEL-DEPLOYMENT.md) · [`docs/08-operations/SUBDIRECTORY-INSTALL.md`](docs/08-operations/SUBDIRECTORY-INSTALL.md)
- [`docs/08-operations/DIAGNOSTICS.md`](docs/08-operations/DIAGNOSTICS.md) · [`docs/08-operations/LOGGING.md`](docs/08-operations/LOGGING.md) · [`docs/08-operations/TROUBLESHOOTING.md`](docs/08-operations/TROUBLESHOOTING.md)
- [`docs/09-recipes/`](docs/09-recipes/) — SMTP, Telegram, domain moves, recovering admin access

**Extending**

- [`docs/05-extensions/PLUGINS.md`](docs/05-extensions/PLUGINS.md) · [`docs/05-extensions/HOOKS-REFERENCE.md`](docs/05-extensions/HOOKS-REFERENCE.md)
- [`docs/04-themes/BUILDING-A-THEME.md`](docs/04-themes/BUILDING-A-THEME.md) · [`docs/04-themes/TEMPLATE-OVERRIDES.md`](docs/04-themes/TEMPLATE-OVERRIDES.md)
- [`docs/06-api/API-V1.md`](docs/06-api/API-V1.md) · [`docs/06-api/AUTH-TOKENS.md`](docs/06-api/AUTH-TOKENS.md) · [`docs/06-api/ERROR-CODES.md`](docs/06-api/ERROR-CODES.md)
- [`docs/07-internals/`](docs/07-internals/) — architecture, request lifecycle, routing, SEO and sitemap pipelines
- [`docs/reference/`](docs/reference/) — settings keys, database tables, routes, permissions, hooks, theme manifest

**Developing**

- `AGENTS.md` — the engineering contract; read before changing anything (source repository only; not shipped in release archives)
- [`CONTRIBUTING.md`](CONTRIBUTING.md)
- [`SECURITY.md`](SECURITY.md) — the security model and how to report a vulnerability
- [`RELEASE-CHECKLIST.md`](RELEASE-CHECKLIST.md) · [`docs/08-operations/RELEASE-CHECKLIST.md`](docs/08-operations/RELEASE-CHECKLIST.md)

## License

See [`LICENSE`](LICENSE).
