# BLUDIT PRO 3.22.0 — FLASH-OPTIMIZED REFERENCE (PATCHED)
`Build 20260510 "BrownBear"`

---

## 1. ID

```
BLUDIT_VERSION        = '3.22.0'
BLUDIT_CODENAME       = 'BrownBear'
BLUDIT_BUILD          = '20260510'
BLUDIT_RELEASE_DATE   = '2026-05-10'
BLUDIT_PRO            = true (if pro file exists)
BLUDIT_PRO_HASH       = substr(md5(BLUDIT_BUILD),0,8) ("5d2fcbb4")
```

**Pro file load** (`init.php`):
```php
define('BLUDIT_PRO_HASH', substr(md5(BLUDIT_BUILD),0,8));
$_bluditProFile = PATH_KERNEL.'bludit.pro.'.BLUDIT_PRO_HASH.'.php';
if(file_exists($_bluditProFile)) include($_bluditProFile);
```

---

## 2. BOOT SEQUENCE

### site
```
index.php → init.php:
  constants: VERSION, codename, build, debug=0
  ini: display_errors=0, html_errors=0, log_errors=1
  PATH_* constants
  boot/variables.php (user env)
  mb_internal_encoding(CHARSET), mb_http_output(CHARSET)
  load abstract: dbjson, dblist, plugin
  load core: pages, users, tags, language, site, categories, syslog, pagex, category, tag, user, url, login, parsedown, security
  functions.php
  helpers: text, log, date, theme, session, redirect, sanitize, valid, email, filesystem, alert, paginator, image, tcp, dom, cookie
  BLUDIT_PRO_HASH → include if exists
  new $pages, $users, $tags, $categories, $site, $url, $security, $syslog
  HTML_PATH_ROOT, DOMAIN_* constants
  $language = new Language($site->language())
  $url->checkFilters($site->uriFilters())
  TAG_URI_FILTER, CATEGORY_URI_FILTER, PAGE_URI_FILTER
  ORDER_BY, EXTREME_FRIENDLY_URL, AUTOSAVE_INTERVAL, IMAGE_RESTRICT, IMAGE_RELATIVE_TO_ABSOLUTE, MARKDOWN_PARSER
  THEME_DIR_*, DOMAIN_* final
  $ID_EXECUTION = uniqid()
  $WHERE_AM_I = $url->whereAmI()
  $L = $language

→ site.php:
  60.plugins.php              (buildPlugins → $plugins, $pluginsInstalled)
  Theme::plugins('beforeAll')
  60.router.php               (trailing-slash redirects)
  69.pages.php
    scheduler() → auto-publish (patched: returns array of keys, each key passed to afterPageCreate)
    preview token = hash_hmac('sha256', $page->uuid(), $site->previewSalt()) (patched)
    buildStaticPages() → $staticContent
    whereAmI='home' + homepage → buildThePage() w/ homepage slug
    whereAmI='page' → buildThePage()
    whereAmI='tag' → buildPagesByTag()
    whereAmI='category' → buildPagesByCategory()
    whereAmI='home'|'blog' → buildPagesForHome()
    $page = $content[0] if set
    $url->notFound() → $page = buildErrorPage()
  99.header.php               (HTTP status, X-Powered-By)
  99.paginator.php            (Paginator::$pager, Theme::plugins('paginator'))
  99.themes.php               (theme language, $themePlugin = getPlugin($site->theme()))
  Theme::plugins('beforeSiteLoad')
  theme init.php (if exists)
  theme index.php              (THEME RENDER)
  Theme::plugins('afterSiteLoad')
  Theme::plugins('afterAll')
```

### admin
```
admin.php:
  Session::start()
  $login = new Login()
  $layout array built from slug
  60.plugins.php
  plugin admin controller detection (case-insensitive)
  if slug==='ajax' → 99.security.php → load ajax file
  else:
    69.pages.php
    99.header.php
    99.paginator.php
    99.themes.php
    99.security.php
    notFound or not logged → login
    Theme::plugins('beforeAdminLoad')
    admin theme init.php (if exists)
    controller loaded
    admin theme index.php or login.php
    Theme::plugins('afterAdminLoad')
```

---

## 3. HOOKS

### Site
| Hook | Fires in |
|------|----------|
| `beforeAll` | site.php before router |
| `afterAll` | site.php after theme |
| `beforeSiteLoad` | site.php before theme init |
| `afterSiteLoad` | site.php after theme render |
| `siteHead` | theme `<head>` |
| `siteBodyBegin` | theme `<body>` |
| `siteBodyEnd` | theme `</body>` |
| `siteSidebar` | theme sidebar |
| `pageBegin` | before page content |
| `pageEnd` | after page content |
| `paginator` | 99.paginator.php |

### Admin
| Hook | Fires in |
|------|----------|
| `beforeAdminLoad` | admin.php before theme |
| `afterAdminLoad` | admin.php after theme |
| `adminHead` | admin `<head>` |
| `adminBodyBegin` | admin `<body>` |
| `adminBodyEnd` | admin `</body>` |
| `adminSidebar` | admin sidebar |
| `adminContentSidebar` | content sidebar |
| `dashboard` | dashboard area |
| `editorToolbar` | content editor toolbar |

### Content lifecycle
| Hook | When | Args |
|------|------|------|
| `afterPageCreate` | page created | `array($key)` |
| `afterPageModify` | page edited | `array($key)` |
| `afterPageDelete` | page deleted | `array($key)` |

### Login
| Hook | Fires in |
|------|----------|
| `loginHead` | login `<head>` |
| `loginBodyBegin` | login body start |
| `loginBodyEnd` | login body end |

---

## 4. PLUGIN SYSTEM

### Plugin abstract class (`bl-kernel/abstract/plugin.class.php`)

**Constructor order**
1. dbFields = []
2. customHooks = []
3. directoryName = ReflectionClass
4. className = ReflectionClass
5. formButtons = true
6. init() → define dbFields, customHooks
7. db = dbFields (defaults)
8. filenameDb, filenameMetadata set
9. if installed: load db from file → prepare()

**Properties**
| Property | Type |
|----------|------|
| `dbFields` | array |
| `db` | array |
| `formButtons` | bool |
| `customHooks` | array |
| `directoryName` | string |
| `className` | string |
| `metadata` | array |

**Methods – signatures & returns**

```
init()
prepare()
form() -> string
post()
save()
install($position)
uninstall()
installed() -> bool
getValue($field, $html=true) -> mixed|null             (patched: isset() check, returns null if key missing)
setField($field, $value)
setPosition($position)
getMetadata($key) -> mixed
setMetadata($key, $value)
includeCSS($filename) -> string
includeJS($filename) -> string
domainPath() -> string
htmlPath() -> string
phpPath() -> string
phpPathDB() -> string                                    (path to plugin database dir)
workspace() -> string
webhook($URI, $afterUri, $fixed) -> bool
isCompatible() -> bool
name() -> string
description() -> string
label() -> string
author() -> string
email() -> string
website() -> string
position() -> int
version() -> string
releaseDate() -> string
className() -> string
formButtons() -> bool
directoryName() -> string
type() -> string
```

### buildPlugins() (`60.plugins.php`)
1. scan PATH_PLUGINS for dirs
2. include plugin.php
3. instantiate plugin class
4. load language file (name/description)
5. merge extra translations into $L
6. $plugins['all'][pluginClass] = $Plugin
7. if installed(): register custom hooks, $pluginsInstalled[pluginClass], bind events
8. sort $plugins['siteSidebar'] by position

---

## 5. THEME SYSTEM

### Theme file structure
```
bl-themes/<theme>/
  index.php          (required)
  metadata.json      (required)
  languages/en.json  (required)
  init.php           (optional)
  install.php        (optional)
```

### 99.themes.php
```php
$themePlugin = getPlugin($site->theme())   // theme companion plugin
```

### Theme static methods (theme.class.php)

```
title() -> string
description() -> string
slogan() -> string
footer() -> string
lang() -> string
siteUrl() -> string
adminUrl() -> string
rssUrl() -> string|false
sitemapUrl() -> string|false
metaTags('title') -> string
metaTags('description') -> string
metaTagTitle() -> string                   (patched: tag/category not found → fallback to homepage title format)
metaTagDescription() -> string
charset($c) -> string
viewport($c) -> string
keywords($k) -> string
favicon($f,$t) -> string
css($files,$base) -> string
js($files,$base,$attrs) -> string
javascript(...) -> string
src($file,$base) -> string
jquery() -> string
jsBootstrap($attrs) -> string
cssBootstrap() -> string
cssBootstrapIcons() -> string
cssLineAwesome() -> string
jsSortable($attrs) -> string
socialNetworks() -> array
plugins($hook,$args) -> void (echo)
headTitle() -> string                     DEPRECATED → metaTagTitle()
headDescription() -> string               DEPRECATED → metaTagDescription()
```

---

## 6. ROUTING

### URI filters (site.class.php::uriFilters())
```php
$filters['admin']    = '/'.ADMIN_URI_FILTER.'/';     // '/admin/'
$filters['page']     = $this->getField('uriPage');    // '/'
$filters['tag']      = $this->getField('uriTag');     // '/tag/'
$filters['category'] = $this->getField('uriCategory');// '/category/'
if($this->getField('uriBlog')){
  $filters['blog']   = $this->getField('uriBlog');    // '/blog/' (if homepage set)
}
```

### URL matching (Url::checkFilters())
1. Admin filter first
2. Others sorted by URI length desc
3. Match URI against HTML_PATH_ROOT + filter
4. Extract slug, set whereAmI
5. Empty slug + '/' → 'home'; admin → slug trimmed
6. No match → setNotFound()

### Router rules (60.router.php)
- `/admin` → redirect `/admin/`
- `/blog` → redirect `/blog/` (if homepage set)
- `/my-page/` → redirect `/my-page` (pages only)

### WHERE_AM_I values
`home` `page` `tag` `category` `blog` `admin` `search`

---

## 7. CONTENT OBJECTS

### Page (pagex.class.php) – 50 methods

```
title() -> string
description() -> string
content($sanitize=false) -> string
contentRaw($sanitize=false) -> string
contentBreak($sanitize=false) -> string
readMore() -> bool
readingTime() -> string                        (patched: uses content(false) for word count)
permalink($absolute=true) -> string
key() -> string
slug() -> string
date($format=false) -> string
dateRaw() -> string (Y-m-d H:i:s)
dateModified($format=false) -> string
relativeTime($complete=false) -> string
username() -> string
user($method=false) -> User|mixed
type() -> string (published|static|draft|sticky|scheduled|autosave)
published() -> bool
sticky() -> bool
isStatic() -> bool
draft() -> bool
scheduled() -> bool
autosave() -> bool
tags($array=false) -> string|array
category() -> string
categoryKey() -> string
categoryDescription() -> string
categoryTemplate() -> string
categoryPermalink() -> string
categoryMap($field) -> mixed
template() -> string
coverImage($absolute=true) -> string|false
thumbCoverImage() -> string|false
uuid() -> string
allowComments() -> bool
position() -> int
noindex() -> bool
nofollow() -> bool
noarchive() -> bool
isParent() -> bool
isChild() -> bool
parent() -> string|false
parentKey() -> string|false
parentMethod($method) -> mixed
hasChildren() -> bool
childrenKeys() -> array
children() -> Page[]
previousKey() -> string|false
nextKey() -> string|false
related() -> array
custom($field,$options=false) -> mixed
json($array=false) -> string|array
getValue($field) -> mixed
getDB() -> array
```

### Category

```
key() -> string
name() -> string
permalink() -> string
template() -> string
description() -> string
pages() -> array (keys)
json($returnsArray=false) -> string|array
```

### Tag

```
key() -> string
name() -> string
permalink() -> string
pages() -> array (keys)
json($returnsArray=false) -> string|array
```

---

## 8. SITE OBJECT (site.class.php)

```
title() -> string
slogan() -> string
description() -> string
footer() -> string
theme() -> string
adminTheme() -> string
homepage() -> string|false
pageNotFound() -> string
language() -> string
languageShortVersion() -> string
locale() -> string
timezone() -> string
dateFormat() -> string
timeFormat() -> string
itemsPerPage() -> int
orderBy() -> string
url() -> string
domain() -> string
urlPath() -> string
isHTTPS() -> bool
logo($absolute=true) -> string|false
emailFrom() -> string
extremeFriendly() -> bool
autosaveInterval() -> int
markdownParser() -> bool
imageRestrict() -> bool
imageRelativeToAbsolute() -> bool
thumbnailEnable() -> bool
thumbnailWidth() -> int
thumbnailHeight() -> int
thumbnailQuality() -> int
titleFormatPages() -> string
titleFormatHomepage() -> string
titleFormatCategory() -> string
titleFormatTag() -> string
uriFilters($filter='') -> array|string
customFields() -> array
defaultContentStatus() -> string
get() -> array
twitter() ... bluesky() -> string
xing() -> string
previewSalt() -> string                    (patched: random secret, auto-generated if empty; used for preview HMAC)
rss() -> string                            DEPRECATED
sitemap() -> string                        DEPRECATED
```

---

## 9. API ENDPOINTS (pluginAPI)

| Method | Endpoint | Auth |
|--------|----------|------|
| GET    | `/api/pages` | no |
| GET    | `/api/pages/{key}` | no |
| POST   | `/api/pages` | yes |
| PUT    | `/api/pages/{key}` | yes |
| DELETE | `/api/pages/{key}` | yes |
| GET    | `/api/settings` | yes |
| PUT    | `/api/settings` | yes |
| POST   | `/api/images` | yes **patched: MIME verified via Filesystem::mimeType()** |
| GET    | `/api/tags` | no |
| GET    | `/api/tags/{key}` | no |
| GET    | `/api/categories` | no |
| GET    | `/api/categories/{key}` | no |
| GET    | `/api/users` | no |
| GET    | `/api/users/{username}` | no |
| GET    | `/api/files/{pageKey}` | no |
| POST   | `/api/files/{pageKey}` | yes |

---

## 10. AJAX ENDPOINTS (`bl-kernel/ajax/`)

All require admin login + CSRF.

| File | Purpose |
|------|---------|
| `change-type.php` | Change page type |
| `clippy.php` | Quick search **patched: role-based filtering (authors see own pages)** |
| `content-get-list.php` | Search by title |
| `delete-image.php` | Delete image + thumbnail (legacy thumb recovery) |
| `generate-slug.php` | Generate slug |
| `get-published.php` | Published pages (select2) |
| `list-images.php` | Media manager pagination (original-centric, paired thumb) |
| `logo-remove.php` | Remove logo |
| `logo-upload.php` | Upload logo |
| `profile-picture-upload.php` | Upload profile pic |
| `save-as-draft.php` | Autosave/preview |
| `upload-images.php` | Upload image(s) – MIME check + sanitize |

---

## 11. EVENT/HOOK DISPATCH

```php
Theme::plugins($type, $args=[]) {
  global $plugins;
  foreach($plugins[$type] as $plugin) {
    echo call_user_func_array([$plugin,$type], $args);
  }
}
```
Output echoed directly – no buffer.

---

## 12. CONFIG DB FIELDS

**site.class.php $dbFields additions:**
- `'xing' => ''`
- `'previewSalt' => ''` (patched, auto-generated if empty on first page load)

**users.class.php $dbFields additions:**
- `'xing' => ''`

---

## 13. AUTH & SESSION

### Session (session.class.php)
- `session_set_cookie_params([...])` array syntax
- `httponly: true`, `samesite: 'Lax'`
- `__Secure-` prefix when HTTPS
- `Session::$sessionName = 'BLUDIT-KEY'`

### Cookie (cookie.class.php)
```
Cookie::get($key) -> mixed
Cookie::set($key,$value,$days,$options=[])
Cookie::remove($key)
Cookie::isEmpty($key) -> bool
```

### Login (login.class.php)
- `setLogin()`: `session_regenerate_id(true)` → prevent session fixation
- `isLogged()`: checks user exists + not disabled (password '!')

### Security (security.class.php)
```
getUserIp() -> string   (only REMOTE_ADDR, no proxy headers)
```

---

## 14. GLOBAL VARIABLES

`$content` (Page[])  `$page` (Page|false)  `$staticContent` (Page[])  `$pages` (Pages)  `$site` (Site)  `$url` (Url)  `$tags` (Tags)  `$categories` (Categories)  `$L` (Language)  `$WHERE_AM_I` (string)  `$plugins` (array)  `$syslog` (Syslog)  `$login` (Login)  `$security` (Security)  `$themePlugin` (Plugin|false)

---

## 15. GLOBAL FUNCTIONS (functions.php)

### Existing
`reindexCategories()`, `reindexTags()`, `buildErrorPage()`, `buildThePage()` (patched: preview token uses $site->previewSalt()), `buildPagesForHome()`, `buildPagesByCategory()`, `buildPagesByTag()`, `buildPagesFor()`, `buildStaticPages()`, `buildPage()`, `buildParentPages()`, `getPlugin()`, `pluginActivated()`, `createPage()`, `editPage()`, `deletePage()`, `editUser()`, `disableUser()`, `deleteUser()`, `createUser()`, `editSettings()`, `getCategories()`, `getCategory()`, `getTags()`, `getTag()`, `ajaxResponse()`

### New in 3.22.0
`activatePlugin($className)` — install & activate  
`deactivatePlugin($className)` — uninstall  
`deactivateAllPlugin()` — uninstall all  
`changePluginsPosition($classList)` — reorder  
`createCategory($args)`  
`editCategory($args)`  
`deleteCategory($args)`  
`changeUserPassword($args)`  
`checkRole($allowRoles,$redirect)`  
`activateTheme($themeDir)`  
`transformImage($file,$imageDir,$thumbnailDir)`  
`mediaManagerListImages($imagePath,$thumbnailPath,$chunk)`  
`downloadRestrictedFile($file)`

---

## 16. PAGES CLASS

```
Pages::countByType($published,$static,$sticky,$draft,$scheduled) -> int
```
`Pages::scheduler()` (patched: returns array of published keys)

---

## 17. IMAGE CLASS

- WebP support in `openImage()` / `saveImage()`
- Decoders guard with `imagetypes()` checks to avoid fatal errors

---

## 18. FILESYSTEM CLASS

```
Filesystem::mv($old,$new)        (rename first, copy+delete fallback)
Filesystem::mimeType($file)       (finfo_file fallback)
Filesystem::symlink($from,$to)    (symlink or copy fallback)
Filesystem::zip()
Filesystem::unzip()
```

---

## 19. USER CLASS

```
User::xing() -> string
User::profilePicture() -> string   (sanitizes username: removeSpecialCharacters, removeQuotes, removeSpaces)
```

---

## 20. URL CLASS

```
uri() -> string
slug() -> string
whereAmI() -> string
notFound() -> bool
pageNumber() -> int
activeFilter() -> string
filters($type,$trim=true) -> string
parameter($field) -> mixed
explodeSlug($delimiter) -> array
httpCode() -> int
httpMessage() -> string
setWhereAmI($where)
setSlug($slug)
setHttpCode($code)
setHttpMessage($msg)
setNotFound()
checkFilters($filters) -> bool
```

---

## 21. PAGINATOR CLASS (static)

```
Paginator::numberOfPages() -> int
Paginator::currentPage() -> int
Paginator::firstPage() -> int
Paginator::nextPage() -> int
Paginator::prevPage() -> int
Paginator::showNext() -> bool
Paginator::showPrev() -> bool
Paginator::firstPageUrl() -> string
Paginator::lastPageUrl() -> string
Paginator::nextPageUrl() -> string
Paginator::previousPageUrl() -> string
Paginator::numberUrl($n) -> string
Paginator::html($prev,$next,$showNum) -> string
Paginator::bootstrap_html($prev,$next,$showNum) -> string
Paginator::get($key) -> mixed
Paginator::set($key,$value)
```

---

## 22. KEY CONSTANTS

`BLUDIT_PRO_HASH` (8-char hex)  
`PROFILE_IMG_WIDTH=400` `PROFILE_IMG_HEIGHT=400` `PROFILE_IMG_QUALITY=100`  
`MEDIA_MANAGER_NUMBER_OF_FILES=5`  
`MEDIA_MANAGER_SORT_BY_DATE=true`  
`NOTIFICATIONS_AMOUNT=10`  
`ALLOWED_IMG_EXTENSION = [gif,png,jpg,jpeg,svg,webp]`  
`ALLOWED_IMG_MIMETYPES = [image/gif, image/png, image/jpeg, image/svg+xml, image/webp]`  
`ALLOWED_FILE_EXTENSIONS = [gif,png,jpg,jpeg,webp,pdf,txt,doc,docx,xls,xlsx,ppt,pptx,zip,tar,gz,mp3,mp4,wav,ogg,json,md]`

---

## 23. BUILT-IN PLUGINS (3.22.0)

| Plugin | Class | Version | Type | Patched? |
|--------|-------|---------|------|----------|
| API | pluginAPI | 3.22.0 | plugin | **image MIME verify** |
| Alternative | alternative | 3.22.0 | theme | |
| Backup Manager | pluginBackupManager | 3.22.0 | plugin (Pro) | **abort restore if emergency backup fails** |
| Canonical | pluginCanonical | 3.22.0 | plugin | |
| Categories | pluginCategories | 3.22.0 | plugin | |
| Comments | pluginComments | 1.0.0 | plugin | |
| Custom Fields Parser | pluginCustomFieldsParser | 3.22.0 | plugin | |
| Domain Migrator | (Pro) | 3.22.0 | plugin (Pro) | |
| EasyMDE | plugineasyMDE | 2.18.0 | plugin | |
| Gallery | pluginGallery | 3.22.0 | plugin (Pro) | |
| Hit Counter | pluginHitCounter | 3.22.0 | plugin | |
| HTML Code | pluginHTMLCode | 3.22.0 | plugin | |
| Links | pluginLinks | 3.22.0 | plugin | |
| Maintenance Mode | pluginMaintenanceMode | 3.22.0 | plugin | |
| Navigation | pluginNavigation | 3.22.0 | plugin | |
| Popeye | popeye | 3.22.0 | theme | |
| Remote Content | pluginRemoteContent | 3.22.0 | plugin | |
| Robots | pluginRobots | 3.22.0 | plugin | |
| RSS | pluginRSS | 3.22.0 | plugin | |
| Search | pluginSearch | 3.22.0 | plugin | |
| Sitemap | pluginSitemap | 3.22.0 | plugin | |
| Static Pages | pluginStaticPages | 3.22.0 | plugin | |
| Tags | pluginTags | 3.22.0 | plugin | |
| TimeMachine X | pluginTimeMachineX | 3.22.0 | plugin (Pro) | |
| TinyMCE | pluginTinymce | 8.3.2 | plugin | |
| Version | pluginVersion | 3.22.0 | plugin | |
| Visits Stats | pluginVisitsStats | 3.22.0 | plugin | |

---

## 24. REMAINING BUGS (not patched)

- Sticky pages merged only on page 1
- $staticContent built even on 404
- Silent reindex loop on stale category/tag data
- Theme::plugins() echoes directly (plugins can't return values)

---

## 25. FILE PATH REFERENCE (patched files marked)

```
bludit/
├─ index.php
├─ bl-kernel/
│  ├─ boot/
│  │  ├─ init.php
│  │  ├─ admin.php
│  │  ├─ site.php
│  │  ├─ variables.php
│  │  └─ rules/
│  │     ├─ 60.plugins.php
│  │     ├─ 60.router.php
│  │     ├─ 69.pages.php           (patched: scheduler + previewSalt)
│  │     ├─ 99.header.php
│  │     ├─ 99.paginator.php
│  │     ├─ 99.security.php
│  │     └─ 99.themes.php
│  ├─ abstract/
│  │  ├─ dbjson.class.php          (patched: LOCK_SH read)
│  │  ├─ dblist.class.php
│  │  └─ plugin.class.php          (patched: getValue isset check)
│  ├─ helpers/
│  │  ├─ alert.class.php
│  │  ├─ cookie.class.php
│  │  ├─ date.class.php
│  │  ├─ dom.class.php
│  │  ├─ email.class.php
│  │  ├─ filesystem.class.php
│  │  ├─ image.class.php           (WebP support)
│  │  ├─ log.class.php
│  │  ├─ paginator.class.php
│  │  ├─ redirect.class.php
│  │  ├─ sanitize.class.php
│  │  ├─ session.class.php
│  │  ├─ tcp.class.php
│  │  ├─ text.class.php
│  │  ├─ theme.class.php           (patched: metaTagTitle fallback)
│  │  └─ valid.class.php
│  ├─ ajax/
│  │  ├─ clippy.php                (patched: role filter)
│  │  └─ ...
│  ├─ admin/
│  ├─ css/
│  ├─ js/
│  ├─ functions.php                (patched: buildThePage uses previewSalt)
│  ├─ pages.class.php              (patched: scheduler return array)
│  ├─ site.class.php               (patched: previewSalt)
│  ├─ security.class.php
│  ├─ login.class.php
│  ├─ pagex.class.php              (patched: readingTime text mode)
│  ├─ url.class.php
│  ├─ user.class.php
│  └─ bludit.pro.*.php
├─ bl-plugins/
│  ├─ api/plugin.php               (patched: image MIME check)
│  ├─ backup-manager/plugin.php    (patched: emergency backup abort)
│  └─ ...
├─ bl-themes/alternative/
└─ bl-content/
```