Merge branch 'e.noorlander'
This commit is contained in:
commit
704cd0ff0c
44
.htaccess
Normal file
44
.htaccess
Normal file
@ -0,0 +1,44 @@
|
||||
# Security - Block access to entire application
|
||||
<Files ~ "^\.">
|
||||
Order allow,deny
|
||||
Deny from all
|
||||
</Files>
|
||||
|
||||
<FilesMatch "\.(php|ini|log|conf|config|md)$">
|
||||
Order allow,deny
|
||||
Deny from all
|
||||
</FilesMatch>
|
||||
|
||||
# Block access to all application files
|
||||
<IfModule mod_authz_core.c>
|
||||
Require all denied
|
||||
</IfModule>
|
||||
|
||||
# Directory protection - Block all access
|
||||
<Directory />
|
||||
Order allow,deny
|
||||
Deny from all
|
||||
</Directory>
|
||||
|
||||
# Only allow access to public directory
|
||||
<Directory "public">
|
||||
Order allow,deny
|
||||
Allow from all
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
# Set default directory to public
|
||||
DirectoryIndex public/index.php
|
||||
|
||||
# Redirect root to public directory
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
RewriteBase /
|
||||
|
||||
# Redirect root to public
|
||||
RewriteRule ^$ public/ [L]
|
||||
|
||||
# Redirect all other requests to public
|
||||
RewriteCond %{REQUEST_URI} !^/public/
|
||||
RewriteRule ^(.*)$ public/$1 [L]
|
||||
</IfModule>
|
||||
19
AGENTS.md
Normal file
19
AGENTS.md
Normal file
@ -0,0 +1,19 @@
|
||||
# Agent Instructions for CodePress CMS
|
||||
|
||||
## Build & Run
|
||||
- **Run Server**: `php -S localhost:8080 -t public`
|
||||
- **Lint PHP**: `find . -name "*.php" -exec php -l {} \;`
|
||||
- **Dependencies**: No Composer/NPM required. Native PHP 8.4+ implementation.
|
||||
|
||||
## Code Style & Conventions
|
||||
- **PHP Standards**: Follow PSR-12. Use 4 spaces for indentation.
|
||||
- **Naming**: Classes `PascalCase` (e.g., `CodePressCMS`), methods `camelCase` (e.g., `renderMenu`), variables `camelCase`, config keys `snake_case`.
|
||||
- **Architecture**:
|
||||
- Core logic resides in `index.php`.
|
||||
- Configuration in `config.php`.
|
||||
- Public entry point is `public/index.php`.
|
||||
- **Content**: Stored in `public/content/`. Supports `.md` (Markdown), `.php` (Dynamic), `.html` (Static).
|
||||
- **Templating**: Simple string replacement `{{placeholder}}` in `templates/layout.html`.
|
||||
- **Navigation**: Auto-generated from directory structure. Folders require an index file to be clickable in breadcrumbs.
|
||||
- **Security**: Always use `htmlspecialchars()` for outputting user/content data.
|
||||
- **Git**: `main` is the clean CMS core. `e.noorlander` contains personal content. Do not mix them.
|
||||
42
README.md
42
README.md
@ -33,21 +33,30 @@ CodePress is a modern, secure CMS that manages content through files instead of
|
||||
```
|
||||
3. **Start server**:
|
||||
```bash
|
||||
php -S localhost:8000 -t public
|
||||
# For Apache: Set DocumentRoot to public/
|
||||
# For Development:
|
||||
php -S localhost:8080 -t public router.php
|
||||
```
|
||||
4. **Visit**: `http://localhost:8000`
|
||||
4. **Visit**: `http://localhost:8080`
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
codepress/
|
||||
├── public/ # Web-accessible directory
|
||||
│ ├── content/ # Content files (MD/PHP/HTML)
|
||||
│ ├── assets/ # Static assets (images, icons)
|
||||
│ └── .htaccess # Security and routing
|
||||
├── templates/ # HTML templates
|
||||
├── config.php # Site configuration
|
||||
├── index.php # Main application logic
|
||||
├── public/ # Web-accessible directory (DocumentRoot)
|
||||
│ ├── index.php # Main entry point
|
||||
│ ├── .htaccess # Apache security and routing
|
||||
│ └── router.php # PHP development server router
|
||||
├── content/ # Content files (MD/PHP/HTML) - outside web root
|
||||
├── engine/ # CMS engine and assets
|
||||
│ ├── core/ # PHP application logic
|
||||
│ │ ├── index.php # CMS class and logic
|
||||
│ │ └── config.php # Site configuration
|
||||
│ ├── templates/ # HTML templates
|
||||
│ └── assets/ # Static assets (CSS, JS, fonts)
|
||||
│ ├── css/ # Bootstrap and custom CSS
|
||||
│ ├── js/ # JavaScript files
|
||||
│ └── fonts/ # Font files
|
||||
├── .htaccess # Root security
|
||||
└── README.md # This documentation
|
||||
```
|
||||
@ -55,11 +64,13 @@ codepress/
|
||||
## Security
|
||||
|
||||
CodePress includes built-in security features:
|
||||
- **.htaccess protection** for sensitive files
|
||||
- **PHP file blocking** in content directory
|
||||
- **Content isolation** - Content files stored outside web root
|
||||
- **.htaccess protection** for sensitive files and directories
|
||||
- **Direct access blocking** - Content files not accessible via URL
|
||||
- **Security headers** for XSS protection
|
||||
- **PHP file blocking** in content directory
|
||||
- **Offline capable** - All assets (Bootstrap) stored locally
|
||||
- **Directory access control**
|
||||
- **Public directory isolation**
|
||||
|
||||
## Content Management
|
||||
|
||||
@ -95,7 +106,7 @@ $date = date('Y-m-d');
|
||||
|
||||
### Directory Structure
|
||||
```
|
||||
public/content/
|
||||
content/
|
||||
├── home.md # Homepage
|
||||
├── about/
|
||||
│ └── company.md # About page
|
||||
@ -115,7 +126,8 @@ Edit `config.php` to customize:
|
||||
return [
|
||||
'site_title' => 'Your Site Name',
|
||||
'site_description' => 'Your site description',
|
||||
'content_dir' => __DIR__ . '/public/content',
|
||||
'content_dir' => __DIR__ . '/../../content',
|
||||
'templates_dir' => __DIR__ . '/../templates',
|
||||
'default_page' => 'home',
|
||||
'markdown_enabled' => true,
|
||||
'php_enabled' => true,
|
||||
@ -148,7 +160,7 @@ This project is developed for specific use cases. Contact the maintainer for lic
|
||||
|
||||
## Support
|
||||
|
||||
- **Documentation**: See `public/content/home.md`
|
||||
- **Documentation**: See `content/home.md`
|
||||
- **Issues**: Report on GitLab
|
||||
- **Community**: Join discussions
|
||||
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="codepress-gradient-small" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#0d6efd;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#6610f2;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background circle -->
|
||||
<circle cx="8" cy="8" r="7.5" fill="url(#codepress-gradient-small)" stroke="#ffffff" stroke-width="0.5"/>
|
||||
|
||||
<!-- Code brackets -->
|
||||
<path d="M4 5 L3 6 L3 10 L4 11" stroke="#ffffff" stroke-width="1" fill="none" stroke-linecap="round"/>
|
||||
<path d="M12 5 L13 6 L13 10 L12 11" stroke="#ffffff" stroke-width="1" fill="none" stroke-linecap="round"/>
|
||||
|
||||
<!-- Code slash -->
|
||||
<path d="M7 4 L9 12" stroke="#ffffff" stroke-width="1" stroke-linecap="round"/>
|
||||
|
||||
<!-- Press dots -->
|
||||
<circle cx="6" cy="13" r="0.75" fill="#ffffff"/>
|
||||
<circle cx="8" cy="13" r="0.75" fill="#ffffff"/>
|
||||
<circle cx="10" cy="13" r="0.75" fill="#ffffff"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB |
@ -1,23 +0,0 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="codepress-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#0d6efd;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#6610f2;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background circle -->
|
||||
<circle cx="16" cy="16" r="15" fill="url(#codepress-gradient)" stroke="#ffffff" stroke-width="1"/>
|
||||
|
||||
<!-- Code brackets -->
|
||||
<path d="M8 10 L6 12 L6 20 L8 22" stroke="#ffffff" stroke-width="2" fill="none" stroke-linecap="round"/>
|
||||
<path d="M24 10 L26 12 L26 20 L24 22" stroke="#ffffff" stroke-width="2" fill="none" stroke-linecap="round"/>
|
||||
|
||||
<!-- Code slash -->
|
||||
<path d="M14 8 L18 24" stroke="#ffffff" stroke-width="2" stroke-linecap="round"/>
|
||||
|
||||
<!-- Press dots -->
|
||||
<circle cx="12" cy="26" r="1.5" fill="#ffffff"/>
|
||||
<circle cx="16" cy="26" r="1.5" fill="#ffffff"/>
|
||||
<circle cx="20" cy="26" r="1.5" fill="#ffffff"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1018 B |
@ -4,7 +4,7 @@ return [
|
||||
'site_title' => 'CodePress',
|
||||
'site_description' => 'A simple PHP CMS',
|
||||
'base_url' => '/',
|
||||
'content_dir' => __DIR__ . '/content',
|
||||
'content_dir' => __DIR__ . '/public/content',
|
||||
'templates_dir' => __DIR__ . '/templates',
|
||||
'cache_dir' => __DIR__ . '/cache',
|
||||
'default_page' => 'home',
|
||||
|
||||
@ -5,14 +5,30 @@ Welkom op de persoonlijke blog van Edwin Noorlander. Hier deel ik mijn gedachten
|
||||
## Categorieën
|
||||
|
||||
### Over Mij
|
||||
- [Welkom, ik ben Edwin](/blog/over-mij/welkom) - Mijn persoonlijke verhaal en achtergrond
|
||||
- [Welkom, ik ben Edwin](?page=blog/over-mij/welkom " title="Lees meer over Edwin Noorlander") - Mijn persoonlijke verhaal en achtergrond
|
||||
|
||||
### Open Source
|
||||
- [De Toekomst van ICT](/blog/open-source/de-toekomst-van-ict) - Hoe open source software de werkvloer transformeert
|
||||
- [Standaardisatie](/blog/open-source/standaardisatie) - Het belang van standaarden en de tegenstelling tussen commerciële bedrijven en open-source gemeenschappen
|
||||
- [De Toekomst van ICT](?page=blog/open-source/de-toekomst-van-ict) - Hoe open source software de werkvloer transformeert
|
||||
- [Standaardisatie](?page=blog/open-source/standaardisatie) - Het belang van standaarden en de tegenstelling tussen commerciële bedrijven en open-source gemeenschappen
|
||||
|
||||
### Leren & Ontwikkeling
|
||||
- [Kennis boven Aantallen](/blog/leren/kennis-boven-aantallen) - Het cruciale belang van kennis boven personeelsaantallen in de werkkracht
|
||||
- [Kennis boven Aantallen](?page=blog/leren/kennis-boven-aantallen) - Het cruciale belang van kennis boven personeelsaantallen in de werkkracht
|
||||
|
||||
### ICT & Bedrijfsvoering
|
||||
- [Excel als Database](?page=blog/ict/excel-als-database) - Waarom grote bedrijven en overheden Access niet toestaan
|
||||
|
||||
### Micro-electronica
|
||||
- [Wat is Arduino](?page=blog/micro-electronica/wat-is-arduino) - Open-source elektronisch platform voor interactieve projecten
|
||||
- [Leren gaat niet over perfectie](?page=blog/micro-electronica/leren-gaat-niet-over-perfectie) - Passie voor electronica en open-source hardware/software
|
||||
|
||||
### Hardware & ICT
|
||||
- [De ware aard van ICT](?page=blog/hardware/de-ware-aard-van-ict) - Meer dan alleen computers en software
|
||||
|
||||
### Retro Gaming
|
||||
- [Commodore 64](?page=blog/retro-gaming/commodore-64) - Een les in creativiteit en innovatie in de game-industrie
|
||||
|
||||
### Linux & Open Source
|
||||
- [Open Source Voorbeelden](?page=blog/linux/open-source-voorbeelden) - Vijf voorbeelden van open-source software in gebruik bij commerciële bedrijven
|
||||
|
||||
## Over Edwin Noorlander
|
||||
|
||||
|
||||
2078
engine/assets/css/bootstrap-icons.css
vendored
Normal file
2078
engine/assets/css/bootstrap-icons.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
6
engine/assets/css/bootstrap.min.css
vendored
Normal file
6
engine/assets/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
engine/assets/css/bootstrap.min.css.map
Normal file
1
engine/assets/css/bootstrap.min.css.map
Normal file
File diff suppressed because one or more lines are too long
BIN
engine/assets/css/fonts/bootstrap-icons.woff
Normal file
BIN
engine/assets/css/fonts/bootstrap-icons.woff
Normal file
Binary file not shown.
BIN
engine/assets/css/fonts/bootstrap-icons.woff2
Normal file
BIN
engine/assets/css/fonts/bootstrap-icons.woff2
Normal file
Binary file not shown.
11
engine/assets/favicon.svg
Normal file
11
engine/assets/favicon.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- < -->
|
||||
<path d="M8 8 L3 16 L8 24" stroke="#ffffff" stroke-width="3" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
|
||||
<!-- / -->
|
||||
<path d="M12 24 L18 8" stroke="#ffffff" stroke-width="3" stroke-linecap="round"/>
|
||||
|
||||
<!-- .. -->
|
||||
<circle cx="22" cy="20" r="2" fill="#ffffff"/>
|
||||
<circle cx="28" cy="20" r="2" fill="#ffffff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 442 B |
11
engine/assets/icon.svg
Normal file
11
engine/assets/icon.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- < -->
|
||||
<path d="M8 8 L3 16 L8 24" stroke="#ffffff" stroke-width="3" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
|
||||
<!-- / -->
|
||||
<path d="M12 24 L18 8" stroke="#ffffff" stroke-width="3" stroke-linecap="round"/>
|
||||
|
||||
<!-- .. -->
|
||||
<circle cx="22" cy="20" r="2" fill="#ffffff"/>
|
||||
<circle cx="28" cy="20" r="2" fill="#ffffff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 442 B |
7
engine/assets/js/bootstrap.bundle.min.js
vendored
Normal file
7
engine/assets/js/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
engine/assets/js/bootstrap.bundle.min.js.map
Normal file
1
engine/assets/js/bootstrap.bundle.min.js.map
Normal file
File diff suppressed because one or more lines are too long
@ -4,9 +4,9 @@ return [
|
||||
'site_title' => 'CodePress',
|
||||
'site_description' => 'A simple PHP CMS',
|
||||
'base_url' => '/',
|
||||
'content_dir' => __DIR__ . '/content',
|
||||
'templates_dir' => __DIR__ . '/templates',
|
||||
'cache_dir' => __DIR__ . '/cache',
|
||||
'content_dir' => __DIR__ . '/../../content',
|
||||
'templates_dir' => __DIR__ . '/../templates',
|
||||
'cache_dir' => __DIR__ . '/../../cache',
|
||||
'default_page' => 'home',
|
||||
'error_404' => '404',
|
||||
'markdown_enabled' => true,
|
||||
476
engine/core/index.php
Normal file
476
engine/core/index.php
Normal file
@ -0,0 +1,476 @@
|
||||
<?php
|
||||
|
||||
require_once 'config.php';
|
||||
|
||||
$config = include 'config.php';
|
||||
|
||||
class CodePressCMS {
|
||||
private $config;
|
||||
private $menu = [];
|
||||
private $searchResults = [];
|
||||
|
||||
public function __construct($config) {
|
||||
$this->config = $config;
|
||||
$this->buildMenu();
|
||||
|
||||
if (isset($_GET['search'])) {
|
||||
$this->performSearch($_GET['search']);
|
||||
}
|
||||
}
|
||||
|
||||
private function buildMenu() {
|
||||
$this->menu = $this->scanDirectory($this->config['content_dir'], '');
|
||||
}
|
||||
|
||||
private function scanDirectory($dir, $prefix) {
|
||||
if (!is_dir($dir)) return [];
|
||||
|
||||
$items = scandir($dir);
|
||||
sort($items);
|
||||
$result = [];
|
||||
|
||||
foreach ($items as $item) {
|
||||
if ($item[0] === '.') continue;
|
||||
|
||||
$path = $dir . '/' . $item;
|
||||
$relativePath = $prefix ? $prefix . '/' . $item : $item;
|
||||
|
||||
if (is_dir($path)) {
|
||||
$result[] = [
|
||||
'type' => 'folder',
|
||||
'title' => ucfirst($item),
|
||||
'path' => $relativePath,
|
||||
'children' => $this->scanDirectory($path, $relativePath)
|
||||
];
|
||||
} elseif (preg_match('/\.(md|php|html)$/', $item)) {
|
||||
$title = ucfirst(pathinfo($item, PATHINFO_FILENAME));
|
||||
$result[] = [
|
||||
'type' => 'file',
|
||||
'title' => $title,
|
||||
'path' => $relativePath,
|
||||
'url' => '?page=' . $relativePath
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function performSearch($query) {
|
||||
$this->searchResults = [];
|
||||
$this->searchInDirectory($this->config['content_dir'], '', $query);
|
||||
}
|
||||
|
||||
private function searchInDirectory($dir, $prefix, $query) {
|
||||
if (!is_dir($dir)) return;
|
||||
|
||||
$items = scandir($dir);
|
||||
|
||||
foreach ($items as $item) {
|
||||
if ($item[0] === '.') continue;
|
||||
|
||||
$path = $dir . '/' . $item;
|
||||
$relativePath = $prefix ? $prefix . '/' . $item : $item;
|
||||
|
||||
if (is_dir($path)) {
|
||||
$this->searchInDirectory($path, $relativePath, $query);
|
||||
} elseif (preg_match('/\.(md|php|html)$/', $item)) {
|
||||
$content = file_get_contents($path);
|
||||
if (stripos($content, $query) !== false || stripos($item, $query) !== false) {
|
||||
$title = ucfirst(pathinfo($item, PATHINFO_FILENAME));
|
||||
$this->searchResults[] = [
|
||||
'title' => $title,
|
||||
'path' => $relativePath,
|
||||
'url' => '?page=' . $relativePath,
|
||||
'snippet' => $this->createSnippet($content, $query)
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function createSnippet($content, $query) {
|
||||
$content = strip_tags($content);
|
||||
$pos = stripos($content, $query);
|
||||
if ($pos === false) return substr($content, 0, 100) . '...';
|
||||
|
||||
$start = max(0, $pos - 50);
|
||||
$snippet = substr($content, $start, 150);
|
||||
return '...' . $snippet . '...';
|
||||
}
|
||||
|
||||
public function getPage() {
|
||||
if (isset($_GET['search'])) {
|
||||
return $this->getSearchResults();
|
||||
}
|
||||
|
||||
$page = $_GET['page'] ?? $this->config['default_page'];
|
||||
$page = preg_replace('/\.[^.]+$/', '', $page);
|
||||
|
||||
$filePath = $this->config['content_dir'] . '/' . $page;
|
||||
$actualFilePath = null;
|
||||
|
||||
if (file_exists($filePath . '.md')) {
|
||||
$actualFilePath = $filePath . '.md';
|
||||
$result = $this->parseMarkdown(file_get_contents($actualFilePath));
|
||||
} elseif (file_exists($filePath . '.php')) {
|
||||
$actualFilePath = $filePath . '.php';
|
||||
$result = $this->parsePHP($actualFilePath);
|
||||
} elseif (file_exists($filePath . '.html')) {
|
||||
$actualFilePath = $filePath . '.html';
|
||||
$result = $this->parseHTML(file_get_contents($actualFilePath));
|
||||
} elseif (file_exists($filePath)) {
|
||||
$actualFilePath = $filePath;
|
||||
$extension = pathinfo($filePath, PATHINFO_EXTENSION);
|
||||
if ($extension === 'md') {
|
||||
$result = $this->parseMarkdown(file_get_contents($actualFilePath));
|
||||
} elseif ($extension === 'php') {
|
||||
$result = $this->parsePHP($actualFilePath);
|
||||
} elseif ($extension === 'html') {
|
||||
$result = $this->parseHTML(file_get_contents($actualFilePath));
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($result) && $actualFilePath) {
|
||||
$result['file_info'] = $this->getFileInfo($actualFilePath);
|
||||
return $result;
|
||||
}
|
||||
|
||||
return $this->getError404();
|
||||
}
|
||||
|
||||
private function getFileInfo($filePath) {
|
||||
if (!file_exists($filePath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$stats = stat($filePath);
|
||||
$created = date('d-m-Y H:i', $stats['ctime']);
|
||||
$modified = date('d-m-Y H:i', $stats['mtime']);
|
||||
|
||||
return [
|
||||
'created' => $created,
|
||||
'modified' => $modified,
|
||||
'size' => $this->formatFileSize($stats['size'])
|
||||
];
|
||||
}
|
||||
|
||||
private function formatFileSize($bytes) {
|
||||
$units = ['B', 'KB', 'MB', 'GB'];
|
||||
$bytes = max($bytes, 0);
|
||||
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
|
||||
$pow = min($pow, count($units) - 1);
|
||||
|
||||
$bytes /= pow(1024, $pow);
|
||||
|
||||
return round($bytes, 2) . ' ' . $units[$pow];
|
||||
}
|
||||
|
||||
private function getSearchResults() {
|
||||
$query = $_GET['search'];
|
||||
$content = '<h2>Search Results for: "' . htmlspecialchars($query) . '"</h2>';
|
||||
|
||||
if (empty($this->searchResults)) {
|
||||
$content .= '<p>No results found.</p>';
|
||||
} else {
|
||||
$content .= '<p>Found ' . count($this->searchResults) . ' results:</p>';
|
||||
foreach ($this->searchResults as $result) {
|
||||
$content .= '<div class="card mb-3">';
|
||||
$content .= '<div class="card-body">';
|
||||
$content .= '<h5 class="card-title"><a href="' . htmlspecialchars($result['url']) . '">' . htmlspecialchars($result['title']) . '</a></h5>';
|
||||
$content .= '<p class="card-text text-muted">' . htmlspecialchars($result['path']) . '</p>';
|
||||
$content .= '<p class="card-text">' . htmlspecialchars($result['snippet']) . '</p>';
|
||||
$content .= '</div></div>';
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'title' => 'Search Results',
|
||||
'content' => $content
|
||||
];
|
||||
}
|
||||
|
||||
private function parseMarkdown($content) {
|
||||
$lines = explode("\n", $content);
|
||||
$title = '';
|
||||
$body = '';
|
||||
$inBody = false;
|
||||
|
||||
foreach ($lines as $line) {
|
||||
if (!$inBody && preg_match('/^#\s+(.+)$/', $line, $matches)) {
|
||||
$title = $matches[1];
|
||||
$inBody = true;
|
||||
} elseif ($inBody || trim($line) !== '') {
|
||||
$body .= $line . "\n";
|
||||
$inBody = true;
|
||||
}
|
||||
}
|
||||
|
||||
$body = preg_replace('/### (.+)/', '<h3>$1</h3>', $body);
|
||||
$body = preg_replace('/## (.+)/', '<h2>$1</h2>', $body);
|
||||
$body = preg_replace('/# (.+)/', '<h1>$1</h1>', $body);
|
||||
$body = preg_replace('/\*\*(.+?)\*\*/', '<strong>$1</strong>', $body);
|
||||
$body = preg_replace('/\*(.+?)\*/', '<em>$1</em>', $body);
|
||||
|
||||
// Auto-link page titles to existing content pages (before markdown link processing)
|
||||
$body = $this->autoLinkPageTitles($body);
|
||||
|
||||
// Convert Markdown links to HTML links
|
||||
$body = preg_replace('/\[([^\]]+)\]\(([^)]+)\)/', '<a href="$2">$1</a>', $body);
|
||||
|
||||
// Convert relative internal links to CMS format
|
||||
$body = preg_replace('/href="\/blog\/([^"]+)"/', 'href="?page=blog/$1"', $body);
|
||||
$body = preg_replace('/href="\/([^"]+)"/', 'href="?page=$1"', $body);
|
||||
|
||||
$body = preg_replace('/\n\n/', '</p><p>', $body);
|
||||
$body = '<p>' . $body . '</p>';
|
||||
$body = preg_replace('/<p><\/p>/', '', $body);
|
||||
$body = preg_replace('/<p>(<h[1-6]>)/', '$1', $body);
|
||||
$body = preg_replace('/(<\/h[1-6]>)<\/p>/', '$1', $body);
|
||||
|
||||
return [
|
||||
'title' => $title ?: 'Untitled',
|
||||
'content' => $body
|
||||
];
|
||||
}
|
||||
|
||||
private function autoLinkPageTitles($content) {
|
||||
// Get all available pages with their titles
|
||||
$pages = $this->getAllPageTitles();
|
||||
|
||||
foreach ($pages as $pagePath => $pageTitle) {
|
||||
// Create a pattern that matches the exact page title (case-insensitive)
|
||||
// Use word boundaries to avoid partial matches
|
||||
$pattern = '/\b' . preg_quote($pageTitle, '/') . '\b/i';
|
||||
|
||||
// Replace with link, but avoid linking inside existing links, headings, or markdown
|
||||
$replacement = function($matches) use ($pageTitle, $pagePath) {
|
||||
$text = $matches[0];
|
||||
|
||||
// Check if we're inside an existing link or markdown syntax
|
||||
if (preg_match('/\[.*?\]\(.*?\)/', $text) ||
|
||||
preg_match('/\[.*?\]:/', $text) ||
|
||||
preg_match('/<a[^>]*>/', $text) ||
|
||||
preg_match('/href=/', $text)) {
|
||||
return $text; // Don't link existing links
|
||||
}
|
||||
|
||||
return '<a href="?page=' . $pagePath . '" class="auto-link" title="Ga naar ' . htmlspecialchars($pageTitle) . '">' . $text . '</a>';
|
||||
};
|
||||
|
||||
$content = preg_replace_callback($pattern, $replacement, $content);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function getAllPageTitles() {
|
||||
$pages = [];
|
||||
$this->scanForPageTitles($this->config['content_dir'], '', $pages);
|
||||
return $pages;
|
||||
}
|
||||
|
||||
private function scanForPageTitles($dir, $prefix, &$pages) {
|
||||
if (!is_dir($dir)) return;
|
||||
|
||||
$items = scandir($dir);
|
||||
sort($items);
|
||||
|
||||
foreach ($items as $item) {
|
||||
if ($item[0] === '.') continue;
|
||||
|
||||
$path = $dir . '/' . $item;
|
||||
$relativePath = $prefix ? $prefix . '/' . $item : $item;
|
||||
|
||||
if (is_dir($path)) {
|
||||
$this->scanForPageTitles($path, $relativePath, $pages);
|
||||
} elseif (preg_match('/\.(md|php|html)$/', $item)) {
|
||||
$title = $this->extractPageTitle($path);
|
||||
if ($title && !empty(trim($title))) {
|
||||
$pagePath = preg_replace('/\.[^.]+$/', '', $relativePath);
|
||||
$pages[$pagePath] = $title;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function extractPageTitle($filePath) {
|
||||
$content = file_get_contents($filePath);
|
||||
$extension = pathinfo($filePath, PATHINFO_EXTENSION);
|
||||
|
||||
if ($extension === 'md') {
|
||||
// Extract first H1 from Markdown
|
||||
if (preg_match('/^#\s+(.+)$/m', $content, $matches)) {
|
||||
return trim($matches[1]);
|
||||
}
|
||||
} elseif ($extension === 'php') {
|
||||
// Extract title from PHP file
|
||||
if (preg_match('/\$title\s*=\s*["\']([^"\']+)["\']/', $content, $matches)) {
|
||||
return trim($matches[1]);
|
||||
}
|
||||
} elseif ($extension === 'html') {
|
||||
// Extract title from HTML file
|
||||
if (preg_match('/<title>(.*?)<\/title>/i', $content, $matches)) {
|
||||
return trim(strip_tags($matches[1]));
|
||||
}
|
||||
if (preg_match('/<h1[^>]*>(.*?)<\/h1>/i', $content, $matches)) {
|
||||
return trim(strip_tags($matches[1]));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function parsePHP($filePath) {
|
||||
ob_start();
|
||||
$title = 'Untitled';
|
||||
include $filePath;
|
||||
$content = ob_get_clean();
|
||||
|
||||
return [
|
||||
'title' => $title,
|
||||
'content' => $content
|
||||
];
|
||||
}
|
||||
|
||||
private function parseHTML($content) {
|
||||
$title = 'Untitled';
|
||||
|
||||
if (preg_match('/<title>(.*?)<\/title>/i', $content, $matches)) {
|
||||
$title = strip_tags($matches[1]);
|
||||
} elseif (preg_match('/<h1[^>]*>(.*?)<\/h1>/i', $content, $matches)) {
|
||||
$title = strip_tags($matches[1]);
|
||||
}
|
||||
|
||||
return [
|
||||
'title' => $title,
|
||||
'content' => $content
|
||||
];
|
||||
}
|
||||
|
||||
private function getError404() {
|
||||
return [
|
||||
'title' => 'Page Not Found',
|
||||
'content' => '<h1>404 - Page Not Found</h1><p>The page you are looking for does not exist.</p>'
|
||||
];
|
||||
}
|
||||
|
||||
public function getMenu() {
|
||||
return $this->menu;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$page = $this->getPage();
|
||||
$menu = $this->getMenu();
|
||||
$breadcrumb = $this->getBreadcrumb();
|
||||
|
||||
$template = file_get_contents($this->config['templates_dir'] . '/layout.html');
|
||||
|
||||
$template = str_replace('{{site_title}}', $this->config['site_title'], $template);
|
||||
$template = str_replace('{{page_title}}', $page['title'], $template);
|
||||
$template = str_replace('{{content}}', $page['content'], $template);
|
||||
$template = str_replace('{{search_query}}', isset($_GET['search']) ? htmlspecialchars($_GET['search']) : '', $template);
|
||||
$template = str_replace('{{breadcrumb}}', $breadcrumb, $template);
|
||||
|
||||
// File info for footer
|
||||
$fileInfo = '';
|
||||
if (isset($page['file_info'])) {
|
||||
$fileInfo = '<i class="bi bi-file-text"></i> Created: ' . htmlspecialchars($page['file_info']['created']) .
|
||||
' | Modified: ' . htmlspecialchars($page['file_info']['modified']);
|
||||
}
|
||||
$template = str_replace('{{file_info}}', $fileInfo, $template);
|
||||
|
||||
$menuHtml = $this->renderMenu($menu);
|
||||
|
||||
$template = str_replace('{{menu}}', $menuHtml, $template);
|
||||
|
||||
echo $template;
|
||||
}
|
||||
|
||||
private function getBreadcrumb() {
|
||||
if (isset($_GET['search'])) {
|
||||
return '<nav aria-label="breadcrumb"><ol class="breadcrumb"><li class="breadcrumb-item"><a href="?page=' . $this->config['default_page'] . '">Home</a></li><li class="breadcrumb-item active">Search</li></ol></nav>';
|
||||
}
|
||||
|
||||
$page = $_GET['page'] ?? $this->config['default_page'];
|
||||
$page = preg_replace('/\.[^.]+$/', '', $page);
|
||||
|
||||
if ($page === $this->config['default_page']) {
|
||||
return '<nav aria-label="breadcrumb"><ol class="breadcrumb"><li class="breadcrumb-item active">Home</li></ol></nav>';
|
||||
}
|
||||
|
||||
$parts = explode('/', $page);
|
||||
$breadcrumb = '<nav aria-label="breadcrumb"><ol class="breadcrumb"><li class="breadcrumb-item"><a href="?page=' . $this->config['default_page'] . '">Home</a></li>';
|
||||
|
||||
$path = '';
|
||||
foreach ($parts as $i => $part) {
|
||||
$path .= ($path ? '/' : '') . $part;
|
||||
$title = ucfirst($part);
|
||||
|
||||
if ($i === count($parts) - 1) {
|
||||
$breadcrumb .= '<li class="breadcrumb-item active">' . $title . '</li>';
|
||||
} else {
|
||||
$breadcrumb .= '<li class="breadcrumb-item"><a href="?page=' . $path . '">' . $title . '</a></li>';
|
||||
}
|
||||
}
|
||||
|
||||
$breadcrumb .= '</ol></nav>';
|
||||
return $breadcrumb;
|
||||
}
|
||||
|
||||
private function renderMenu($items, $level = 0) {
|
||||
$html = '';
|
||||
foreach ($items as $item) {
|
||||
if ($item['type'] === 'folder') {
|
||||
$hasChildren = !empty($item['children']);
|
||||
$html .= '<li class="nav-item">';
|
||||
|
||||
if ($hasChildren) {
|
||||
$folderId = 'folder-' . str_replace('/', '-', $item['path']);
|
||||
|
||||
// Check if this folder contains the active page
|
||||
$containsActive = $this->folderContainsActivePage($item['children']);
|
||||
$ariaExpanded = $containsActive ? 'true' : 'false';
|
||||
$collapseClass = $containsActive ? 'collapse show' : 'collapse';
|
||||
|
||||
$html .= '<span class="nav-link folder-toggle" data-bs-toggle="collapse" data-bs-target="#' . $folderId . '" aria-expanded="' . $ariaExpanded . '">';
|
||||
$html .= '<i class="arrow bi bi-chevron-right"></i> ' . htmlspecialchars($item['title']);
|
||||
$html .= '</span>';
|
||||
$html .= '<ul class="nav flex-column ms-2 ' . $collapseClass . '" id="' . $folderId . '">';
|
||||
$html .= $this->renderMenu($item['children'], $level + 1);
|
||||
$html .= '</ul>';
|
||||
} else {
|
||||
$html .= '<span class="nav-link folder-disabled" disabled>';
|
||||
$html .= '<i class="arrow bi bi-chevron-right"></i> ' . htmlspecialchars($item['title']);
|
||||
$html .= '</span>';
|
||||
}
|
||||
|
||||
$html .= '</li>';
|
||||
} else {
|
||||
$active = (isset($_GET['page']) && $_GET['page'] === $item['path']) ? 'active' : '';
|
||||
$html .= '<li class="nav-item">';
|
||||
$html .= '<a class="nav-link page-link ' . $active . '" href="' . htmlspecialchars($item['url']) . '">' . htmlspecialchars($item['title']) . '</a>';
|
||||
$html .= '</li>';
|
||||
}
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
|
||||
private function folderContainsActivePage($children) {
|
||||
foreach ($children as $child) {
|
||||
if ($child['type'] === 'folder') {
|
||||
if (!empty($child['children']) && $this->folderContainsActivePage($child['children'])) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (isset($_GET['page']) && $_GET['page'] === $child['path']) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$cms = new CodePressCMS($config);
|
||||
$cms->render();
|
||||
@ -4,9 +4,9 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{page_title}} - {{site_title}}</title>
|
||||
<link rel="icon" type="image/svg+xml" href="assets/favicon.svg">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||
<link rel="icon" type="image/svg+xml" href="/engine/assets/favicon.svg">
|
||||
<link href="/engine/assets/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="/engine/assets/css/bootstrap-icons.css" rel="stylesheet">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
@ -22,12 +22,14 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: calc(100vh - 70px); /* Minus header height */
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
@ -42,12 +44,81 @@
|
||||
border-right: 1px solid #dee2e6;
|
||||
overflow-y: auto;
|
||||
flex-shrink: 0;
|
||||
transition: transform 0.3s ease;
|
||||
position: fixed;
|
||||
top: 70px;
|
||||
left: 0;
|
||||
height: calc(100vh - 140px); /* 70px header + 70px footer */
|
||||
z-index: 999;
|
||||
transform: translateX(0);
|
||||
}
|
||||
.sidebar.collapsed {
|
||||
transform: translateX(-250px);
|
||||
}
|
||||
.sidebar-toggle {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
z-index: 1001;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
font-size: 20px;
|
||||
color: #6c757d;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.sidebar-toggle:hover {
|
||||
color: #0d6efd;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.sidebar-toggle-inner {
|
||||
/* Toggle inside sidebar */
|
||||
}
|
||||
.sidebar-toggle-outer {
|
||||
position: fixed;
|
||||
top: 90px;
|
||||
left: 20px;
|
||||
z-index: 1001;
|
||||
background-color: white;
|
||||
border: 1px solid #dee2e6;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
.sidebar.collapsed .sidebar-toggle-inner {
|
||||
right: auto;
|
||||
left: 15px;
|
||||
}
|
||||
.sidebar.collapsed ~ .main-content .sidebar-toggle-outer {
|
||||
display: block !important;
|
||||
}
|
||||
.sidebar:not(.collapsed) ~ .main-content .sidebar-toggle-outer {
|
||||
display: none !important;
|
||||
}
|
||||
.sidebar-toggle:hover {
|
||||
background-color: #0a58ca;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
.main-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
transition: all 0.3s ease;
|
||||
margin-left: 250px;
|
||||
}
|
||||
.sidebar.collapsed ~ .main-content {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
transition: margin-left 0.3s ease;
|
||||
}
|
||||
|
||||
.folder-toggle {
|
||||
@ -155,6 +226,26 @@
|
||||
font-size: 0.9rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
.site-info a {
|
||||
color: #0d6efd;
|
||||
text-decoration: none;
|
||||
}
|
||||
.site-info a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.auto-link {
|
||||
color: #0d6efd;
|
||||
text-decoration: none;
|
||||
border-bottom: 2px dashed #0d6efd;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.auto-link:hover {
|
||||
color: #0a58ca;
|
||||
text-decoration: none;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: #0a58ca;
|
||||
}
|
||||
.search-form {
|
||||
max-width: 300px;
|
||||
}
|
||||
@ -195,7 +286,7 @@
|
||||
<div class="row align-items-center">
|
||||
<div class="col">
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="assets/icon.svg" alt="CodePress Logo" width="32" height="32" class="me-2">
|
||||
<img src="/engine/assets/icon.svg" alt="CodePress Logo" width="32" height="32" class="me-2">
|
||||
<h1 class="h3 mb-0">{{site_title}}</h1>
|
||||
</div>
|
||||
</div>
|
||||
@ -211,7 +302,10 @@
|
||||
|
||||
<div class="main-wrapper">
|
||||
<div class="content-wrapper">
|
||||
<nav class="sidebar">
|
||||
<nav class="sidebar" id="sidebar">
|
||||
<div class="sidebar-toggle sidebar-toggle-inner" id="sidebarToggleInner">
|
||||
<i class="bi bi-list"></i>
|
||||
</div>
|
||||
<div class="pt-3">
|
||||
<ul class="nav flex-column">
|
||||
{{menu}}
|
||||
@ -220,6 +314,9 @@
|
||||
</nav>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="sidebar-toggle sidebar-toggle-outer" id="sidebarToggleOuter" style="display: none;">
|
||||
<i class="bi bi-list"></i>
|
||||
</div>
|
||||
<div>
|
||||
{{breadcrumb}}
|
||||
</div>
|
||||
@ -233,7 +330,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="bg-light border-top py-3">
|
||||
<footer class="bg-light border-top py-3" style="position: fixed; bottom: 0; left: 0; right: 0; z-index: 998;">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
@ -242,7 +339,7 @@
|
||||
{{file_info}}
|
||||
</div>
|
||||
<div class="site-info">
|
||||
<small class="text-muted">Powered by CodePress CMS</small>
|
||||
<small class="text-muted">Powered by <a href="https://git.noorlander.info/E.Noorlander/CodePress.git" target="_blank" rel="noopener">CodePress CMS</a></small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -250,53 +347,90 @@
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="/engine/assets/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Open folders that contain the current active page
|
||||
const activeLink = document.querySelector('.nav-link.active');
|
||||
if (activeLink) {
|
||||
let parent = activeLink.closest('.collapse');
|
||||
while (parent) {
|
||||
const toggle = document.querySelector('[data-bs-target="#' + parent.id + '"]');
|
||||
if (toggle) {
|
||||
const collapse = new bootstrap.Collapse(parent, {
|
||||
show: true
|
||||
});
|
||||
toggle.setAttribute('aria-expanded', 'true');
|
||||
}
|
||||
parent = parent.parentElement.closest('.collapse');
|
||||
// Sidebar toggle functionality
|
||||
const sidebarToggleInner = document.getElementById('sidebarToggleInner');
|
||||
const sidebarToggleOuter = document.getElementById('sidebarToggleOuter');
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
|
||||
// Initialize sidebar state (open by default)
|
||||
sidebar.classList.remove('collapsed');
|
||||
const innerIcon = sidebarToggleInner.querySelector('i');
|
||||
const outerIcon = sidebarToggleOuter.querySelector('i');
|
||||
innerIcon.classList.remove('bi-list');
|
||||
innerIcon.classList.add('bi-x');
|
||||
outerIcon.classList.remove('bi-list');
|
||||
outerIcon.classList.add('bi-x');
|
||||
|
||||
function toggleSidebar() {
|
||||
sidebar.classList.toggle('collapsed');
|
||||
|
||||
// Change icons
|
||||
if (sidebar.classList.contains('collapsed')) {
|
||||
innerIcon.classList.remove('bi-x');
|
||||
innerIcon.classList.add('bi-list');
|
||||
outerIcon.classList.remove('bi-x');
|
||||
outerIcon.classList.add('bi-list');
|
||||
} else {
|
||||
innerIcon.classList.remove('bi-list');
|
||||
innerIcon.classList.add('bi-x');
|
||||
outerIcon.classList.remove('bi-list');
|
||||
outerIcon.classList.add('bi-x');
|
||||
}
|
||||
}
|
||||
|
||||
sidebarToggleInner.addEventListener('click', toggleSidebar);
|
||||
sidebarToggleOuter.addEventListener('click', toggleSidebar);
|
||||
|
||||
// Folders are now automatically expanded by PHP if they contain the active page
|
||||
|
||||
// Close other folders when opening a new one
|
||||
const folderToggles = document.querySelectorAll('.folder-toggle');
|
||||
folderToggles.forEach(toggle => {
|
||||
toggle.addEventListener('click', function(e) {
|
||||
const targetId = this.getAttribute('data-bs-target');
|
||||
const targetCollapse = document.querySelector(targetId);
|
||||
const isExpanded = this.getAttribute('aria-expanded') === 'true';
|
||||
|
||||
if (!isExpanded) {
|
||||
// Close all other folders
|
||||
if (!isExpanded && targetCollapse) {
|
||||
// Close all other folders first
|
||||
folderToggles.forEach(otherToggle => {
|
||||
if (otherToggle !== this) {
|
||||
const otherTargetId = otherToggle.getAttribute('data-bs-target');
|
||||
if (otherTargetId) {
|
||||
const otherCollapse = document.querySelector(otherTargetId);
|
||||
if (otherCollapse) {
|
||||
const bsCollapse = bootstrap.Collapse.getInstance(otherCollapse);
|
||||
if (bsCollapse) {
|
||||
bsCollapse.hide();
|
||||
} else {
|
||||
new bootstrap.Collapse(otherCollapse, {
|
||||
hide: true
|
||||
});
|
||||
}
|
||||
otherCollapse.classList.remove('show');
|
||||
otherToggle.setAttribute('aria-expanded', 'false');
|
||||
// Reset arrow
|
||||
const otherArrow = otherToggle.querySelector('.arrow');
|
||||
if (otherArrow) {
|
||||
otherArrow.style.transform = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Open this folder
|
||||
targetCollapse.classList.add('show');
|
||||
this.setAttribute('aria-expanded', 'true');
|
||||
// Rotate arrow
|
||||
const arrow = this.querySelector('.arrow');
|
||||
if (arrow) {
|
||||
arrow.style.transform = 'rotate(90deg)';
|
||||
}
|
||||
} else if (isExpanded && targetCollapse) {
|
||||
// Close this folder
|
||||
targetCollapse.classList.remove('show');
|
||||
this.setAttribute('aria-expanded', 'false');
|
||||
// Reset arrow
|
||||
const arrow = this.querySelector('.arrow');
|
||||
if (arrow) {
|
||||
arrow.style.transform = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
99
index.php
99
index.php
@ -212,6 +212,9 @@ class CodePressCMS {
|
||||
$body = preg_replace('/\*\*(.+?)\*\*/', '<strong>$1</strong>', $body);
|
||||
$body = preg_replace('/\*(.+?)\*/', '<em>$1</em>', $body);
|
||||
|
||||
// Auto-link page titles to existing content pages (before markdown link processing)
|
||||
$body = $this->autoLinkPageTitles($body);
|
||||
|
||||
// Convert Markdown links to HTML links
|
||||
$body = preg_replace('/\[([^\]]+)\]\(([^)]+)\)/', '<a href="$2">$1</a>', $body);
|
||||
|
||||
@ -231,6 +234,102 @@ class CodePressCMS {
|
||||
];
|
||||
}
|
||||
|
||||
private function autoLinkPageTitles($content) {
|
||||
// Get all available pages with their titles
|
||||
$pages = $this->getAllPageTitles();
|
||||
|
||||
foreach ($pages as $pagePath => $pageTitle) {
|
||||
// Create a pattern that matches the exact page title (case-insensitive)
|
||||
// Use word boundaries to avoid partial matches
|
||||
$pattern = '/\b' . preg_quote($pageTitle, '/') . '\b/i';
|
||||
|
||||
// Replace with link, but avoid linking inside existing links, headings, or markdown
|
||||
$replacement = function($matches) use ($pageTitle, $pagePath) {
|
||||
$text = $matches[0];
|
||||
|
||||
// Check if we're inside an existing link or markdown syntax
|
||||
if (preg_match('/\[.*?\]\(.*?\)/', $text) ||
|
||||
preg_match('/\[.*?\]:/', $text) ||
|
||||
preg_match('/<a[^>]*>/', $text) ||
|
||||
preg_match('/href=/', $text)) {
|
||||
return $text; // Don't link existing links
|
||||
}
|
||||
|
||||
return '<a href="?page=' . $pagePath . '" class="auto-link" title="Ga naar ' . htmlspecialchars($pageTitle) . '">' . $text . '</a>';
|
||||
};
|
||||
|
||||
$content = preg_replace_callback($pattern, $replacement, $content);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
return '<a href="?page=' . $pagePath . '" class="auto-link" title="Ga naar ' . htmlspecialchars($pageTitle) . '">' . $matches[0] . '</a>';
|
||||
};
|
||||
|
||||
$content = preg_replace_callback($pattern, $replacement, $content);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function getAllPageTitles() {
|
||||
$pages = [];
|
||||
$this->scanForPageTitles($this->config['content_dir'], '', $pages);
|
||||
return $pages;
|
||||
}
|
||||
|
||||
private function scanForPageTitles($dir, $prefix, &$pages) {
|
||||
if (!is_dir($dir)) return;
|
||||
|
||||
$items = scandir($dir);
|
||||
sort($items);
|
||||
|
||||
foreach ($items as $item) {
|
||||
if ($item[0] === '.') continue;
|
||||
|
||||
$path = $dir . '/' . $item;
|
||||
$relativePath = $prefix ? $prefix . '/' . $item : $item;
|
||||
|
||||
if (is_dir($path)) {
|
||||
$this->scanForPageTitles($path, $relativePath, $pages);
|
||||
} elseif (preg_match('/\.(md|php|html)$/', $item)) {
|
||||
$title = $this->extractPageTitle($path);
|
||||
if ($title && !empty(trim($title))) {
|
||||
$pagePath = preg_replace('/\.[^.]+$/', '', $relativePath);
|
||||
$pages[$pagePath] = $title;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function extractPageTitle($filePath) {
|
||||
$content = file_get_contents($filePath);
|
||||
$extension = pathinfo($filePath, PATHINFO_EXTENSION);
|
||||
|
||||
if ($extension === 'md') {
|
||||
// Extract first H1 from Markdown
|
||||
if (preg_match('/^#\s+(.+)$/m', $content, $matches)) {
|
||||
return trim($matches[1]);
|
||||
}
|
||||
} elseif ($extension === 'php') {
|
||||
// Extract title from PHP file
|
||||
if (preg_match('/\$title\s*=\s*["\']([^"\']+)["\']/', $content, $matches)) {
|
||||
return trim($matches[1]);
|
||||
}
|
||||
} elseif ($extension === 'html') {
|
||||
// Extract title from HTML file
|
||||
if (preg_match('/<title>(.*?)<\/title>/i', $content, $matches)) {
|
||||
return trim(strip_tags($matches[1]));
|
||||
}
|
||||
if (preg_match('/<h1[^>]*>(.*?)<\/h1>/i', $content, $matches)) {
|
||||
return trim(strip_tags($matches[1]));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function parsePHP($filePath) {
|
||||
ob_start();
|
||||
$title = 'Untitled';
|
||||
|
||||
74
public/.htaccess
Normal file
74
public/.htaccess
Normal file
@ -0,0 +1,74 @@
|
||||
# Security - Block access to sensitive files and directories
|
||||
<Files ~ "^\.">
|
||||
Order allow,deny
|
||||
Deny from all
|
||||
</Files>
|
||||
|
||||
<FilesMatch "\.(php|ini|log|conf|config)$">
|
||||
Order allow,deny
|
||||
Deny from all
|
||||
</FilesMatch>
|
||||
|
||||
# Block access to core directories
|
||||
<IfModule mod_authz_core.c>
|
||||
<RequireAll>
|
||||
Require all granted
|
||||
<RequireNone>
|
||||
Require all denied
|
||||
</RequireNone>
|
||||
</RequireAll>
|
||||
</IfModule>
|
||||
|
||||
# Directory protection
|
||||
<Directory ~ "^\.|/(config|templates|vendor|cache)/">
|
||||
Order allow,deny
|
||||
Deny from all
|
||||
</Directory>
|
||||
|
||||
# URL Routing - Route all requests to index.php
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
|
||||
# Set base directory
|
||||
RewriteBase /
|
||||
|
||||
# Block direct access to PHP files in content directory
|
||||
RewriteRule ^content/.*\.php$ - [F,L]
|
||||
|
||||
# Route all non-file/non-directory requests to index.php
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^(.*)$ index.php [QSA,L]
|
||||
|
||||
# Allow access to assets
|
||||
RewriteCond %{REQUEST_FILENAME} -f
|
||||
RewriteRule ^assets/.*$ - [L]
|
||||
|
||||
# Block direct access to all content files
|
||||
RewriteRule ^content/.*$ - [F,L]
|
||||
</IfModule>
|
||||
|
||||
# Security headers
|
||||
<IfModule mod_headers.c>
|
||||
Header always set X-Content-Type-Options nosniff
|
||||
Header always set X-Frame-Options DENY
|
||||
Header always set X-XSS-Protection "1; mode=block"
|
||||
Header always set Referrer-Policy "strict-origin-when-cross-origin"
|
||||
</IfModule>
|
||||
|
||||
# PHP settings
|
||||
<IfModule mod_php.c>
|
||||
php_flag display_errors Off
|
||||
php_flag log_errors On
|
||||
php_value error_log /var/log/php_errors.log
|
||||
php_value max_execution_time 30
|
||||
php_value memory_limit 128M
|
||||
php_value upload_max_filesize 10M
|
||||
php_value post_max_size 10M
|
||||
</IfModule>
|
||||
|
||||
# Default index file
|
||||
DirectoryIndex index.php
|
||||
|
||||
# Error handling
|
||||
ErrorDocument 404 /index.php
|
||||
@ -1,23 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="codepress-gradient-small" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#0d6efd;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#6610f2;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background circle -->
|
||||
<circle cx="8" cy="8" r="7.5" fill="url(#codepress-gradient-small)" stroke="#ffffff" stroke-width="0.5"/>
|
||||
|
||||
<!-- Code brackets -->
|
||||
<path d="M4 5 L3 6 L3 10 L4 11" stroke="#ffffff" stroke-width="1" fill="none" stroke-linecap="round"/>
|
||||
<path d="M12 5 L13 6 L13 10 L12 11" stroke="#ffffff" stroke-width="1" fill="none" stroke-linecap="round"/>
|
||||
|
||||
<!-- Code slash -->
|
||||
<path d="M7 4 L9 12" stroke="#ffffff" stroke-width="1" stroke-linecap="round"/>
|
||||
|
||||
<!-- Press dots -->
|
||||
<circle cx="6" cy="13" r="0.75" fill="#ffffff"/>
|
||||
<circle cx="8" cy="13" r="0.75" fill="#ffffff"/>
|
||||
<circle cx="10" cy="13" r="0.75" fill="#ffffff"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB |
@ -1,23 +0,0 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="codepress-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#0d6efd;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#6610f2;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background circle -->
|
||||
<circle cx="16" cy="16" r="15" fill="url(#codepress-gradient)" stroke="#ffffff" stroke-width="1"/>
|
||||
|
||||
<!-- Code brackets -->
|
||||
<path d="M8 10 L6 12 L6 20 L8 22" stroke="#ffffff" stroke-width="2" fill="none" stroke-linecap="round"/>
|
||||
<path d="M24 10 L26 12 L26 20 L24 22" stroke="#ffffff" stroke-width="2" fill="none" stroke-linecap="round"/>
|
||||
|
||||
<!-- Code slash -->
|
||||
<path d="M14 8 L18 24" stroke="#ffffff" stroke-width="2" stroke-linecap="round"/>
|
||||
|
||||
<!-- Press dots -->
|
||||
<circle cx="12" cy="26" r="1.5" fill="#ffffff"/>
|
||||
<circle cx="16" cy="26" r="1.5" fill="#ffffff"/>
|
||||
<circle cx="20" cy="26" r="1.5" fill="#ffffff"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1018 B |
@ -1,39 +0,0 @@
|
||||
# Het Cruciale Belang van Kennis boven Aantallen in de Werkkracht
|
||||
|
||||
In de dynamische wereld van vandaag wordt vaak de fout gemaakt om te denken dat het simpelweg uitbreiden van het personeelsbestand automatisch zal leiden tot verbeterde efficiëntie en productiviteit binnen een organisatie. Deze misvatting miskent echter het werkelijke knelpunt: een gebrek aan diepgaande kennis en expertise bij het bestaande personeel. Het is niet een kwestie van meer mensen aannemen, maar eerder van het ontwikkelen van beter opgeleide en gekwalificeerde werknemers.
|
||||
|
||||
## Problemen door gebrek aan kennis
|
||||
|
||||
### 1. Foutieve informatie en besluitvorming
|
||||
|
||||
Stel je voor dat een bedrijf een projectteam heeft samengesteld dat verantwoordelijk is voor het lanceren van een nieuw product. Als de teamleden niet beschikken over de vereiste kennis van markttrends, consumentengedrag en concurrentieanalyse, kunnen ze beslissingen nemen die gebaseerd zijn op onvolledige of verouderde informatie.
|
||||
|
||||
Dit kan resulteren in het nemen van verkeerde beslissingen, zoals het produceren van een product dat niet aansluit op de behoeften van de markt, met als gevolg financiële verliezen voor het bedrijf.
|
||||
|
||||
### 2. Behoefte aan constante correcties
|
||||
|
||||
Stel je voor dat een klantenserviceteam te maken heeft met klachten over een nieuw gelanceerd product. Als de medewerkers niet goed zijn opgeleid over de specificaties en eigenschappen van het product, zullen ze moeite hebben om de klachten van klanten adequaat te begrijpen en op te lossen.
|
||||
|
||||
Dit kan leiden tot een situatie waarin meer ervaren medewerkers voortdurend de tijd moeten nemen om de fouten van minder goed opgeleide collega's te corrigeren, wat leidt tot inefficiëntie en frustratie bij zowel medewerkers als klanten.
|
||||
|
||||
### 3. Vertraagde processen
|
||||
|
||||
In een technologiebedrijf kan een gebrek aan kennis over de nieuwste software en systemen leiden tot vertragingen bij het uitvoeren van projecten. Stel je voor dat programmeurs niet op de hoogte zijn van de nieuwste programmeertools en -methoden.
|
||||
|
||||
Dit kan resulteren in een langzamer ontwikkelingsproces en uiteindelijk een vertraagde productlancering, waardoor het bedrijf een concurrentievoordeel verliest.
|
||||
|
||||
## Oplossing: Investeren in kennisontwikkeling
|
||||
|
||||
Om deze problemen effectief aan te pakken en de algehele prestaties van een organisatie te verbeteren, is het van essentieel belang om te investeren in de ontwikkeling van de kennis en vaardigheden van het bestaande personeel.
|
||||
|
||||
Door middel van regelmatige trainingssessies, bijscholing en professionele ontwikkelingsprogramma's kunnen werknemers worden uitgerust met de nodige expertise om hun taken effectief en efficiënt uit te voeren.
|
||||
|
||||
## Focus op kwaliteit boven kwantiteit
|
||||
|
||||
In plaats van te streven naar een kwantitatieve uitbreiding van het personeelsbestand, moet de focus liggen op het versterken van de kwaliteit van het personeel door middel van continue leerinitiatieven.
|
||||
|
||||
Dit zal niet alleen leiden tot betere prestaties en productiviteit, maar ook tot een meer bevredigende werkomgeving waarin werknemers zich gewaardeerd en gesteund voelen in hun professionele ontwikkeling.
|
||||
|
||||
## Conclusie
|
||||
|
||||
Kortom, het is niet het aantal werknemers dat telt, maar de kwaliteit van hun kennis en vaardigheden die bepalend zijn voor het succes van een organisatie. Door te investeren in de ontwikkeling van het bestaande personeel kunnen organisaties effectiever opereren en beter inspelen op de uitdagingen van de moderne zakelijke omgeving.
|
||||
@ -1,46 +0,0 @@
|
||||
# De Toekomst van ICT: Hoe Open Source Software de Werkvloer Transformeert
|
||||
|
||||
In onze steeds digitaler wordende samenleving zijn medewerkers digitaal vaardiger geworden en verwachten zij dat de ICT-voorzieningen op kantoor en de werkvloer aansluiten bij hun behoeften. Helaas lopen traditionele ICT-afdelingen hier vaak op achter, wat leidt tot een kloof tussen de beschikbare technologie en de wensen van de medewerkers.
|
||||
|
||||
## Beperkingen van traditionele ICT-oplossingen
|
||||
|
||||
Traditionele ICT-afdelingen bieden doorgaans standaard softwarepakketten aan, zoals Office-suites en omvangrijke HRM- of communicatiesoftware. Hoewel deze tools een basisfunctionaliteit bieden, schieten ze vaak tekort in flexibiliteit en maatwerk.
|
||||
|
||||
Technisch onderlegde medewerkers weten soms meer uit deze pakketten te halen, maar voor velen zijn de mogelijkheden binnen de bestaande infrastructuur beperkt. Dit leidt tot frustratie en inefficiëntie op de werkvloer.
|
||||
|
||||
## De dynamiek van een digitale samenleving
|
||||
|
||||
Onze maatschappij is dynamisch en past zich snel aan nieuwe ICT-mogelijkheden aan. Wanneer ICT-afdelingen vasthouden aan rigide systemen, ontstaan er hiaten tussen hoe mensen willen werken en de beschikbare technologie.
|
||||
|
||||
De werkvloer wordt hierdoor leidend in de vraag naar ICT-oplossingen, terwijl de ICT-afdeling zou moeten anticiperen op deze behoeften. Maatwerk wordt vaak gezien als een dure oplossing, waardoor men kiest voor grote, minder flexibele softwarepakketten die alleen na dure trainingen volledig benut kunnen worden.
|
||||
|
||||
Hierdoor moeten werknemers zich aanpassen aan de software, in plaats van dat de software aansluit bij de werkprocessen en het karakter van het bedrijf.
|
||||
|
||||
## De kracht van open standaarden en open-source oplossingen
|
||||
|
||||
Een mogelijke oplossing voor dit probleem is het omarmen van open standaarden en open-source oplossingen. Deze benadering maakt kantoorautomatisering dynamischer en beter aanpasbaar aan de behoeften van de samenleving.
|
||||
|
||||
Open-source software biedt flexibiliteit en controle, waardoor bedrijven hun workflows kunnen optimaliseren zonder afhankelijk te zijn van dure licenties of beperkte functionaliteiten.
|
||||
|
||||
## Voorbeelden van open-source kantoorsoftware
|
||||
|
||||
Er zijn diverse open-source tools beschikbaar die kunnen bijdragen aan een flexibelere werkomgeving:
|
||||
|
||||
### LibreOffice
|
||||
Een volwaardig kantoorsoftwarepakket dat een uitstekend alternatief biedt voor commerciële producten.
|
||||
|
||||
### Nextcloud
|
||||
Een cloudoplossing voor documentbeheer en samenwerking, die bedrijven in staat stelt hun eigen cloudomgeving te beheren.
|
||||
|
||||
### Thunderbird
|
||||
Een open-source e-mailclient die flexibiliteit en controle biedt over e-mailbeheer.
|
||||
|
||||
## Integratie van ICT en bedrijfsafdelingen
|
||||
|
||||
Om de kloof tussen ICT en de werkvloer te overbruggen, is het essentieel dat ICT-afdelingen nauwer samenwerken met verschillende bedrijfsafdelingen. Door gezamenlijk te bepalen welke tools en systemen het beste aansluiten bij de werkprocessen, kunnen organisaties efficiënter en effectiever opereren.
|
||||
|
||||
Deze integratie bevordert niet alleen de productiviteit, maar ook de tevredenheid en betrokkenheid van medewerkers.
|
||||
|
||||
## Conclusie
|
||||
|
||||
In een tijd waarin digitalisering en flexibiliteit centraal staan, is het cruciaal dat ICT-afdelingen zich aanpassen aan de behoeften van de werkvloer. Door open standaarden en open-source oplossingen te omarmen en een nauwe samenwerking met andere afdelingen te zoeken, kunnen organisaties een dynamische en efficiënte werkomgeving creëren die aansluit bij de moderne samenleving.
|
||||
@ -1,63 +0,0 @@
|
||||
# Standaardisatie: Het Belang ervan en de Tegenstelling tussen Commerciële Bedrijven en Open-Source Gemeenschappen
|
||||
|
||||
Standaardisatie is een cruciaal concept in de moderne wereld, met name in de technologie-industrie. Het verwijst naar het vaststellen van gemeenschappelijke normen en specificaties voor producten, processen, protocollen en systemen. Deze normen dienen als de ruggengraat van interoperabiliteit en efficiëntie in verschillende sectoren, zoals informatietechnologie, telecommunicatie, gezondheidszorg en meer.
|
||||
|
||||
## Waarom is Standaardisatie Belangrijk?
|
||||
|
||||
### 1. Interoperabiliteit
|
||||
|
||||
Standaardisatie zorgt ervoor dat verschillende systemen, producten en software met elkaar kunnen communiceren en samenwerken. Dit vergemakkelijkt de uitwisseling van informatie en diensten tussen verschillende leveranciers en platforms.
|
||||
|
||||
Bijvoorbeeld, standaardisatie van internetprotocollen maakt het mogelijk dat verschillende apparaten en websites wereldwijd met elkaar kunnen communiceren.
|
||||
|
||||
### 2. Efficiëntie
|
||||
|
||||
Standaardisatie kan de efficiëntie verbeteren door het verminderen van redundantie en complexiteit. Wanneer bedrijven en organisaties dezelfde normen volgen, kunnen ze resources beter beheren en kosten besparen.
|
||||
|
||||
Het voorkomt bijvoorbeeld dat ze verschillende aangepaste oplossingen moeten ontwikkelen voor vergelijkbare taken.
|
||||
|
||||
### 3. Veiligheid en Betrouwbaarheid
|
||||
|
||||
Standaardisatie kan de veiligheid en betrouwbaarheid van producten en systemen verbeteren. Gemeenschappelijke normen stellen minimumeisen vast voor bijvoorbeeld cybersecurity en kwaliteitscontrole, wat de bescherming van gegevens en de integriteit van systemen bevordert.
|
||||
|
||||
### 4. Innovatie en Concurrentie
|
||||
|
||||
Standaardisatie kan innovatie stimuleren door bedrijven aan te moedigen nieuwe technologieën te ontwikkelen die aan de normen voldoen. Het kan ook de concurrentie bevorderen, omdat het de toetreding van nieuwe spelers vergemakkelijkt door hen een gemeenschappelijke basis te bieden om op voort te bouwen.
|
||||
|
||||
## Waarom Commerciële Bedrijven van Standaardisatie Afwijken
|
||||
|
||||
Commerciële bedrijven hebben soms redenen om van standaardisatie af te wijken, vooral als ze streven naar concurrentievoordeel, vendor lock-in of maximale winst.
|
||||
|
||||
### 1. Vendor Lock-In
|
||||
|
||||
Sommige bedrijven willen klanten aan zich binden door proprietaire technologieën te gebruiken die niet compatibel zijn met die van andere leveranciers. Dit creëert een zogenaamde "vendor lock-in", waarbij klanten moeilijk kunnen overstappen naar concurrenten.
|
||||
|
||||
### 2. Concurrentievoordeel
|
||||
|
||||
Bedrijven kunnen proberen een uniek concurrentievoordeel te behouden door niet-conforme technologieën of protocollen te gebruiken. Dit kan tijdelijk voordelig zijn, maar kan de algemene interoperabiliteit belemmeren.
|
||||
|
||||
### 3. Winstmaximalisatie
|
||||
|
||||
Sommige bedrijven willen maximale winst behalen en zien geen voordeel in het delen van kennis of het bevorderen van open normen.
|
||||
|
||||
## Waarom Open-Source Gemeenschappen Standaardisatie Omarmen
|
||||
|
||||
Open-source gemeenschappen, daarentegen, hebben vaak een sterke affiniteit met standaardisatie vanwege de voordelen die het biedt aan samenwerking en innovatie.
|
||||
|
||||
### 1. Samenwerking en Gedeelde Waarden
|
||||
|
||||
Open-source gemeenschappen gedijen op samenwerking, delen en transparantie. Het omarmen van standaardisatie past goed bij deze waarden, omdat het de basis legt voor gemeenschappelijke ontwikkeling en kennisuitwisseling.
|
||||
|
||||
### 2. Toegankelijkheid en Gelijkheid
|
||||
|
||||
Open standaarden bevorderen toegankelijkheid en gelijkheid, omdat ze de drempels voor deelname en concurrentie verlagen. Iedereen kan bijdragen aan open-source projecten en gebruikmaken van de resulterende standaarden.
|
||||
|
||||
### 3. Innovatie en Duurzaamheid
|
||||
|
||||
Open-source projecten kunnen innovatie stimuleren door een bredere groep mensen en organisaties bij het ontwikkelingsproces te betrekken. Dit leidt vaak tot duurzamere oplossingen die langer relevant blijven.
|
||||
|
||||
## Conclusie
|
||||
|
||||
Kortom, standaardisatie is een essentieel concept dat de basis vormt voor interoperabiliteit, efficiëntie en innovatie in verschillende industrieën. Terwijl commerciële bedrijven soms van standaardisatie kunnen afwijken om hun eigen belangen te behartigen, omarmen open-source gemeenschappen vaak actief standaardisatie vanwege de voordelen van samenwerking, toegankelijkheid en duurzaamheid die het met zich meebrengt.
|
||||
|
||||
Het evenwicht tussen deze twee benaderingen speelt een cruciale rol bij het vormgeven van de technologische wereld waarin we leven.
|
||||
@ -1,33 +0,0 @@
|
||||
# Welkom, ik ben Edwin
|
||||
|
||||
**Innovatie en Technologische Passie: Het Inspirerende Verhaal van Edwin Noorlander**
|
||||
|
||||
Mijn naam is Edwin Noorlander, en ik wil graag mijn inspirerende reis met jullie delen. Mijn leven is doordrenkt van innovatie en een onwankelbare omhelzing van technologie als mijn leidraad naar waar ik nu ben. Mijn verhaal is er een van vastberadenheid, autodidactisch leren en een diepe liefde voor complexiteit.
|
||||
|
||||
## Uitdagingen als Drijfveer
|
||||
|
||||
Laat me beginnen door te benadrukken dat mijn pad niet zonder uitdagingen is geweest. Van jongs af aan heb ik dyslexie en ADHD ervaren, wat mijn leerproces op traditionele scholen zeker niet eenvoudig maakte. Maar deze obstakels hebben me nooit ontmoedigd. In plaats daarvan dienden ze als drijfveer om mijn eigen weg te vinden in de wereld van technologie.
|
||||
|
||||
## Autodidactisch Leren
|
||||
|
||||
Ik heb altijd technologie als mijn roeping beschouwd, en dit dreef me om een unieke weg van zelfontdekking te bewandelen. In tegenstelling tot traditionele onderwijsroutes koos ik voor praktische hands-on ervaringen, en ondanks de uitdagingen die dyslexie en ADHD met zich meebrengen, leerde ik mezelf programmeren in diverse programmeertalen, waaronder ASM, C/C++, Java, PHP, HTML, JavaScript en SCSS. Deze diversiteit aan talen stelde me in staat om complexe problemen op te lossen en innovatieve oplossingen te creëren.
|
||||
|
||||
## Micro-elektronica en Ruimtevaart
|
||||
|
||||
Mijn passie reikte verder dan alleen software; ik dook diep in de wereld van micro-elektronica, waar ik leerde om microprocessors te programmeren en sensoren en actuatoren aan te sturen. Dit gaf me de mogelijkheid om fysieke systemen te bouwen en mijn ideeën tot leven te brengen.
|
||||
|
||||
Naast mijn technologische avonturen was ik altijd gefascineerd door astronomie en ruimtevaart, waar ik mijn leergierigheid en passie voor ontdekking voedde.
|
||||
|
||||
## Huidige Werk
|
||||
|
||||
Momenteel werk ik als adviseur bij de overheid, waar ik mijn kennis en ervaring toepas om advies te geven over ICT-infrastructuur met betrekking tot digitaal leren. Dit stelt me in staat om bij te dragen aan de groei en ontwikkeling van de samenleving door middel van technologie.
|
||||
|
||||
## Missie en Doel
|
||||
|
||||
Mijn ultieme doel is om anderen te inspireren die zich in een vergelijkbare positie bevinden, met een handicap zoals dyslexie en ADHD, om zichzelf te blijven ontwikkelen en te leren wat ze willen leren. Ik geloof dat het nooit te laat is om te beginnen met leren en dat er altijd manieren zijn om je doelen te bereiken, zelfs als de weg er naartoe uitdagend lijkt.
|
||||
|
||||
Daarom heb ik deze blog gecreëerd. Hier wil ik graag mijn kennis en ervaringen delen en anderen aanmoedigen om hun passie te volgen en te blijven leren, ongeacht welke obstakels zich voordoen.
|
||||
|
||||
Dankjewel voor het bezoeken van mijn blog, en ik hoop je snel weer terug te zien!
|
||||
|
||||
Mijn naam is Edwin Noorlander, en ik ben vastbesloten om de kracht van innovatie en technologie te blijven omarmen.
|
||||
@ -1,37 +0,0 @@
|
||||
# CodePress Blog
|
||||
|
||||
Welkom op de persoonlijke blog van Edwin Noorlander. Hier deel ik mijn gedachten, ervaringen en kennis over technologie, open-source software en digitale transformatie.
|
||||
|
||||
## Categorieën
|
||||
|
||||
### Over Mij
|
||||
- [Welkom, ik ben Edwin](?page=blog/over-mij/welkom) - Mijn persoonlijke verhaal en achtergrond
|
||||
|
||||
### Open Source
|
||||
- [De Toekomst van ICT](?page=blog/open-source/de-toekomst-van-ict) - Hoe open source software de werkvloer transformeert
|
||||
- [Standaardisatie](?page=blog/open-source/standaardisatie) - Het belang van standaarden en de tegenstelling tussen commerciële bedrijven en open-source gemeenschappen
|
||||
|
||||
### Leren & Ontwikkeling
|
||||
- [Kennis boven Aantallen](?page=blog/leren/kennis-boven-aantallen) - Het cruciale belang van kennis boven personeelsaantallen in de werkkracht
|
||||
|
||||
### ICT & Bedrijfsvoering
|
||||
- [Excel als Database](?page=blog/ict/excel-als-database) - Waarom grote bedrijven en overheden Access niet toestaan
|
||||
|
||||
### Micro-electronica
|
||||
- [Wat is Arduino](?page=blog/micro-electronica/wat-is-arduino) - Open-source elektronisch platform voor interactieve projecten
|
||||
- [Leren gaat niet over perfectie](?page=blog/micro-electronica/leren-gaat-niet-over-perfectie) - Passie voor electronica en open-source hardware/software
|
||||
|
||||
### Hardware & ICT
|
||||
- [De ware aard van ICT](?page=blog/hardware/de-ware-aard-van-ict) - Meer dan alleen computers en software
|
||||
|
||||
### Retro Gaming
|
||||
- [Commodore 64](?page=blog/retro-gaming/commodore-64) - Een les in creativiteit en innovatie in de game-industrie
|
||||
|
||||
### Linux & Open Source
|
||||
- [Open Source Voorbeelden](?page=blog/linux/open-source-voorbeelden) - Vijf voorbeelden van open-source software in gebruik bij commerciële bedrijven
|
||||
|
||||
## Over Edwin Noorlander
|
||||
|
||||
Ik ben adviseur bij de overheid met een passie voor technologie, innovatie en open-source software. Met een achtergrond in micro-elektronica en diverse programmeertalen, deel ik graag mijn kennis en ervaringen om anderen te inspireren.
|
||||
|
||||
**Meer weten?** Bezoek mijn [persoonlijke website](https://noorlander.info) of volg mijn projecten op [GitLab](https://git.noorlander.info).
|
||||
1
public/engine
Symbolic link
1
public/engine
Symbolic link
@ -0,0 +1 @@
|
||||
../engine
|
||||
@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
require_once 'config.php';
|
||||
require_once __DIR__ . '/../engine/core/config.php';
|
||||
|
||||
$config = include 'config.php';
|
||||
$config = include __DIR__ . '/../engine/core/config.php';
|
||||
|
||||
class CodePressCMS {
|
||||
private $config;
|
||||
@ -119,6 +119,21 @@ class CodePressCMS {
|
||||
} elseif (file_exists($filePath . '.html')) {
|
||||
$actualFilePath = $filePath . '.html';
|
||||
$result = $this->parseHTML(file_get_contents($actualFilePath));
|
||||
} elseif (is_dir($filePath)) {
|
||||
// Check for index files in directory
|
||||
if (file_exists($filePath . '/index.md')) {
|
||||
$actualFilePath = $filePath . '/index.md';
|
||||
$result = $this->parseMarkdown(file_get_contents($actualFilePath));
|
||||
} elseif (file_exists($filePath . '/index.php')) {
|
||||
$actualFilePath = $filePath . '/index.php';
|
||||
$result = $this->parsePHP($actualFilePath);
|
||||
} elseif (file_exists($filePath . '/index.html')) {
|
||||
$actualFilePath = $filePath . '/index.html';
|
||||
$result = $this->parseHTML(file_get_contents($actualFilePath));
|
||||
} else {
|
||||
// Generate directory listing
|
||||
return $this->generateDirectoryListing($filePath, $page);
|
||||
}
|
||||
} elseif (file_exists($filePath)) {
|
||||
$actualFilePath = $filePath;
|
||||
$extension = pathinfo($filePath, PATHINFO_EXTENSION);
|
||||
@ -166,6 +181,62 @@ class CodePressCMS {
|
||||
return round($bytes, 2) . ' ' . $units[$pow];
|
||||
}
|
||||
|
||||
private function generateDirectoryListing($dirPath, $urlPath) {
|
||||
$title = ucfirst(basename($dirPath));
|
||||
$content = '<div class="row">';
|
||||
|
||||
$items = scandir($dirPath);
|
||||
sort($items);
|
||||
|
||||
foreach ($items as $item) {
|
||||
if ($item[0] === '.') continue;
|
||||
|
||||
$path = $dirPath . '/' . $item;
|
||||
$relativePath = $urlPath . '/' . $item;
|
||||
$itemName = ucfirst(pathinfo($item, PATHINFO_FILENAME));
|
||||
|
||||
if (is_dir($path)) {
|
||||
$content .= '<div class="col-md-6 mb-4">';
|
||||
$content .= '<div class="card h-100 border-0 rounded-0 bg-light">';
|
||||
$content .= '<div class="card-body">';
|
||||
$content .= '<h3 class="h5 card-title"><a href="?page=' . $relativePath . '" class="text-decoration-none text-dark"><i class="bi bi-folder me-2"></i> ' . $itemName . '</a></h3>';
|
||||
$content .= '</div></div></div>';
|
||||
} elseif (preg_match('/\.(md|php|html)$/', $item)) {
|
||||
// Remove extension from URL for cleaner links
|
||||
$cleanPath = preg_replace('/\.[^.]+$/', '', $relativePath);
|
||||
|
||||
// Get preview content
|
||||
$preview = '';
|
||||
$fileContent = file_get_contents($path);
|
||||
|
||||
// Extract title if possible
|
||||
$fileTitle = $itemName;
|
||||
if (preg_match('/^#\s+(.+)$/m', $fileContent, $matches)) {
|
||||
$fileTitle = trim($matches[1]);
|
||||
}
|
||||
|
||||
// Extract preview text (first paragraph)
|
||||
$fileContent = strip_tags($this->parseMarkdown($fileContent)['content']);
|
||||
$preview = substr($fileContent, 0, 150) . '...';
|
||||
|
||||
$content .= '<div class="col-md-6 mb-4">';
|
||||
$content .= '<div class="card h-100 border rounded-0">';
|
||||
$content .= '<div class="card-body">';
|
||||
$content .= '<h3 class="h5 card-title"><a href="?page=' . $cleanPath . '" class="text-decoration-none text-primary">' . $fileTitle . '</a></h3>';
|
||||
$content .= '<p class="card-text text-muted small">' . $preview . '</p>';
|
||||
$content .= '<a href="?page=' . $cleanPath . '" class="btn btn-sm btn-outline-primary rounded-0">Lees meer</a>';
|
||||
$content .= '</div></div></div>';
|
||||
}
|
||||
}
|
||||
|
||||
$content .= '</div>';
|
||||
|
||||
return [
|
||||
'title' => $title,
|
||||
'content' => $content
|
||||
];
|
||||
}
|
||||
|
||||
private function getSearchResults() {
|
||||
$query = $_GET['search'];
|
||||
$content = '<h2>Search Results for: "' . htmlspecialchars($query) . '"</h2>';
|
||||
@ -320,6 +391,11 @@ class CodePressCMS {
|
||||
if ($i === count($parts) - 1) {
|
||||
$breadcrumb .= '<li class="breadcrumb-item active">' . $title . '</li>';
|
||||
} else {
|
||||
// Check if directory has index file
|
||||
$dirPath = $this->config['content_dir'] . '/' . $path;
|
||||
$hasIndex = file_exists($dirPath . '/index.md') || file_exists($dirPath . '/index.php') || file_exists($dirPath . '/index.html');
|
||||
|
||||
// Always make breadcrumb items clickable, CMS will generate index if missing
|
||||
$breadcrumb .= '<li class="breadcrumb-item"><a href="?page=' . $path . '">' . $title . '</a></li>';
|
||||
}
|
||||
}
|
||||
@ -361,5 +437,13 @@ class CodePressCMS {
|
||||
}
|
||||
}
|
||||
|
||||
// Block direct access to content files
|
||||
$requestUri = $_SERVER['REQUEST_URI'] ?? '';
|
||||
if (strpos($requestUri, '/content/') !== false) {
|
||||
http_response_code(403);
|
||||
echo '<h1>403 - Forbidden</h1><p>Direct access to content files is not allowed.</p>';
|
||||
exit;
|
||||
}
|
||||
|
||||
$cms = new CodePressCMS($config);
|
||||
$cms->render();
|
||||
52
public/router.php
Normal file
52
public/router.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
// Router file for PHP development server to handle security and static files
|
||||
|
||||
$requestUri = $_SERVER['REQUEST_URI'];
|
||||
$parsedUrl = parse_url($requestUri);
|
||||
$path = $parsedUrl['path'];
|
||||
|
||||
// Block direct access to content directory
|
||||
if (strpos($path, '/content/') === 0) {
|
||||
http_response_code(403);
|
||||
echo '<h1>403 - Forbidden</h1><p>Direct access to content files is not allowed.</p>';
|
||||
return true;
|
||||
}
|
||||
|
||||
// Block access to sensitive files
|
||||
$sensitiveFiles = ['.htaccess', 'config.php'];
|
||||
foreach ($sensitiveFiles as $file) {
|
||||
if (basename($path) === $file && dirname($path) === '/') {
|
||||
http_response_code(403);
|
||||
echo '<h1>403 - Forbidden</h1><p>Access to this file is not allowed.</p>';
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Serve static files from engine/assets
|
||||
if (strpos($path, '/engine/') === 0) {
|
||||
$filePath = __DIR__ . $path;
|
||||
if (file_exists($filePath)) {
|
||||
// Set appropriate content type
|
||||
$extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
|
||||
$mimeTypes = [
|
||||
'css' => 'text/css',
|
||||
'js' => 'application/javascript',
|
||||
'svg' => 'image/svg+xml',
|
||||
'woff' => 'font/woff',
|
||||
'woff2' => 'font/woff2',
|
||||
'ttf' => 'font/ttf'
|
||||
];
|
||||
|
||||
if (isset($mimeTypes[$extension])) {
|
||||
header('Content-Type: ' . $mimeTypes[$extension]);
|
||||
}
|
||||
|
||||
// Serve the file
|
||||
readfile($filePath);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Route all other requests to index.php
|
||||
include __DIR__ . '/index.php';
|
||||
return true;
|
||||
1
server.log
Normal file
1
server.log
Normal file
@ -0,0 +1 @@
|
||||
[Wed Nov 19 17:58:28 2025] Failed to listen on localhost:8080 (reason: Address already in use)
|
||||
@ -42,12 +42,43 @@
|
||||
border-right: 1px solid #dee2e6;
|
||||
overflow-y: auto;
|
||||
flex-shrink: 0;
|
||||
transition: transform 0.3s ease;
|
||||
position: relative;
|
||||
z-index: 1000;
|
||||
}
|
||||
.sidebar.collapsed {
|
||||
transform: translateX(-250px);
|
||||
}
|
||||
.sidebar-toggle {
|
||||
position: fixed;
|
||||
top: 80px;
|
||||
left: 10px;
|
||||
z-index: 1001;
|
||||
background-color: #0d6efd;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.sidebar-toggle:hover {
|
||||
background-color: #0a58ca;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
.sidebar-toggle.shifted {
|
||||
left: 270px;
|
||||
}
|
||||
.main-content.shifted {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
transition: margin-left 0.3s ease;
|
||||
}
|
||||
|
||||
.folder-toggle {
|
||||
@ -155,6 +186,26 @@
|
||||
font-size: 0.9rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
.site-info a {
|
||||
color: #0d6efd;
|
||||
text-decoration: none;
|
||||
}
|
||||
.site-info a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.auto-link {
|
||||
color: #0d6efd;
|
||||
text-decoration: none;
|
||||
border-bottom: 2px dashed #0d6efd;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.auto-link:hover {
|
||||
color: #0a58ca;
|
||||
text-decoration: none;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: #0a58ca;
|
||||
}
|
||||
.search-form {
|
||||
max-width: 300px;
|
||||
}
|
||||
@ -211,7 +262,10 @@
|
||||
|
||||
<div class="main-wrapper">
|
||||
<div class="content-wrapper">
|
||||
<nav class="sidebar">
|
||||
<button class="sidebar-toggle" id="sidebarToggle">
|
||||
<i class="bi bi-list"></i>
|
||||
</button>
|
||||
<nav class="sidebar" id="sidebar">
|
||||
<div class="pt-3">
|
||||
<ul class="nav flex-column">
|
||||
{{menu}}
|
||||
@ -242,7 +296,7 @@
|
||||
{{file_info}}
|
||||
</div>
|
||||
<div class="site-info">
|
||||
<small class="text-muted">Powered by CodePress CMS</small>
|
||||
<small class="text-muted">Powered by <a href="https://git.noorlander.info/E.Noorlander/CodePress.git" target="_blank" rel="noopener">CodePress CMS</a></small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -253,6 +307,27 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Sidebar toggle functionality
|
||||
const sidebarToggle = document.getElementById('sidebarToggle');
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const mainContent = document.querySelector('.main-content');
|
||||
|
||||
sidebarToggle.addEventListener('click', function() {
|
||||
sidebar.classList.toggle('collapsed');
|
||||
mainContent.classList.toggle('shifted');
|
||||
sidebarToggle.classList.toggle('shifted');
|
||||
|
||||
// Change icon
|
||||
const icon = this.querySelector('i');
|
||||
if (sidebar.classList.contains('collapsed')) {
|
||||
icon.classList.remove('bi-list');
|
||||
icon.classList.add('bi-chevron-right');
|
||||
} else {
|
||||
icon.classList.remove('bi-chevron-right');
|
||||
icon.classList.add('bi-list');
|
||||
}
|
||||
});
|
||||
|
||||
// Open folders that contain the current active page
|
||||
const activeLink = document.querySelector('.nav-link.active');
|
||||
if (activeLink) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user