Initial commit: CodePress CMS with blog content from noorlander.info

- Complete PHP CMS with Bootstrap 5
- Multi-format content support (MD/PHP/HTML)
- Dynamic navigation with collapsible folders
- Search functionality with snippets
- WCAG compliant design
- Blog content imported from Edwin Noorlander's personal blog
- README with project background and credits
This commit is contained in:
Edwin Noorlander 2025-11-19 13:41:28 +01:00
commit 4d3c3391f7
22 changed files with 1201 additions and 0 deletions

80
README.md Normal file
View File

@ -0,0 +1,80 @@
# CodePress CMS
Een lichtgewicht PHP Content Management System gebouwd met Bootstrap en Mustache-style templating.
## Project Achtergrond
Dit project is ontwikkeld in opdracht van **Edwin Noorlander** als een demonstratie van een modern, file-based CMS. De opdracht was om een eenvoudig maar krachtig systeem te creëren dat content beheert via bestanden in plaats van een database.
## Ontwikkelingsproces
### Technische Keuzes
- **PHP 8.4+** voor server-side logica
- **Bootstrap 5** voor responsive design
- **File-based content storage** voor eenvoudig beheer
- **Markdown en PHP support** voor flexibele content
- **SVG iconen** voor scherpe graphics
### Iteratieve Ontwikkeling
Het project is stapsgewijs opgebouwd met continue feedback:
1. **Basis structuur** - Configuratie en templating systeem
2. **Content parsing** - Markdown, PHP en HTML ondersteuning
3. **Navigatie** - Dynamische menu generatie met hiërarchie
4. **Zoekfunctionaliteit** - Volledige tekst doorzoeking van content
5. **UI/UX verbeteringen** - Progressieve achtergrondkleuren, collapsible mappen
6. **Toegankelijkheid** - WCAG compliant contrast en navigatie
7. **Footer met metadata** - Bestandsinformatie en aanmaak/bewerkingsdatums
### Kernfeatures
- ✅ **Multi-format content**: Markdown, PHP, HTML bestanden
- ✅ **Dynamische navigatie**: Automatische menu generatie uit directory structuur
- ✅ **Zoekfunctie**: Volledige tekst doorzoeking met snippets
- ✅ **Kruimelpad**: Navigatie pad indicatie
- ✅ **Collapsible mappen**: Accordion-style navigatie
- ✅ **WCAG compliant**: Goede contrast ratio's en toegankelijkheid
- ✅ **Responsive design**: Werkt op alle schermformaten
- ✅ **File metadata**: Aanmaak- en bewerkingsdatums in footer
## Installatie
1. Zorg dat PHP 8.4+ geïnstalleerd is
2. Clone dit repository
3. Start de development server:
```bash
php -S localhost:8000
```
4. Bezoek `http://localhost:8000` in je browser
## Content Beheer
- Plaats content bestanden in de `content/` map
- Gebruik `.md`, `.php` of `.html` extensies
- De menu structuur volgt automatisch de directory hiërarchie
- Submappen worden genest in de navigatie weergegeven
## Project Structuur
```
codepress/
├── assets/ # Iconen en static files
├── content/ # Content bestanden (MD/PHP/HTML)
├── templates/ # HTML templates
├── config.php # Site configuratie
├── index.php # Hoofd applicatie logica
└── README.md # Deze documentatie
```
## Opdrachtgever
**Edwin Noorlander**
- Persoonlijke blog: [https://noorlander.info](https://noorlander.info)
- Git repository: [https://git.noorlander.info/E.Noorlander/CodePress.git](https://git.noorlander.info/E.Noorlander/CodePress.git)
## Licentie
Dit project is ontwikkeld als onderdeel van een opdracht en valt onder de voorwaarden zoals overeengekomen met de opdrachtgever.
---
*Gebouwd met ❤️ en PHP*

23
assets/favicon.svg Normal file
View File

@ -0,0 +1,23 @@
<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>

After

Width:  |  Height:  |  Size: 1.0 KiB

23
assets/icon.svg Normal file
View File

@ -0,0 +1,23 @@
<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>

After

Width:  |  Height:  |  Size: 1018 B

16
config.php Normal file
View File

@ -0,0 +1,16 @@
<?php
return [
'site_title' => 'CodePress',
'site_description' => 'A simple PHP CMS',
'base_url' => '/',
'content_dir' => __DIR__ . '/content',
'templates_dir' => __DIR__ . '/templates',
'cache_dir' => __DIR__ . '/cache',
'default_page' => 'home',
'error_404' => '404',
'markdown_enabled' => true,
'php_enabled' => true,
'bootstrap_version' => '5.3.0',
'jquery_version' => '3.7.1'
];

24
content/about/about.md Normal file
View File

@ -0,0 +1,24 @@
# About CodePress
CodePress is a lightweight content management system designed for simplicity and ease of use.
## Philosophy
- **Simplicity First**: No complex database required
- **File-based**: Content is stored in simple files
- **Flexible**: Support for both static content (Markdown) and dynamic content (PHP)
- **Modern**: Built with responsive Bootstrap 5 design
## Technology Stack
- **PHP 7.4+** for server-side logic
- **Bootstrap 5** for responsive design
- **File-based content storage**
- **Simple templating system**
## Perfect For
- Personal blogs
- Documentation sites
- Small business websites
- Portfolio sites

View File

@ -0,0 +1,28 @@
# Getting Started with CodePress
Learn how to set up your first CodePress website in just a few minutes.
## Installation
1. Download the CodePress files
2. Upload to your PHP-enabled server
3. Set appropriate permissions
4. Start adding content!
## Your First Page
Create a new file in the `content/` directory:
```markdown
# My First Page
This is the content of my first page.
```
Save it as `my-page.md` and visit `?page=my-page` to see it live!
## Advanced Features
- Use PHP files for dynamic content
- Organize content in subdirectories
- Customize the appearance through templates

11
content/blog/index.md Normal file
View File

@ -0,0 +1,11 @@
# Blog
Welcome to our blog section. Here you'll find articles, tutorials, and updates about CodePress and web development.
## Recent Posts
Check out our latest blog posts using the menu on the left.
## Categories
Our blog posts are organized into different categories for easy navigation.

View File

@ -0,0 +1,39 @@
# 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.

View File

@ -0,0 +1,46 @@
# 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.

View File

@ -0,0 +1,63 @@
# 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.

View File

@ -0,0 +1,33 @@
# 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.

36
content/contact.html Normal file
View File

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Contact Us</title>
</head>
<body>
<h1>Contact Us</h1>
<div class="contact-form">
<form>
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input type="text" class="form-control" id="name" placeholder="Your name">
</div>
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control" id="email" placeholder="your@email.com">
</div>
<div class="mb-3">
<label for="message" class="form-label">Message</label>
<textarea class="form-control" id="message" rows="3"></textarea>
</div>
<button type="submit" class="btn btn-primary">Send Message</button>
</form>
</div>
<div class="contact-info mt-4">
<h3>Contact Information</h3>
<p><strong>Email:</strong> info@codepress.com</p>
<p><strong>Phone:</strong> +31 123 456 789</p>
<p><strong>Address:</strong> Amsterdam, Netherlands</p>
</div>
</body>
</html>

39
content/demo.php Normal file
View File

@ -0,0 +1,39 @@
<?php
$title = "Dynamic PHP Example";
$currentTime = date('Y-m-d H:i:s');
$visitorCount = rand(1000, 9999);
?>
<h2>PHP Dynamic Content</h2>
<p>This page demonstrates how PHP files can generate dynamic content.</p>
<div class="alert alert-info">
<strong>Current Time:</strong> <?php echo $currentTime; ?>
</div>
<div class="card">
<div class="card-header">
Statistics
</div>
<div class="card-body">
<p><strong>Visitor Count:</strong> <?php echo number_format($visitorCount); ?></p>
<p><strong>PHP Version:</strong> <?php echo phpversion(); ?></p>
<p><strong>Server Software:</strong> <?php echo $_SERVER['SERVER_SOFTWARE'] ?? 'Unknown'; ?></p>
</div>
</div>
<h3>Random Quote</h3>
<?php
$quotes = [
"Simplicity is the ultimate sophistication.",
"Code is poetry.",
"Less is more.",
"Keep it simple, stupid."
];
$randomQuote = $quotes[array_rand($quotes)];
?>
<blockquote class="blockquote">
<p><?php echo $randomQuote; ?></p>
</blockquote>

View File

@ -0,0 +1,11 @@
# CSS Tutorials
Style your web pages with modern CSS.
## Flexbox and Grid
Modern layout techniques for responsive design.
## Animations
Create smooth animations and transitions.

View File

@ -0,0 +1,11 @@
# HTML Tutorials
Master HTML5 and modern web development.
## Semantic HTML
Learn about proper HTML structure and accessibility.
## Forms and Input
Creating interactive forms and validation.

View File

@ -0,0 +1,11 @@
# PHP Tutorials
Learn PHP programming with our comprehensive tutorials.
## Getting Started
Introduction to PHP basics and syntax.
## Advanced Topics
Object-oriented programming, databases, and more.

22
content/home.md Normal file
View File

@ -0,0 +1,22 @@
# Welcome to CodePress
This is a simple PHP CMS built with Bootstrap and Mustache-style templating.
## Features
- **Dynamic Content**: Supports both Markdown and PHP files
- **Auto-generated Menu**: Automatically creates navigation from content directory structure
- **Responsive Design**: Built with Bootstrap 5
- **Simple Configuration**: Easy to customize via config.php
## Getting Started
1. Add your content files to the `content/` directory
2. Use either `.md` or `.php` extensions
3. The menu will automatically update to show your content structure
## Content Structure
The content directory structure determines the navigation menu. Folders become section headers and files become menu items.
Enjoy using CodePress!

View File

@ -0,0 +1,11 @@
# Mobile Projects
Mobile application development examples.
## React Native App
Cross-platform mobile application.
## Progressive Web App
Modern PWA with offline functionality.

View File

@ -0,0 +1,11 @@
# Web Projects
Examples of web development projects.
## E-commerce Site
Full-stack e-commerce application.
## Blog Platform
Content management system with user authentication.

View File

357
index.php Normal file
View File

@ -0,0 +1,357 @@
<?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);
$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 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']);
$html .= '<span class="nav-link folder-toggle" data-bs-toggle="collapse" data-bs-target="#' . $folderId . '" aria-expanded="false">';
$html .= '<i class="arrow bi bi-chevron-right"></i> ' . htmlspecialchars($item['title']);
$html .= '</span>';
$html .= '<ul class="nav flex-column ms-2 collapse" 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;
}
}
$cms = new CodePressCMS($config);
$cms->render();

306
templates/layout.html Normal file
View File

@ -0,0 +1,306 @@
<!DOCTYPE html>
<html lang="en">
<head>
<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">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
height: 100%;
}
body {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.main-wrapper {
flex: 1;
display: flex;
flex-direction: column;
}
.content-wrapper {
flex: 1;
display: flex;
overflow: hidden;
}
.sidebar {
width: 250px;
background-color: #f8f9fa;
border-right: 1px solid #dee2e6;
overflow-y: auto;
flex-shrink: 0;
}
.main-content {
flex: 1;
overflow-y: auto;
padding: 20px;
}
.folder-toggle {
font-weight: bold;
color: #212529 !important;
cursor: pointer;
display: flex;
align-items: center;
padding: 0.5rem 0.75rem;
background-color: #f8f9fa !important;
margin: 0.125rem 0;
transition: background-color 0.2s ease;
}
.folder-toggle:hover {
background-color: #e9ecef !important;
}
.folder-toggle[aria-expanded="true"] {
background-color: #dee2e6 !important;
color: #212529 !important;
font-weight: 600;
}
/* Progressive background colors for nested folders */
.nav .nav .folder-toggle {
background-color: #f1f3f4 !important;
}
.nav .nav .nav .folder-toggle {
background-color: #eaedee !important;
}
.nav .nav .nav .nav .folder-toggle {
background-color: #e3e7e8 !important;
}
.nav .nav .nav .nav .nav .folder-toggle {
background-color: #dce1e2 !important;
}
.nav .nav .nav .nav .nav .nav .folder-toggle {
background-color: #d5dbdd !important;
}
.nav .nav .nav .nav .nav .nav .nav .folder-toggle {
background-color: #ced5d8 !important;
}
.nav .nav .nav .nav .nav .nav .nav .nav .folder-toggle {
background-color: #c7cfd3 !important;
}
.nav .nav .nav .nav .nav .nav .nav .nav .nav .folder-toggle {
background-color: #c0c9ce !important;
}
.nav .nav .nav .nav .nav .nav .nav .nav .nav .nav .folder-toggle {
background-color: #b9c3c9 !important;
}
.folder-toggle .arrow {
margin-right: 8px;
transition: transform 0.2s;
font-size: 0.8em;
}
.folder-toggle[aria-expanded="true"] .arrow {
transform: rotate(90deg);
}
.page-link {
color: #495057 !important;
font-weight: 500;
padding: 0.5rem 0.75rem;
padding-left: 2rem;
background-color: #ffffff !important;
margin: 0.125rem 0;
transition: background-color 0.2s ease;
}
/* Progressive background colors for nested pages */
.nav .nav .page-link {
background-color: #fafbfc !important;
}
.nav .nav .nav .page-link {
background-color: #f5f7f8 !important;
}
.nav .nav .nav .nav .page-link {
background-color: #f0f3f4 !important;
}
.nav .nav .nav .nav .nav .page-link {
background-color: #ebefef !important;
}
.nav .nav .nav .nav .nav .nav .page-link {
background-color: #e6eaea !important;
}
.nav .nav .nav .nav .nav .nav .nav .page-link {
background-color: #e1e5e5 !important;
}
.nav .nav .nav .nav .nav .nav .nav .nav .page-link {
background-color: #dce0e0 !important;
}
.nav .nav .nav .nav .nav .nav .nav .nav .nav .page-link {
background-color: #d7dbdb !important;
}
.nav .nav .nav .nav .nav .nav .nav .nav .nav .nav .page-link {
background-color: #d2d6d6 !important;
}
.page-link:hover {
color: #212529 !important;
background-color: #f8f9fa !important;
}
.nav-link.active {
background-color: #0d6efd !important;
color: #212529 !important;
font-weight: 600;
}
.file-info {
font-size: 0.9rem;
color: #6c757d;
}
.search-form {
max-width: 300px;
}
.card-title a {
text-decoration: none;
color: inherit;
}
.card-title a:hover {
text-decoration: underline;
}
.folder-disabled {
font-weight: 500;
color: #6c757d !important;
cursor: not-allowed;
display: flex;
align-items: center;
padding: 0.5rem 0.75rem;
background-color: #f8f9fa !important;
margin: 0.125rem 0;
opacity: 0.7;
}
.folder-disabled .arrow {
margin-right: 8px;
font-size: 0.8em;
opacity: 0.6;
}
@media (max-width: 768px) {
.sidebar {
width: 200px;
}
}
</style>
</head>
<body>
<header class="bg-primary text-white py-3">
<div class="container-fluid">
<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">
<h1 class="h3 mb-0">{{site_title}}</h1>
</div>
</div>
<div class="col-auto">
<form class="d-flex" method="GET" action="">
<input class="form-control me-2" type="search" name="search" placeholder="Search..." value="{{search_query}}">
<button class="btn btn-outline-light" type="submit">Search</button>
</form>
</div>
</div>
</div>
</header>
<div class="main-wrapper">
<div class="content-wrapper">
<nav class="sidebar">
<div class="pt-3">
<ul class="nav flex-column">
{{menu}}
</ul>
</div>
</nav>
<main class="main-content">
<div>
{{breadcrumb}}
</div>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h2>{{page_title}}</h2>
</div>
<div class="content">
{{content}}
</div>
</main>
</div>
</div>
<footer class="bg-light border-top py-3">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="d-flex justify-content-between align-items-center">
<div class="file-info">
{{file_info}}
</div>
<div class="site-info">
<small class="text-muted">Powered by CodePress CMS</small>
</div>
</div>
</div>
</div>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/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');
}
}
// 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 isExpanded = this.getAttribute('aria-expanded') === 'true';
if (!isExpanded) {
// Close all other folders
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
});
}
otherToggle.setAttribute('aria-expanded', 'false');
}
}
}
});
}
});
});
});
</script>
</body>
</html>