@artilingo/artiframe-cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/logo.png +0 -0
- package/assets/logo.svg +22 -0
- package/assets/logo.webp +0 -0
- package/assets/logo@0.5x.png +0 -0
- package/assets/logo@0.75x.png +0 -0
- package/assets/logo@1.5x.png +0 -0
- package/assets/logo@2x.png +0 -0
- package/assets/logo@3x.png +0 -0
- package/assets/logo@4x.png +0 -0
- package/bin/artiframe.js +57 -0
- package/bin/artiframe.php +45 -0
- package/core-stubs/app/ApiControl.php +81 -0
- package/core-stubs/app/Database.php +69 -0
- package/core-stubs/app/DotEnv.php +63 -0
- package/core-stubs/app/R2Manager.php +130 -0
- package/core-stubs/app/ViewControl.php +24 -0
- package/core-stubs/bin/SystemMethod.php +271 -0
- package/core-stubs/bin/ViewMethod.php +354 -0
- package/core-stubs/docs/de.html +951 -0
- package/core-stubs/docs/en.html +952 -0
- package/core-stubs/docs/es.html +947 -0
- package/core-stubs/docs/fr.html +850 -0
- package/core-stubs/docs/tr.html +951 -0
- package/core-stubs/public/assets/css/components/footer.css +0 -0
- package/core-stubs/public/assets/css/components/header.css +0 -0
- package/core-stubs/public/assets/css/components/mobilenav.css +0 -0
- package/core-stubs/public/assets/css/components/sidebar.css +0 -0
- package/core-stubs/public/assets/css/components/theme-modal.css +0 -0
- package/core-stubs/public/assets/css/root/app.css +61 -0
- package/core-stubs/public/assets/js/components/header.js +0 -0
- package/core-stubs/public/assets/js/components/mobilenav.js +0 -0
- package/core-stubs/public/assets/js/components/sidebar.js +0 -0
- package/core-stubs/public/assets/js/components/theme-modal.js +0 -0
- package/core-stubs/public/assets/js/root/app.js +30 -0
- package/core-stubs/public/includes/footer.php +5 -0
- package/core-stubs/public/includes/head.php +26 -0
- package/core-stubs/public/includes/header.php +5 -0
- package/core-stubs/public/includes/mobilenav.php +5 -0
- package/core-stubs/public/includes/sidebar.php +5 -0
- package/core-stubs/public/includes/theme-modal.php +5 -0
- package/core-stubs/readme/de.md +29 -0
- package/core-stubs/readme/en.md +29 -0
- package/core-stubs/readme/es.md +29 -0
- package/core-stubs/readme/fr.md +29 -0
- package/core-stubs/readme/tr.md +29 -0
- package/package.json +49 -0
- package/scripts/postinstall.js +21 -0
- package/src/App.php +266 -0
- package/src/Commands/MakeApiCommand.php +71 -0
- package/src/Commands/MakeClassCommand.php +88 -0
- package/src/Commands/MakeViewCommand.php +95 -0
- package/src/Commands/NewProjectCommand.php +633 -0
- package/src/Commands/VersionCommand.php +92 -0
- package/src/Lang/de.php +53 -0
- package/src/Lang/en.php +53 -0
- package/src/Lang/es.php +53 -0
- package/src/Lang/fr.php +53 -0
- package/src/Lang/tr.php +53 -0
- package/src/Services/Safeguard.php +48 -0
- package/src/Services/Translator.php +52 -0
|
@@ -0,0 +1,952 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>ArtiFrame | Official Developer Documentation</title>
|
|
7
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
8
|
+
<style>
|
|
9
|
+
:root {
|
|
10
|
+
--bg: #0d1117;
|
|
11
|
+
--surface: #161b22;
|
|
12
|
+
--surface-2: #1c2128;
|
|
13
|
+
--border: #30363d;
|
|
14
|
+
--text: #e6edf3;
|
|
15
|
+
--muted: #7d8590;
|
|
16
|
+
--green: #009d6c;
|
|
17
|
+
--green-light: #00c88c;
|
|
18
|
+
--green-dim: rgba(0,157,108,0.12);
|
|
19
|
+
--blue: #58a6ff;
|
|
20
|
+
--yellow: #e3b341;
|
|
21
|
+
--red: #f85149;
|
|
22
|
+
--purple: #bc8cff;
|
|
23
|
+
--code-bg: #0d1117;
|
|
24
|
+
--sidebar-w: 280px;
|
|
25
|
+
}
|
|
26
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
27
|
+
|
|
28
|
+
html { scroll-behavior: smooth; }
|
|
29
|
+
|
|
30
|
+
body {
|
|
31
|
+
font-family: 'Inter', sans-serif;
|
|
32
|
+
background: var(--bg);
|
|
33
|
+
color: var(--text);
|
|
34
|
+
line-height: 1.75;
|
|
35
|
+
display: flex;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* ---- SIDEBAR ---- */
|
|
39
|
+
.sidebar {
|
|
40
|
+
width: var(--sidebar-w);
|
|
41
|
+
background: var(--surface);
|
|
42
|
+
height: 100vh;
|
|
43
|
+
position: fixed;
|
|
44
|
+
top: 0; left: 0;
|
|
45
|
+
border-right: 1px solid var(--border);
|
|
46
|
+
overflow-y: auto;
|
|
47
|
+
padding: 28px 0 40px;
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-direction: column;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.sb-logo {
|
|
53
|
+
padding: 0 22px 24px;
|
|
54
|
+
border-bottom: 1px solid var(--border);
|
|
55
|
+
margin-bottom: 16px;
|
|
56
|
+
}
|
|
57
|
+
.sb-logo strong {
|
|
58
|
+
font-size: 1.35rem;
|
|
59
|
+
font-weight: 700;
|
|
60
|
+
color: var(--green);
|
|
61
|
+
letter-spacing: -0.3px;
|
|
62
|
+
}
|
|
63
|
+
.sb-logo span {
|
|
64
|
+
display: block;
|
|
65
|
+
font-size: 0.75rem;
|
|
66
|
+
color: var(--muted);
|
|
67
|
+
margin-top: 3px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.sb-cat {
|
|
71
|
+
padding: 16px 22px 6px;
|
|
72
|
+
font-size: 0.68rem;
|
|
73
|
+
text-transform: uppercase;
|
|
74
|
+
letter-spacing: 1.2px;
|
|
75
|
+
color: var(--green);
|
|
76
|
+
font-weight: 700;
|
|
77
|
+
}
|
|
78
|
+
.sidebar nav a {
|
|
79
|
+
display: block;
|
|
80
|
+
padding: 7px 22px;
|
|
81
|
+
font-size: 0.88rem;
|
|
82
|
+
color: var(--muted);
|
|
83
|
+
text-decoration: none;
|
|
84
|
+
border-left: 2px solid transparent;
|
|
85
|
+
transition: all .15s;
|
|
86
|
+
}
|
|
87
|
+
.sidebar nav a:hover,
|
|
88
|
+
.sidebar nav a.active {
|
|
89
|
+
color: var(--text);
|
|
90
|
+
background: var(--green-dim);
|
|
91
|
+
border-left-color: var(--green);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* ---- MAIN ---- */
|
|
95
|
+
.content {
|
|
96
|
+
margin-left: var(--sidebar-w);
|
|
97
|
+
padding: 60px 70px 100px;
|
|
98
|
+
max-width: 1100px;
|
|
99
|
+
width: 100%;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* ---- TYPOGRAPHY ---- */
|
|
103
|
+
h1 { font-size: 2.6rem; font-weight: 700; letter-spacing: -0.5px; margin-bottom: 16px; }
|
|
104
|
+
h1 span { color: var(--green); }
|
|
105
|
+
h2 {
|
|
106
|
+
font-size: 1.7rem; font-weight: 700;
|
|
107
|
+
margin: 72px 0 20px;
|
|
108
|
+
padding-bottom: 14px;
|
|
109
|
+
border-bottom: 1px solid var(--border);
|
|
110
|
+
}
|
|
111
|
+
h2 .tag { font-size: 0.65rem; padding: 3px 8px; border-radius: 4px; font-weight: 700; text-transform: uppercase; letter-spacing: .5px; vertical-align: middle; margin-left: 10px; }
|
|
112
|
+
h3 { font-size: 1.1rem; font-weight: 600; margin: 32px 0 12px; color: var(--green-light); display: flex; align-items: center; gap: 10px; }
|
|
113
|
+
p { color: var(--muted); margin-bottom: 16px; font-size: 0.97rem; }
|
|
114
|
+
strong { color: var(--text); }
|
|
115
|
+
ul, ol { margin: 0 0 16px 22px; color: var(--muted); font-size: 0.97rem; }
|
|
116
|
+
li { margin-bottom: 8px; }
|
|
117
|
+
a { color: var(--green-light); text-decoration: none; }
|
|
118
|
+
a:hover { text-decoration: underline; }
|
|
119
|
+
|
|
120
|
+
/* ---- LEAD ---- */
|
|
121
|
+
.lead { font-size: 1.05rem; color: var(--muted); margin-bottom: 0; max-width: 720px; }
|
|
122
|
+
|
|
123
|
+
/* ---- BADGES ---- */
|
|
124
|
+
.badge { font-size: 0.68rem; padding: 3px 8px; border-radius: 4px; font-weight: 700; text-transform: uppercase; letter-spacing: .5px; white-space: nowrap; }
|
|
125
|
+
.b-view { background: rgba(88,166,255,.12); color: #58a6ff; border: 1px solid rgba(88,166,255,.3); }
|
|
126
|
+
.b-api { background: rgba(188,140,255,.12); color: #bc8cff; border: 1px solid rgba(188,140,255,.3); }
|
|
127
|
+
.b-cli { background: rgba(0,200,140,.12); color: #00c88c; border: 1px solid rgba(0,200,140,.3); }
|
|
128
|
+
.b-core { background: rgba(227,179,65,.12); color: #e3b341; border: 1px solid rgba(227,179,65,.3); }
|
|
129
|
+
.b-sec { background: rgba(248,81,73,.12); color: #f85149; border: 1px solid rgba(248,81,73,.3); }
|
|
130
|
+
|
|
131
|
+
/* ---- CODE ---- */
|
|
132
|
+
pre {
|
|
133
|
+
background: var(--code-bg);
|
|
134
|
+
border: 1px solid var(--border);
|
|
135
|
+
border-radius: 8px;
|
|
136
|
+
padding: 22px 24px;
|
|
137
|
+
overflow-x: auto;
|
|
138
|
+
margin: 18px 0 24px;
|
|
139
|
+
font-size: 0.875rem;
|
|
140
|
+
line-height: 1.6;
|
|
141
|
+
}
|
|
142
|
+
code { font-family: 'JetBrains Mono', 'Consolas', monospace; color: #e6edf3; }
|
|
143
|
+
.ic { /* inline code */
|
|
144
|
+
background: var(--surface-2);
|
|
145
|
+
padding: 2px 7px;
|
|
146
|
+
border-radius: 4px;
|
|
147
|
+
font-family: 'JetBrains Mono', monospace;
|
|
148
|
+
font-size: 0.85em;
|
|
149
|
+
color: var(--yellow);
|
|
150
|
+
border: 1px solid var(--border);
|
|
151
|
+
}
|
|
152
|
+
.kw { color: #ff7b72; } /* keyword / php tag */
|
|
153
|
+
.fn { color: #d2a8ff; } /* function name */
|
|
154
|
+
.st { color: #a5d6ff; } /* string */
|
|
155
|
+
.cm { color: #8b949e; } /* comment */
|
|
156
|
+
.nu { color: #79c0ff; } /* number / bool */
|
|
157
|
+
.var { color: #ffa657; } /* variable */
|
|
158
|
+
|
|
159
|
+
/* ---- CARDS ---- */
|
|
160
|
+
.card {
|
|
161
|
+
background: var(--surface);
|
|
162
|
+
border: 1px solid var(--border);
|
|
163
|
+
border-radius: 10px;
|
|
164
|
+
padding: 28px 32px;
|
|
165
|
+
margin-bottom: 20px;
|
|
166
|
+
}
|
|
167
|
+
.card.highlight { border-left: 3px solid var(--green); }
|
|
168
|
+
|
|
169
|
+
/* ---- ALERTS ---- */
|
|
170
|
+
.alert { padding: 18px 22px; border-radius: 8px; margin: 24px 0; border-left: 3px solid; font-size: 0.95rem; }
|
|
171
|
+
.a-info { background: rgba(88,166,255,.08); border-color: var(--blue); color: #93c5fd; }
|
|
172
|
+
.a-warn { background: rgba(227,179,65,.08); border-color: var(--yellow); color: #fbbf24; }
|
|
173
|
+
.a-danger { background: rgba(248,81,73,.08); border-color: var(--red); color: #fca5a5; }
|
|
174
|
+
.a-success { background: rgba(0,157,108,.08); border-color: var(--green); color: #6ee7b7; }
|
|
175
|
+
|
|
176
|
+
/* ---- RETURN TYPE ---- */
|
|
177
|
+
.ret { display: inline-block; font-size: 0.78rem; color: var(--green); font-family: 'JetBrains Mono', monospace; background: rgba(0,157,108,.12); padding: 3px 10px; border-radius: 4px; border: 1px solid rgba(0,157,108,.25); margin-bottom: 12px; }
|
|
178
|
+
|
|
179
|
+
/* ---- GRID ---- */
|
|
180
|
+
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; margin-bottom: 24px; }
|
|
181
|
+
@media(max-width: 900px) { .grid-2 { grid-template-columns: 1fr; } }
|
|
182
|
+
|
|
183
|
+
/* ---- DIR TREE ---- */
|
|
184
|
+
.tree { background: var(--code-bg); border: 1px solid var(--border); border-radius: 8px; padding: 22px 24px; font-family: 'JetBrains Mono', monospace; font-size: 0.875rem; line-height: 1.9; white-space: pre; overflow-x: auto; }
|
|
185
|
+
.tree .dir { color: var(--blue); font-weight: 600; }
|
|
186
|
+
.tree .file { color: #e6edf3; }
|
|
187
|
+
.tree .note { color: #8b949e; }
|
|
188
|
+
.tree .warn { color: var(--yellow); }
|
|
189
|
+
.tree .safe { color: var(--green); }
|
|
190
|
+
|
|
191
|
+
/* ---- STEP LIST ---- */
|
|
192
|
+
.steps { counter-reset: step; list-style: none; margin: 0 0 24px; padding: 0; }
|
|
193
|
+
.steps li { counter-increment: step; display: flex; gap: 18px; margin-bottom: 20px; }
|
|
194
|
+
.steps li::before {
|
|
195
|
+
content: counter(step);
|
|
196
|
+
flex-shrink: 0;
|
|
197
|
+
width: 30px; height: 30px;
|
|
198
|
+
background: var(--green-dim);
|
|
199
|
+
border: 1px solid rgba(0,157,108,.3);
|
|
200
|
+
color: var(--green);
|
|
201
|
+
border-radius: 50%;
|
|
202
|
+
display: flex; align-items: center; justify-content: center;
|
|
203
|
+
font-weight: 700; font-size: 0.85rem;
|
|
204
|
+
margin-top: 2px;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/* ---- SIGNATURE TABLE ---- */
|
|
208
|
+
.sig-table { width: 100%; border-collapse: collapse; margin: 16px 0 24px; font-size: 0.87rem; }
|
|
209
|
+
.sig-table th { text-align: left; padding: 10px 14px; background: var(--surface-2); color: var(--muted); font-weight: 600; border-bottom: 1px solid var(--border); }
|
|
210
|
+
.sig-table td { padding: 10px 14px; border-bottom: 1px solid rgba(48,54,61,.5); vertical-align: top; }
|
|
211
|
+
.sig-table td:first-child { font-family: 'JetBrains Mono', monospace; color: var(--yellow); white-space: nowrap; }
|
|
212
|
+
.sig-table td:last-child { color: var(--muted); }
|
|
213
|
+
|
|
214
|
+
/* ---- SCROLL SPY ---- */
|
|
215
|
+
header#page-header { margin-bottom: 52px; }
|
|
216
|
+
</style>
|
|
217
|
+
</head>
|
|
218
|
+
<body>
|
|
219
|
+
|
|
220
|
+
<!-- SIDEBAR -->
|
|
221
|
+
<aside class="sidebar">
|
|
222
|
+
<div class="sb-logo">
|
|
223
|
+
<strong>ArtiFrame</strong>
|
|
224
|
+
<span>Developer Documentation</span>
|
|
225
|
+
</div>
|
|
226
|
+
<nav>
|
|
227
|
+
<div class="sb-cat">Getting Started</div>
|
|
228
|
+
<a href="#giris">Introduction & Philosophy</a>
|
|
229
|
+
<a href="#kurulum">Installation</a>
|
|
230
|
+
<a href="#dizin">Directory Structure</a>
|
|
231
|
+
|
|
232
|
+
<div class="sb-cat">Architecture</div>
|
|
233
|
+
<a href="#bootstrapper">Bootstrapper Architecture</a>
|
|
234
|
+
<a href="#kurallar">Rule Set</a>
|
|
235
|
+
|
|
236
|
+
<div class="sb-cat">CLI Tool</div>
|
|
237
|
+
<a href="#cli-giris">Introduction to CLI</a>
|
|
238
|
+
<a href="#cli-new">new</a>
|
|
239
|
+
<a href="#cli-view">make:view</a>
|
|
240
|
+
<a href="#cli-api">make:api</a>
|
|
241
|
+
<a href="#cli-class">make:class</a>
|
|
242
|
+
<a href="#cli-version">version</a>
|
|
243
|
+
|
|
244
|
+
<div class="sb-cat">View Helpers</div>
|
|
245
|
+
<a href="#vh-display">display()</a>
|
|
246
|
+
<a href="#vh-escapeurl">escapeUrl()</a>
|
|
247
|
+
<a href="#vh-csrf">csrfField()</a>
|
|
248
|
+
<a href="#vh-dates">Date Functions</a>
|
|
249
|
+
<a href="#vh-format">Formatting</a>
|
|
250
|
+
<a href="#vh-money">money()</a>
|
|
251
|
+
|
|
252
|
+
<div class="sb-cat">System Helpers</div>
|
|
253
|
+
<a href="#sh-json">jsonResponse()</a>
|
|
254
|
+
<a href="#sh-csrf">verifyCsrf()</a>
|
|
255
|
+
<a href="#sh-sanitize">Sanitize</a>
|
|
256
|
+
<a href="#sh-request">HTTP & Request</a>
|
|
257
|
+
<a href="#sh-security">Security</a>
|
|
258
|
+
|
|
259
|
+
<div class="sb-cat">API Security</div>
|
|
260
|
+
<a href="#api-methods">HTTP Method Control</a>
|
|
261
|
+
<a href="#api-cors">CORS</a>
|
|
262
|
+
<a href="#api-rate">Rate Limiting</a>
|
|
263
|
+
|
|
264
|
+
<div class="sb-cat">Practical</div>
|
|
265
|
+
<a href="#workflow">Full Workflow</a>
|
|
266
|
+
</nav>
|
|
267
|
+
</aside>
|
|
268
|
+
|
|
269
|
+
<!-- MAIN CONTENT -->
|
|
270
|
+
<main class="content">
|
|
271
|
+
|
|
272
|
+
<!-- ====== INTRODUCTION ====== -->
|
|
273
|
+
<header id="page-header">
|
|
274
|
+
<h1>ArtiFrame <span>Documentation</span></h1>
|
|
275
|
+
<p class="lead">A lightweight, fast ecosystem for scalable native PHP projects, built with zero external dependencies, strict rules, and a powerful CLI. Free from dependency bloat and Composer package soup, allowing you to focus on your business logic.</p>
|
|
276
|
+
</header>
|
|
277
|
+
|
|
278
|
+
<!-- ====== INTRODUCTION & PHILOSOPHY ====== -->
|
|
279
|
+
<section id="giris">
|
|
280
|
+
<h2>Introduction & Philosophy</h2>
|
|
281
|
+
<p>ArtiFrame is based on the <strong>"Convention over Configuration"</strong> principle. The power of a framework comes not from the richness of the tools it offers, but from the consistency of the structure it establishes.</p>
|
|
282
|
+
|
|
283
|
+
<div class="grid-2">
|
|
284
|
+
<div class="card highlight">
|
|
285
|
+
<h3>Zero Overhead</h3>
|
|
286
|
+
<p>Zero dependencies on Composer packages, framework core bloat, or 3rd-party libraries. Every line of code is yours—free from bloat and unnecessary abstraction layers.</p>
|
|
287
|
+
</div>
|
|
288
|
+
<div class="card highlight">
|
|
289
|
+
<h3>Security First</h3>
|
|
290
|
+
<p>XSS protection, CSRF verification, sanitization against SQL injection, and HTTP method control come built-in by default. Security is not an option, but a standard.</p>
|
|
291
|
+
</div>
|
|
292
|
+
<div class="card highlight">
|
|
293
|
+
<h3>Strict Rule Set</h3>
|
|
294
|
+
<p>A junior developer joining the project grasps the <code class="ic">data-js</code> architecture and directory structure within minutes. Team alignment is guaranteed at the framework level.</p>
|
|
295
|
+
</div>
|
|
296
|
+
<div class="card highlight">
|
|
297
|
+
<h3>CLI First</h3>
|
|
298
|
+
<p>No manual creation of View, API, or class files. The CLI generates them from stub templates, links assets, and keeps the project consistent.</p>
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
|
|
302
|
+
<div class="alert a-info">
|
|
303
|
+
<strong>ℹ️ AGPLv3 License:</strong> ArtiFrame is open-source. Derivative works you produce can be freely used provided the source code remains open. Copyright notices cannot be removed.
|
|
304
|
+
</div>
|
|
305
|
+
</section>
|
|
306
|
+
|
|
307
|
+
<!-- ====== INSTALLATION ====== -->
|
|
308
|
+
<section id="kurulum">
|
|
309
|
+
<h2>Installation</h2>
|
|
310
|
+
<p>ArtiFrame CLI is installed as a global PHP tool. Install once, use across all projects.</p>
|
|
311
|
+
|
|
312
|
+
<h3>1. Install CLI Tool Globally</h3>
|
|
313
|
+
<pre><code><span class="cm"># Global installation via NPM</span>
|
|
314
|
+
npm install -g @artilingo/artiframe-cli
|
|
315
|
+
|
|
316
|
+
<span class="cm"># Verify installation</span>
|
|
317
|
+
artiframe</code></pre>
|
|
318
|
+
|
|
319
|
+
<h3>2. Interactive Shell</h3>
|
|
320
|
+
<p>Type simply <code class="ic">artiframe</code> in the terminal and press Enter. The CLI does not exit; an interactive shell opens that continuously listens for commands:</p>
|
|
321
|
+
<pre><code>==================================================
|
|
322
|
+
ArtiFrame CLI Interactive Shell v1.0.0
|
|
323
|
+
==================================================
|
|
324
|
+
Type 'help' for commands, or 'exit' to quit.
|
|
325
|
+
|
|
326
|
+
artiframe> </code></pre>
|
|
327
|
+
|
|
328
|
+
<h3>3. Start a New Project</h3>
|
|
329
|
+
<pre><code>artiframe> new my-project</code></pre>
|
|
330
|
+
<p>This command creates the <code class="ic">my-project/</code> directory and copies the entire skeleton structure into it: <code class="ic">app/</code>, <code class="ic">bin/</code>, <code class="ic">config/</code>, <code class="ic">public/</code>, <code class="ic">src/</code>, <code class="ic">.env.example</code>, and the initial <code class="ic">index.php</code> page.</p>
|
|
331
|
+
|
|
332
|
+
<h3>4. Environment Configuration</h3>
|
|
333
|
+
<pre><code>cp .env.example .env</code></pre>
|
|
334
|
+
<p>Open your <code class="ic">.env</code> file and fill in database and application configuration. This file is never committed to version control.</p>
|
|
335
|
+
|
|
336
|
+
<div class="alert a-warn">
|
|
337
|
+
<strong>⚠️ Web Server Configuration:</strong> Direct Apache/Nginx's document root to the <code class="ic">/public/</code> folder. Other directories must never be exposed externally.
|
|
338
|
+
</div>
|
|
339
|
+
</section>
|
|
340
|
+
|
|
341
|
+
<!-- ====== DIRECTORY STRUCTURE ====== -->
|
|
342
|
+
<section id="dizin">
|
|
343
|
+
<h2>Directory Structure</h2>
|
|
344
|
+
<p>The architecture generated when a project is initialized ensures a clear Separation of Concerns (SoC).</p>
|
|
345
|
+
|
|
346
|
+
<div class="tree">
|
|
347
|
+
<span class="dir">project-name/</span>
|
|
348
|
+
├── <span class="dir">app/</span> <span class="note"># Infrastructure layer</span>
|
|
349
|
+
│ ├── <span class="file">ViewControl.php</span> <span class="note"># View (HTML page) bootstrapper</span>
|
|
350
|
+
│ ├── <span class="file">ApiControl.php</span> <span class="note"># API endpoint bootstrapper</span>
|
|
351
|
+
│ ├── <span class="file">Database.php</span> <span class="note"># PDO-based database connection</span>
|
|
352
|
+
│ ├── <span class="file">DotEnv.php</span> <span class="note"># .env reader</span>
|
|
353
|
+
│ └── <span class="file">R2Manager.php</span> <span class="note"># Cloudflare R2 file manager</span>
|
|
354
|
+
│
|
|
355
|
+
├── <span class="dir warn">bin/</span> <span class="note"># ⚠️ Core system — do not edit directly</span>
|
|
356
|
+
│ ├── <span class="file">SystemMethod.php</span> <span class="note"># API/Backend global helpers</span>
|
|
357
|
+
│ ├── <span class="file">ViewMethod.php</span> <span class="note"># View/Frontend global helpers</span>
|
|
358
|
+
│ └── <span class="dir">stubs/</span> <span class="note"># Stub templates used by CLI</span>
|
|
359
|
+
│ ├── view.stub
|
|
360
|
+
│ ├── api-standart.stub
|
|
361
|
+
│ ├── api-switch-case.stub
|
|
362
|
+
│ └── class.stub
|
|
363
|
+
│
|
|
364
|
+
├── <span class="dir">config/</span> <span class="note"># Configuration files</span>
|
|
365
|
+
│ └── <span class="file">app-version.php</span> <span class="note"># APP_VERSION and APP_ENV constants</span>
|
|
366
|
+
│
|
|
367
|
+
├── <span class="dir">public/</span> <span class="note"># ← Web server's only public directory</span>
|
|
368
|
+
│ ├── <span class="dir safe">assets/</span>
|
|
369
|
+
│ │ ├── <span class="dir">css/</span> <span class="note"># View-specific CSS files</span>
|
|
370
|
+
│ │ └── <span class="dir">js/</span> <span class="note"># View-specific JS files</span>
|
|
371
|
+
│ ├── <span class="dir">includes/</span> <span class="note"># Shared components</span>
|
|
372
|
+
│ │ ├── head.php
|
|
373
|
+
│ │ ├── header.php
|
|
374
|
+
│ │ └── footer.php
|
|
375
|
+
│ ├── <span class="dir">api/</span> <span class="note"># API endpoint files</span>
|
|
376
|
+
│ └── <span class="file">index.php</span> <span class="note"># Main page</span>
|
|
377
|
+
│
|
|
378
|
+
├── <span class="dir">src/</span> <span class="note"># Business logic and classes</span>
|
|
379
|
+
├── <span class="file">.env</span> <span class="note"># Environment variables (not in git)</span>
|
|
380
|
+
├── <span class="file">.env.example</span> <span class="note"># Template — committed to git</span>
|
|
381
|
+
└── <span class="file">guide.html</span> <span class="note"># This document</span>
|
|
382
|
+
</div>
|
|
383
|
+
|
|
384
|
+
<div class="alert a-danger">
|
|
385
|
+
<strong>🚫 Do not modify the bin/ directory:</strong> Files inside <code class="ic">bin/</code> are the core of the framework. Project-specific business logic is not added here. Classes and services you add belong under <code class="ic">src/</code>, and infrastructure components belong under <code class="ic">app/</code>.
|
|
386
|
+
</div>
|
|
387
|
+
</section>
|
|
388
|
+
|
|
389
|
+
<!-- ====== BOOTSTRAPPER ====== -->
|
|
390
|
+
<section id="bootstrapper">
|
|
391
|
+
<h2>Bootstrapper Architecture <span class="tag b-core">Critical</span></h2>
|
|
392
|
+
<p>ArtiFrame uses two completely independent bootstrappers. This architecture fundamentally prevents <strong>HTML header issues and security vulnerabilities</strong>.</p>
|
|
393
|
+
|
|
394
|
+
<div class="grid-2">
|
|
395
|
+
<div class="card highlight">
|
|
396
|
+
<h3><span class="badge b-view">View</span> ViewControl.php</h3>
|
|
397
|
+
<p>Used for HTML pages (view files). Starts session, loads <code class="ic">ViewMethod</code>.</p>
|
|
398
|
+
<pre><code><span class="cm">// public/profile.php — AT THE VERY TOP, before outputting HTML</span>
|
|
399
|
+
<span class="kw"><?php</span>
|
|
400
|
+
<span class="fn">require_once</span> <span class="var">$_SERVER</span>[<span class="st">'DOCUMENT_ROOT'</span>]
|
|
401
|
+
. <span class="st">'/../app/ViewControl.php'</span>;
|
|
402
|
+
<span class="kw">use</span> Bin\ViewMethod;
|
|
403
|
+
<span class="kw">?></span>
|
|
404
|
+
<span class="kw"><!DOCTYPE html></span>
|
|
405
|
+
...</code></pre>
|
|
406
|
+
</div>
|
|
407
|
+
<div class="card highlight">
|
|
408
|
+
<h3><span class="badge b-api">API</span> ApiControl.php</h3>
|
|
409
|
+
<p>Used for API endpoint files. Sets JSON header, checks HTTP method, loads <code class="ic">SystemMethod</code>.</p>
|
|
410
|
+
<pre><code><span class="cm">// public/api/user/get.php</span>
|
|
411
|
+
<span class="kw"><?php</span>
|
|
412
|
+
<span class="cm">// $allowedMethods MUST be defined before require</span>
|
|
413
|
+
<span class="var">$allowedMethods</span> = [<span class="st">'GET'</span>];
|
|
414
|
+
|
|
415
|
+
<span class="fn">require_once</span> <span class="var">$_SERVER</span>[<span class="st">'DOCUMENT_ROOT'</span>]
|
|
416
|
+
. <span class="st">'/../app/ApiControl.php'</span>;
|
|
417
|
+
|
|
418
|
+
<span class="kw">use</span> Bin\SystemMethod;</code></pre>
|
|
419
|
+
</div>
|
|
420
|
+
</div>
|
|
421
|
+
|
|
422
|
+
<div class="alert a-danger">
|
|
423
|
+
<strong>🚫 Never mix ViewControl and ApiControl:</strong> If ViewControl is required in an API file, it may return HTML headers instead of JSON headers, corrupting the entire API response. If ApiControl is required in an HTML page, the session won't start and the page will break.
|
|
424
|
+
</div>
|
|
425
|
+
</section>
|
|
426
|
+
|
|
427
|
+
<!-- ====== RULES ====== -->
|
|
428
|
+
<section id="kurallar">
|
|
429
|
+
<h2>Rule Set <span class="tag b-core">Standard</span></h2>
|
|
430
|
+
|
|
431
|
+
<div class="card">
|
|
432
|
+
<h3>Rule 1: data-js Architecture <span class="badge b-sec">Critical</span></h3>
|
|
433
|
+
<p>JavaScript events can never be listened to via a <code class="ic">class</code> or <code class="ic">id</code>. These are visual / style identifiers. All JS interactions are managed using the <code class="ic">data-js</code> attribute. When CSS deletes a class, JavaScript never breaks.</p>
|
|
434
|
+
<pre><code><span class="cm"><!-- ❌ Anti-Pattern — not supported --></span>
|
|
435
|
+
<button id="submitBtn" class="btn">Submit</button>
|
|
436
|
+
<span class="cm">// JS: document.getElementById('submitBtn').addEventListener(...)</span>
|
|
437
|
+
|
|
438
|
+
<span class="cm"><!-- ✅ ArtiFrame Standard --></span>
|
|
439
|
+
<button class="btn btn-primary" <span class="var">data-js</span>="login-submit">Submit</button>
|
|
440
|
+
<span class="cm">// JS: document.querySelector('[data-js="login-submit"]').addEventListener(...)</span></code></pre>
|
|
441
|
+
</div>
|
|
442
|
+
|
|
443
|
+
<div class="card">
|
|
444
|
+
<h3>Rule 2: Theme Architecture</h3>
|
|
445
|
+
<p>Dark/Light mode and themes are managed via <code class="ic">data-theme</code> and <code class="ic">data-mode</code> attributes on the <code class="ic"><html></code> tag. Body classes are not used.</p>
|
|
446
|
+
<pre><code><span class="cm"><!-- HTML opening tag coming from view.stub template --></span>
|
|
447
|
+
<html lang="en" data-theme="default" data-mode="light">
|
|
448
|
+
|
|
449
|
+
<span class="cm">/* Theme definition in app.css */</span>
|
|
450
|
+
html[data-theme="default"][data-mode="dark"] {
|
|
451
|
+
--bg-color: #0b0c0e;
|
|
452
|
+
--text-main: #ffffff;
|
|
453
|
+
}
|
|
454
|
+
html[data-theme="default"][data-mode="light"] {
|
|
455
|
+
--bg-color: #ffffff;
|
|
456
|
+
--text-main: #111111;
|
|
457
|
+
}</code></pre>
|
|
458
|
+
</div>
|
|
459
|
+
|
|
460
|
+
<div class="card">
|
|
461
|
+
<h3>Rule 3: Secure Data Flow</h3>
|
|
462
|
+
<p>All data coming from the database is wrapped with <code class="ic">display()</code> before being output to the DOM. All data arriving at the API is cleaned with <code class="ic">sanitizeString()</code> or <code class="ic">sanitizeInt()</code> before processing.</p>
|
|
463
|
+
</div>
|
|
464
|
+
|
|
465
|
+
<div class="card">
|
|
466
|
+
<h3>Rule 4: Error Management with APP_ENV</h3>
|
|
467
|
+
<p>The <code class="ic">APP_ENV</code> value in the <code class="ic">.env</code> file determines error visibility. In Production, error messages are not displayed to the user.</p>
|
|
468
|
+
<pre><code><span class="cm">// config/app-version.php</span>
|
|
469
|
+
define(<span class="st">'APP_ENV'</span>, (int)<span class="var">$_ENV</span>[<span class="st">'APP_ENV'</span>]); <span class="cm">// 1=Debug, 0=Production</span>
|
|
470
|
+
|
|
471
|
+
<span class="kw">if</span> (APP_ENV === <span class="nu">1</span>) {
|
|
472
|
+
ini_set(<span class="st">'display_errors'</span>, <span class="nu">1</span>);
|
|
473
|
+
error_reporting(E_ALL);
|
|
474
|
+
} <span class="kw">else</span> {
|
|
475
|
+
ini_set(<span class="st">'display_errors'</span>, <span class="nu">0</span>);
|
|
476
|
+
}</code></pre>
|
|
477
|
+
</div>
|
|
478
|
+
</section>
|
|
479
|
+
|
|
480
|
+
<!-- ====== CLI INTRO ====== -->
|
|
481
|
+
<section id="cli-giris">
|
|
482
|
+
<h2>CLI Ecosystem</h2>
|
|
483
|
+
<p>ArtiFrame CLI opens an interactive shell when <code class="ic">artiframe</code> is typed in the terminal. All commands run within this shell. Commands can also be executed as single-shot runs.</p>
|
|
484
|
+
<pre><code><span class="cm"># Interactive mode (recommended)</span>
|
|
485
|
+
artiframe
|
|
486
|
+
artiframe> make:view admin/users.php
|
|
487
|
+
|
|
488
|
+
<span class="cm"># Single-shot mode</span>
|
|
489
|
+
artiframe make:view admin/users.php</code></pre>
|
|
490
|
+
</section>
|
|
491
|
+
|
|
492
|
+
<!-- new -->
|
|
493
|
+
<section id="cli-new">
|
|
494
|
+
<h2><code>new</code> <span class="tag b-cli">CLI Command</span></h2>
|
|
495
|
+
<p>Creates a new ArtiFrame project. Generates the entire directory skeleton, bootstrapper files, and initial index page.</p>
|
|
496
|
+
<pre><code>artiframe> <span class="fn">new</span> <span class="st">project-name</span></code></pre>
|
|
497
|
+
<p>Generated structure:</p>
|
|
498
|
+
<div class="tree" style="font-size:0.82rem; line-height:1.7">
|
|
499
|
+
project-name/
|
|
500
|
+
├── app/ (ViewControl.php, ApiControl.php, Database.php, DotEnv.php)
|
|
501
|
+
├── bin/ (SystemMethod.php, ViewMethod.php, stubs/)
|
|
502
|
+
├── config/ (app-version.php)
|
|
503
|
+
├── public/ (index.php, assets/, includes/, api/)
|
|
504
|
+
├── src/
|
|
505
|
+
├── .env.example
|
|
506
|
+
└── guide.html
|
|
507
|
+
</div>
|
|
508
|
+
</section>
|
|
509
|
+
|
|
510
|
+
<!-- make:view -->
|
|
511
|
+
<section id="cli-view">
|
|
512
|
+
<h2><code>make:view</code> <span class="tag b-cli">CLI Command</span></h2>
|
|
513
|
+
<p>Creates a new page (view) file and its dedicated CSS/JS assets. Assets are automatically linked to the page and cache-busting is applied via <code class="ic">?v=APP_VERSION</code>.</p>
|
|
514
|
+
<pre><code>artiframe> <span class="fn">make:view</span> <span class="st">admin/users.php</span></code></pre>
|
|
515
|
+
<p>Generated files:</p>
|
|
516
|
+
<pre><code><span class="cm">✔ public/admin/users.php</span>
|
|
517
|
+
<span class="cm">✔ public/assets/css/admin/users.css</span>
|
|
518
|
+
<span class="cm">✔ public/assets/js/admin/users.js</span></code></pre>
|
|
519
|
+
<p>The generated view file comes with ViewControl required at the top, head/header/footer includes added, and CSS/JS links connected with cache-busting.</p>
|
|
520
|
+
</section>
|
|
521
|
+
|
|
522
|
+
<!-- make:api -->
|
|
523
|
+
<section id="cli-api">
|
|
524
|
+
<h2><code>make:api</code> <span class="tag b-cli">CLI Command</span></h2>
|
|
525
|
+
<p>Creates an API endpoint file by choosing from two different templates. Every new API file comes pre-configured with the <code class="ic">$allowedMethods</code> variable and <code class="ic">ApiControl.php</code> require statement.</p>
|
|
526
|
+
|
|
527
|
+
<div class="card">
|
|
528
|
+
<h3><span class="badge b-api">standart</span> — Single Action API</h3>
|
|
529
|
+
<p>For endpoints performing a single task (login, submit, delete). Business logic is written directly.</p>
|
|
530
|
+
<pre><code>artiframe> <span class="fn">make:api</span> <span class="st">standart</span> <span class="st">api/auth/login.php</span></code></pre>
|
|
531
|
+
<pre><code><span class="kw"><?php</span>
|
|
532
|
+
<span class="var">$allowedMethods</span> = [<span class="st">'POST'</span>]; <span class="cm">// Accept POST only</span>
|
|
533
|
+
<span class="fn">require_once</span> <span class="var">$_SERVER</span>[<span class="st">'DOCUMENT_ROOT'</span>] . <span class="st">'/../app/ApiControl.php'</span>;
|
|
534
|
+
|
|
535
|
+
<span class="kw">use</span> Bin\SystemMethod;
|
|
536
|
+
|
|
537
|
+
<span class="cm">// Business logic here...</span>
|
|
538
|
+
<span class="fn">jsonResponse</span>([<span class="st">'status'</span> => <span class="st">'success'</span>], <span class="nu">200</span>);</code></pre>
|
|
539
|
+
</div>
|
|
540
|
+
|
|
541
|
+
<div class="card">
|
|
542
|
+
<h3><span class="badge b-api">switch-case</span> — Multi-Action API</h3>
|
|
543
|
+
<p>A structure managing CRUD operations for a module in a single endpoint. The action to perform is determined by the <code class="ic">action</code> parameter.</p>
|
|
544
|
+
<pre><code>artiframe> <span class="fn">make:api</span> <span class="st">switch-case</span> <span class="st">api/user/manage.php</span></code></pre>
|
|
545
|
+
<pre><code><span class="kw"><?php</span>
|
|
546
|
+
<span class="var">$allowedMethods</span> = [<span class="st">'POST'</span>];
|
|
547
|
+
<span class="fn">require_once</span> <span class="var">$_SERVER</span>[<span class="st">'DOCUMENT_ROOT'</span>] . <span class="st">'/../app/ApiControl.php'</span>;
|
|
548
|
+
|
|
549
|
+
<span class="kw">use</span> Bin\SystemMethod;
|
|
550
|
+
|
|
551
|
+
<span class="var">$action</span> = <span class="fn">sanitizeString</span>(<span class="var">$_POST</span>[<span class="st">'action'</span>] ?? <span class="st">''</span>);
|
|
552
|
+
|
|
553
|
+
<span class="kw">switch</span> (<span class="var">$action</span>) {
|
|
554
|
+
<span class="kw">case</span> <span class="st">'create'</span>:
|
|
555
|
+
<span class="fn">jsonResponse</span>([<span class="st">'status'</span> => <span class="st">'success'</span>, <span class="st">'message'</span> => <span class="st">'Created.'</span>], <span class="nu">200</span>);
|
|
556
|
+
<span class="kw">break</span>;
|
|
557
|
+
<span class="kw">case</span> <span class="st">'update'</span>:
|
|
558
|
+
<span class="fn">jsonResponse</span>([<span class="st">'status'</span> => <span class="st">'success'</span>, <span class="st">'message'</span> => <span class="st">'Updated.'</span>], <span class="nu">200</span>);
|
|
559
|
+
<span class="kw">break</span>;
|
|
560
|
+
<span class="kw">case</span> <span class="st">'delete'</span>:
|
|
561
|
+
<span class="fn">jsonResponse</span>([<span class="st">'status'</span> => <span class="st">'success'</span>, <span class="st">'message'</span> => <span class="st">'Deleted.'</span>], <span class="nu">200</span>);
|
|
562
|
+
<span class="kw">break</span>;
|
|
563
|
+
<span class="kw">default</span>:
|
|
564
|
+
<span class="fn">jsonResponse</span>([<span class="st">'status'</span> => <span class="st">'error'</span>, <span class="st">'message'</span> => <span class="st">'Invalid action.'</span>], <span class="nu">400</span>);
|
|
565
|
+
}</code></pre>
|
|
566
|
+
</div>
|
|
567
|
+
</section>
|
|
568
|
+
|
|
569
|
+
<!-- make:class -->
|
|
570
|
+
<section id="cli-class">
|
|
571
|
+
<h2><code>make:class</code> <span class="tag b-cli">CLI Command</span></h2>
|
|
572
|
+
<p>Creates a new PHP class file with namespace and class boilerplate ready.</p>
|
|
573
|
+
<pre><code>artiframe> <span class="fn">make:class</span> <span class="st">classes/EmailService.php</span></code></pre>
|
|
574
|
+
</section>
|
|
575
|
+
|
|
576
|
+
<!-- version -->
|
|
577
|
+
<section id="cli-version">
|
|
578
|
+
<h2><code>version</code> <span class="tag b-cli">CLI Command</span></h2>
|
|
579
|
+
<p>Updates the version number in <code class="ic">config/app-version.php</code> according to Semantic Versioning (SemVer) rules. Version format: <strong>MAJOR.MINOR.PATCH</strong></p>
|
|
580
|
+
|
|
581
|
+
<table class="sig-table">
|
|
582
|
+
<thead>
|
|
583
|
+
<tr><th>Command</th><th>Description</th><th>Example</th></tr>
|
|
584
|
+
</thead>
|
|
585
|
+
<tbody>
|
|
586
|
+
<tr><td>version upgrade patch</td><td>Bug fix, minor improvement</td><td>1.2.3 → 1.2.4</td></tr>
|
|
587
|
+
<tr><td>version upgrade minor</td><td>Backward-compatible new feature</td><td>1.2.3 → 1.3.0</td></tr>
|
|
588
|
+
<tr><td>version upgrade major</td><td>Breaking change</td><td>1.2.3 → 2.0.0</td></tr>
|
|
589
|
+
<tr><td>version downgrade patch</td><td>Revert last patch</td><td>1.2.4 → 1.2.3</td></tr>
|
|
590
|
+
<tr><td>version downgrade minor</td><td>Revert last minor</td><td>1.3.0 → 1.2.0</td></tr>
|
|
591
|
+
<tr><td>version downgrade major</td><td>Revert last major</td><td>2.0.0 → 1.0.0</td></tr>
|
|
592
|
+
</tbody>
|
|
593
|
+
</table>
|
|
594
|
+
|
|
595
|
+
<pre><code>artiframe> <span class="fn">version</span> upgrade minor
|
|
596
|
+
<span class="cm">✔ Version updated from 1.2.0 → 1.3.0.</span></code></pre>
|
|
597
|
+
</section>
|
|
598
|
+
|
|
599
|
+
<!-- ====== VIEW HELPERS ====== -->
|
|
600
|
+
<section id="vh-display">
|
|
601
|
+
<h2>View Helpers — <code>display()</code> <span class="tag b-view">ViewMethod</span></h2>
|
|
602
|
+
<p>Applies <strong>mandatory XSS protection</strong> when outputting data from database or user input to the DOM. Instead of throwing an error on null or empty values, it returns a default value.</p>
|
|
603
|
+
<span class="ret">string display(mixed $data, string $default = '')</span>
|
|
604
|
+
<div class="alert a-info" style="margin-top: 10px; margin-bottom: 24px; padding: 12px 20px;">
|
|
605
|
+
<strong>💡 Tip: Using <code>$default</code></strong><br>
|
|
606
|
+
The second parameter (<code>$default</code>) is the fallback text rendered when data from the database is empty (null, false, empty string). For instance, for a user without a name entered, if you use <code>display($name, 'Anonymous')</code>, your page presents meaningful content instead of awkward blanks.
|
|
607
|
+
</div>
|
|
608
|
+
<pre><code><span class="cm"><!-- ❌ Unsafe — creates XSS vulnerability --></span>
|
|
609
|
+
<h1><?= <span class="var">$user</span>[<span class="st">'name'</span>] ?></h1>
|
|
610
|
+
|
|
611
|
+
<span class="cm"><!-- ✅ ArtiFrame Safe Usage --></span>
|
|
612
|
+
<h1><?= <span class="fn">display</span>(<span class="var">$user</span>[<span class="st">'name'</span>], <span class="st">'Anonymous User'</span>) ?></h1></code></pre>
|
|
613
|
+
</section>
|
|
614
|
+
|
|
615
|
+
<section id="vh-escapeurl">
|
|
616
|
+
<h2>View Helpers — <code>escapeUrl()</code> <span class="tag b-view">ViewMethod</span></h2>
|
|
617
|
+
<p>Used when rendering links received from users (e.g. profile websites) inside <code><a href="..."></code> or <code><img src="..."></code>. Prevents execution of code via links by neutralizing dangerous payloads like <code>javascript:alert(1)</code> (Stored XSS).</p>
|
|
618
|
+
<span class="ret">string escapeUrl(string $url)</span>
|
|
619
|
+
<pre><code><span class="cm"><!-- ❌ Unsafe — JS code can be leaked via URL --></span>
|
|
620
|
+
<a href="<?= <span class="var">$user</span>[<span class="st">'website'</span>] ?>">Visit Site</a>
|
|
621
|
+
|
|
622
|
+
<span class="cm"><!-- ✅ ArtiFrame Safe Usage --></span>
|
|
623
|
+
<a href="<?= <span class="fn">escapeUrl</span>(<span class="var">$user</span>[<span class="st">'website'</span>]) ?>">Visit Site</a></code></pre>
|
|
624
|
+
</section>
|
|
625
|
+
|
|
626
|
+
<section id="vh-csrf">
|
|
627
|
+
<h2>View Helpers — <code>csrfField()</code> <span class="tag b-view">ViewMethod</span></h2>
|
|
628
|
+
<p>Adds a hidden token field to HTML forms against CSRF attacks. <strong>Mandatory to use on every page containing a POST form.</strong></p>
|
|
629
|
+
<span class="ret">string csrfField()</span>
|
|
630
|
+
<pre><code><form action="/api/save.php" method="POST">
|
|
631
|
+
<?= <span class="fn">csrfField</span>() ?> <span class="cm"><!-- Mandatory for security --></span>
|
|
632
|
+
<input type="text" name="name">
|
|
633
|
+
<button type="submit" data-js="save-btn">Save</button>
|
|
634
|
+
</form></code></pre>
|
|
635
|
+
</section>
|
|
636
|
+
|
|
637
|
+
<section id="vh-dates">
|
|
638
|
+
<h2>View Helpers — Date Functions <span class="tag b-view">ViewMethod</span></h2>
|
|
639
|
+
<p>Processes date strings in <code class="ic">Y-m-d H:i:s</code> format or UNIX timestamp values from the database. All textual outputs are localized according to language (<code class="ic">tr</code>, <code class="ic">en</code>, <code class="ic">de</code>, <code class="ic">fr</code>, <code class="ic">es</code>).</p>
|
|
640
|
+
|
|
641
|
+
<table class="sig-table">
|
|
642
|
+
<thead><tr><th>Function</th><th>Sample Output</th><th>Description</th></tr></thead>
|
|
643
|
+
<tbody>
|
|
644
|
+
<tr><td>day($date)</td><td>24</td><td>Day only</td></tr>
|
|
645
|
+
<tr><td>month($date)</td><td>07</td><td>Month only (numeric)</td></tr>
|
|
646
|
+
<tr><td>year($date)</td><td>2026</td><td>Year</td></tr>
|
|
647
|
+
<tr><td>timeOnly($date)</td><td>14:30</td><td>Hour:Minute</td></tr>
|
|
648
|
+
<tr><td>fulldate($date)</td><td>24.07.2026</td><td>Full date</td></tr>
|
|
649
|
+
<tr><td>formatDate($date, $format)</td><td>24.07.2026 14:30</td><td>Custom format</td></tr>
|
|
650
|
+
<tr><td>monthName($date, $lang)</td><td>July / Temmuz / Juli</td><td>Month name (by language)</td></tr>
|
|
651
|
+
<tr><td>fulldateName($date, $lang)</td><td>July 24, 2026 / 24 Temmuz 2026</td><td>Full date (with month name, by language)</td></tr>
|
|
652
|
+
<tr><td>timeAgo($date, $lang)</td><td>5 minutes ago / 5 dakika önce</td><td>Social media style (by language)</td></tr>
|
|
653
|
+
</tbody>
|
|
654
|
+
</table>
|
|
655
|
+
|
|
656
|
+
<div class="alert a-info" style="margin-top: 0; margin-bottom: 24px;">
|
|
657
|
+
<strong>💡 Understanding Parameters: <code>$format</code> and <code>$lang</code></strong>
|
|
658
|
+
<ul style="margin-top: 10px; margin-bottom: 0;">
|
|
659
|
+
<li style="margin-bottom: 8px;"><strong><code>$format</code> (Format):</strong> Used only with the <code>formatDate()</code> function. Accepts standard PHP date letters (e.g. <code>'d/m/Y'</code> ➔ 24/07/2026, or <code>'H:i'</code> ➔ 15:30). Allows building custom date formats when pre-built functions (day, year, etc.) are insufficient.</li>
|
|
660
|
+
<li style="margin-bottom: 0;"><strong><code>$lang</code> (Language):</strong> Used in functions containing text (month names, "ago" words) in their output. If left empty, the system defaults to <strong><code>'tr'</code> (Turkish)</strong>. For multi-language projects, simply pass the language code (<code>tr, en, de, fr, es</code>) as the second parameter. (e.g. <code>timeAgo($date, 'en')</code> ➔ 5 minutes ago)</li>
|
|
661
|
+
</ul>
|
|
662
|
+
</div>
|
|
663
|
+
|
|
664
|
+
<pre><code><span class="cm"><!-- Assuming a UNIX timestamp retrieved with time() function --></span>
|
|
665
|
+
<span class="kw"><?php</span> <span class="var">$date</span> = <span class="fn">time</span>(); <span class="kw">?></span>
|
|
666
|
+
|
|
667
|
+
<span class="cm"><!-- Full date by language --></span>
|
|
668
|
+
<span><?= <span class="fn">fulldateName</span>(<span class="var">$date</span>, <span class="st">'en'</span>) ?></span>
|
|
669
|
+
<span class="cm"><!-- Output: July 24, 2026 --></span>
|
|
670
|
+
|
|
671
|
+
<span class="cm"><!-- Social media style time display --></span>
|
|
672
|
+
<span><?= <span class="fn">timeAgo</span>(<span class="var">$date</span>, <span class="st">'en'</span>) ?></span>
|
|
673
|
+
<span class="cm"><!-- Output: 5 minutes ago --></span>
|
|
674
|
+
|
|
675
|
+
<span><?= <span class="fn">timeAgo</span>(<span class="var">$date</span>, <span class="st">'de'</span>) ?></span>
|
|
676
|
+
<span class="cm"><!-- Output: vor 5 Minuten --></span></code></pre>
|
|
677
|
+
</section>
|
|
678
|
+
|
|
679
|
+
<section id="vh-format">
|
|
680
|
+
<h2>View Helpers — Text Formatting <span class="tag b-view">ViewMethod</span></h2>
|
|
681
|
+
|
|
682
|
+
<div class="card">
|
|
683
|
+
<h3><code>truncate($text, $length, $append)</code></h3>
|
|
684
|
+
<span class="ret">string truncate(string $text, int $length = 100, string $append = '...')</span>
|
|
685
|
+
<p>Truncates long texts (e.g. blog summaries) at the specified character limit without cutting off the last word, and appends the specified string to the end.</p>
|
|
686
|
+
<pre><code><p><?= <span class="fn">truncate</span>(<span class="var">$post</span>[<span class="st">'content'</span>], <span class="nu">160</span>, <span class="st">'...'</span>) ?></p></code></pre>
|
|
687
|
+
</div>
|
|
688
|
+
</section>
|
|
689
|
+
|
|
690
|
+
<section id="vh-money">
|
|
691
|
+
<h2>View Helpers — <code>money()</code> <span class="tag b-view">ViewMethod</span></h2>
|
|
692
|
+
<p>Formats amount with currency symbol. Currency code (ISO) automatically determines symbol placement. Default currency is <code class="ic">usd</code>.</p>
|
|
693
|
+
<span class="ret">string money(float $amount, string $currency = 'usd')</span>
|
|
694
|
+
|
|
695
|
+
<table class="sig-table">
|
|
696
|
+
<thead><tr><th>Code</th><th>Output</th><th>Currency</th></tr></thead>
|
|
697
|
+
<tbody>
|
|
698
|
+
<tr><td>usd</td><td>$1.250,00</td><td>US Dollar</td></tr>
|
|
699
|
+
<tr><td>eur</td><td>1.250,00 €</td><td>Euro</td></tr>
|
|
700
|
+
<tr><td>try / tl</td><td>1.250,00 ₺</td><td>Turkish Lira</td></tr>
|
|
701
|
+
<tr><td>gbp</td><td>£1.250,00</td><td>British Pound</td></tr>
|
|
702
|
+
<tr><td>jpy</td><td>1.250,00 ¥</td><td>Japanese Yen</td></tr>
|
|
703
|
+
<tr><td>inr</td><td>1.250,00 ₹</td><td>Indian Rupee</td></tr>
|
|
704
|
+
<tr><td>rub</td><td>1.250,00 ₽</td><td>Russian Ruble</td></tr>
|
|
705
|
+
<tr><td>krw</td><td>1.250,00 ₩</td><td>South Korean Won</td></tr>
|
|
706
|
+
<tr><td>brl</td><td>R$1.250,00</td><td>Brazilian Real</td></tr>
|
|
707
|
+
<tr><td>aed</td><td>1.250,00 د.إ</td><td>UAE Dirham</td></tr>
|
|
708
|
+
</tbody>
|
|
709
|
+
</table>
|
|
710
|
+
|
|
711
|
+
<pre><code><span><?= <span class="fn">money</span>(<span class="var">$product</span>[<span class="st">'price'</span>], <span class="st">'try'</span>) ?></span>
|
|
712
|
+
<span class="cm"><!-- Output: 1.250,00 ₺ --></span>
|
|
713
|
+
|
|
714
|
+
<span><?= <span class="fn">money</span>(<span class="var">$product</span>[<span class="st">'price'</span>], <span class="st">'usd'</span>) ?></span>
|
|
715
|
+
<span class="cm"><!-- Output: $1.250,00 --></span></code></pre>
|
|
716
|
+
</section>
|
|
717
|
+
|
|
718
|
+
<!-- ====== SYSTEM HELPERS ====== -->
|
|
719
|
+
<section id="sh-json">
|
|
720
|
+
<h2>System Helpers — <code>jsonResponse()</code> <span class="tag b-api">SystemMethod</span></h2>
|
|
721
|
+
<p>Sets JSON header, sets HTTP status code, outputs payload safely with json_encode, and terminates script execution. <strong>All API responses must be delivered through this function.</strong></p>
|
|
722
|
+
<span class="ret">void jsonResponse(array $data, int $statusCode = 200)</span>
|
|
723
|
+
<pre><code><span class="cm">// Successful response</span>
|
|
724
|
+
<span class="fn">jsonResponse</span>([<span class="st">'status'</span> => <span class="st">'success'</span>, <span class="st">'data'</span> => <span class="var">$user</span>], <span class="nu">200</span>);
|
|
725
|
+
|
|
726
|
+
<span class="cm">// Error response</span>
|
|
727
|
+
<span class="fn">jsonResponse</span>([<span class="st">'status'</span> => <span class="st">'error'</span>, <span class="st">'message'</span> => <span class="st">'Unauthorized access.'</span>], <span class="nu">401</span>);</code></pre>
|
|
728
|
+
</section>
|
|
729
|
+
|
|
730
|
+
<section id="sh-csrf">
|
|
731
|
+
<h2>System Helpers — <code>verifyCsrf()</code> <span class="tag b-api">SystemMethod</span></h2>
|
|
732
|
+
<p>Verifies that incoming POST request originates from a legitimate form. Returns false if <code class="ic">csrfField()</code> is missing or token is invalid.</p>
|
|
733
|
+
<span class="ret">bool verifyCsrf(string $token)</span>
|
|
734
|
+
<pre><code><span class="kw">if</span> (!<span class="fn">verifyCsrf</span>(<span class="var">$_POST</span>[<span class="st">'csrf_token'</span>] ?? <span class="st">''</span>)) {
|
|
735
|
+
<span class="fn">jsonResponse</span>([<span class="st">'status'</span> => <span class="st">'error'</span>, <span class="st">'message'</span> => <span class="st">'Invalid CSRF token.'</span>], <span class="nu">403</span>);
|
|
736
|
+
}</code></pre>
|
|
737
|
+
</section>
|
|
738
|
+
|
|
739
|
+
<section id="sh-sanitize">
|
|
740
|
+
<h2>System Helpers — Sanitize Functions <span class="tag b-api">SystemMethod</span></h2>
|
|
741
|
+
|
|
742
|
+
<table class="sig-table">
|
|
743
|
+
<thead><tr><th>Function</th><th>Description</th></tr></thead>
|
|
744
|
+
<tbody>
|
|
745
|
+
<tr><td>sanitizeInt($value)</td><td>Strips all letters, symbols, and commas, keeping <strong>integers only</strong>. Used for IDs or limits.</td></tr>
|
|
746
|
+
<tr><td>sanitizeFloat($value)</td><td>Cleans everything except fractional (decimal) numbers. Used for monetary amounts or metrics.</td></tr>
|
|
747
|
+
<tr><td>sanitizeString($value)</td><td><strong>Destroys all HTML and PHP tags (<code><script></code>, <code><iframe></code>, etc.)</strong> to prevent XSS and derived attacks. Leaves safe plain text.</td></tr>
|
|
748
|
+
<tr><td>sanitizeEmail($email)</td><td>Filters all invalid and dangerous characters that do not conform to email format.</td></tr>
|
|
749
|
+
</tbody>
|
|
750
|
+
</table>
|
|
751
|
+
|
|
752
|
+
<pre><code><span class="var">$id</span> = <span class="fn">sanitizeInt</span>(<span class="var">$_POST</span>[<span class="st">'id'</span>] ?? <span class="nu">0</span>);
|
|
753
|
+
<span class="var">$name</span> = <span class="fn">sanitizeString</span>(<span class="var">$_POST</span>[<span class="st">'name'</span>] ?? <span class="st">''</span>);
|
|
754
|
+
<span class="var">$email</span> = <span class="fn">sanitizeEmail</span>(<span class="var">$_POST</span>[<span class="st">'email'</span>] ?? <span class="st">''</span>);</code></pre>
|
|
755
|
+
</section>
|
|
756
|
+
|
|
757
|
+
<section id="sh-request">
|
|
758
|
+
<h2>System Helpers — HTTP & Request <span class="tag b-api">SystemMethod</span></h2>
|
|
759
|
+
|
|
760
|
+
<table class="sig-table">
|
|
761
|
+
<thead><tr><th>Function</th><th>Description</th></tr></thead>
|
|
762
|
+
<tbody>
|
|
763
|
+
<tr><td>isPost()</td><td>Is request POST?</td></tr>
|
|
764
|
+
<tr><td>isGet()</td><td>Is request GET?</td></tr>
|
|
765
|
+
<tr><td>isPut()</td><td>Is request PUT?</td></tr>
|
|
766
|
+
<tr><td>isDelete()</td><td>Is request DELETE?</td></tr>
|
|
767
|
+
<tr><td>isAjax()</td><td>Is request via XHR/Fetch API?</td></tr>
|
|
768
|
+
<tr><td>getClientIp()</td><td>Real IP address (Cloudflare & Proxy supported)</td></tr>
|
|
769
|
+
<tr><td>redirect($url)</td><td>Redirects to given URL and terminates script</td></tr>
|
|
770
|
+
</tbody>
|
|
771
|
+
</table>
|
|
772
|
+
|
|
773
|
+
<pre><code><span class="cm">// Block direct browser access</span>
|
|
774
|
+
<span class="kw">if</span> (!<span class="fn">isAjax</span>()) {
|
|
775
|
+
<span class="fn">jsonResponse</span>([<span class="st">'error'</span> => <span class="st">'Direct access restricted.'</span>], <span class="nu">403</span>);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
<span class="cm">// Redirect if no session</span>
|
|
779
|
+
<span class="kw">if</span> (!isset(<span class="var">$_SESSION</span>[<span class="st">'user'</span>])) {
|
|
780
|
+
<span class="fn">redirect</span>(<span class="st">'/login.php'</span>);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
<span class="var">$ip</span> = <span class="fn">getClientIp</span>(); <span class="cm">// Real IP behind Cloudflare as well</span></code></pre>
|
|
784
|
+
</section>
|
|
785
|
+
|
|
786
|
+
<section id="sh-security">
|
|
787
|
+
<h2>System Helpers — Security <span class="tag b-sec">SystemMethod</span></h2>
|
|
788
|
+
|
|
789
|
+
<table class="sig-table">
|
|
790
|
+
<thead><tr><th>Function</th><th>Description</th></tr></thead>
|
|
791
|
+
<tbody>
|
|
792
|
+
<tr><td>generateCsrf()</td><td>Generates new CSRF token and saves to session</td></tr>
|
|
793
|
+
<tr><td>generateToken($length)</td><td>Cryptographically secure random hex string (password reset, API key, etc.)</td></tr>
|
|
794
|
+
<tr><td>hashPassword($password)</td><td>Hashes password with bcrypt</td></tr>
|
|
795
|
+
<tr><td>verifyPassword($password, $hash)</td><td>Verifies password against hash</td></tr>
|
|
796
|
+
</tbody>
|
|
797
|
+
</table>
|
|
798
|
+
|
|
799
|
+
<pre><code><span class="cm">// Secure token generation (API key, email verification link, etc.)</span>
|
|
800
|
+
<span class="var">$token</span> = <span class="fn">generateToken</span>(<span class="nu">32</span>);
|
|
801
|
+
<span class="cm">// ⚠️ Note: 32 bytes of data generated, but converted to hex format</span>
|
|
802
|
+
<span class="cm">// resulting in a string exactly twice as long, i.e., 64 characters.</span>
|
|
803
|
+
|
|
804
|
+
<span class="cm">// Password registration</span>
|
|
805
|
+
<span class="var">$hash</span> = <span class="fn">hashPassword</span>(<span class="var">$_POST</span>[<span class="st">'password'</span>]);
|
|
806
|
+
|
|
807
|
+
<span class="cm">// Password verification</span>
|
|
808
|
+
<span class="kw">if</span> (!<span class="fn">verifyPassword</span>(<span class="var">$_POST</span>[<span class="st">'password'</span>], <span class="var">$user</span>[<span class="st">'password_hash'</span>])) {
|
|
809
|
+
<span class="fn">jsonResponse</span>([<span class="st">'error'</span> => <span class="st">'Incorrect password.'</span>], <span class="nu">401</span>);
|
|
810
|
+
}</code></pre>
|
|
811
|
+
</section>
|
|
812
|
+
|
|
813
|
+
<!-- ====== API SECURITY ====== -->
|
|
814
|
+
<section id="api-methods">
|
|
815
|
+
<h2>API — HTTP Method Control <span class="tag b-sec">ApiControl</span></h2>
|
|
816
|
+
<p>The <code class="ic">$allowedMethods</code> array MUST be defined <strong>before</strong> ApiControl require. ApiControl reads this array and automatically rejects requests from non-allowed methods with <code class="ic">405 Method Not Allowed</code>.</p>
|
|
817
|
+
<pre><code><span class="cm">// Endpoint accepting only GET and POST</span>
|
|
818
|
+
<span class="var">$allowedMethods</span> = [<span class="st">'GET'</span>, <span class="st">'POST'</span>];
|
|
819
|
+
<span class="fn">require_once</span> ... . <span class="st">'/../app/ApiControl.php'</span>;
|
|
820
|
+
|
|
821
|
+
<span class="cm">// Endpoint accepting only DELETE</span>
|
|
822
|
+
<span class="var">$allowedMethods</span> = [<span class="st">'DELETE'</span>];
|
|
823
|
+
<span class="fn">require_once</span> ... . <span class="st">'/../app/ApiControl.php'</span>;</code></pre>
|
|
824
|
+
<p>Preflight <code class="ic">OPTIONS</code> requests automatically return <code class="ic">200</code> for CORS and script terminates — no manual intervention needed.</p>
|
|
825
|
+
</section>
|
|
826
|
+
|
|
827
|
+
<section id="api-cors">
|
|
828
|
+
<h2>API — CORS <span class="tag b-api">ApiControl (Commented Out)</span></h2>
|
|
829
|
+
<p>If you want to allow API access from other domains or mobile apps, activate the CORS block inside <code class="ic">ApiControl.php</code>.</p>
|
|
830
|
+
<pre><code><span class="cm">// Inside ApiControl.php — activate by uncommenting</span>
|
|
831
|
+
header(<span class="st">"Access-Control-Allow-Origin: https://your-domain.com"</span>);
|
|
832
|
+
header(<span class="st">"Access-Control-Allow-Methods: GET, POST, OPTIONS"</span>);
|
|
833
|
+
header(<span class="st">"Access-Control-Allow-Headers: Content-Type, Authorization"</span>);</code></pre>
|
|
834
|
+
<div class="alert a-warn">
|
|
835
|
+
<strong>⚠️</strong> Do not use <code class="ic">*</code> (all domains) in Production environment. Specify domain address explicitly.
|
|
836
|
+
</div>
|
|
837
|
+
</section>
|
|
838
|
+
|
|
839
|
+
<section id="api-rate">
|
|
840
|
+
<h2>API — Rate Limiting <span class="tag b-api">ApiControl (Commented Out)</span></h2>
|
|
841
|
+
<p>To prevent malicious users or bots from flooding the API, you can activate the Rate Limiting block inside <code class="ic">ApiControl.php</code>. Default rule: <strong>60 requests / IP in 60 seconds</strong>.</p>
|
|
842
|
+
<div class="alert a-warn" style="margin-top: 10px; margin-bottom: 20px;">
|
|
843
|
+
<strong>⚠️ Redis Server Required:</strong> Rate Limiting feature works using Redis via <code>\Src\Service\RedisService::getInstance()</code> class. Before enabling this code block, make sure you have installed a Redis service in your project and created a connection class under <code>src/Service/</code>.
|
|
844
|
+
</div>
|
|
845
|
+
<pre><code><span class="cm">// If more than 60 requests arrive from the same IP in 60 seconds:</span>
|
|
846
|
+
http_response_code(<span class="nu">429</span>); <span class="cm">// Too Many Requests</span>
|
|
847
|
+
echo json_encode([<span class="st">'error'</span> => <span class="st">'Too many requests. Please wait.'</span>]);</code></pre>
|
|
848
|
+
</section>
|
|
849
|
+
|
|
850
|
+
<!-- ====== WORKFLOW ====== -->
|
|
851
|
+
<section id="workflow">
|
|
852
|
+
<h2>Full Workflow: Contact Form (End-to-End)</h2>
|
|
853
|
+
<p>Let's walk step-by-step through developing a <strong>Contact Form</strong> feature from scratch using ArtiFrame standards.</p>
|
|
854
|
+
|
|
855
|
+
<ol class="steps">
|
|
856
|
+
<li>
|
|
857
|
+
<div>
|
|
858
|
+
<strong>Create the View File</strong>
|
|
859
|
+
<pre><code>artiframe> <span class="fn">make:view</span> <span class="st">contact.php</span>
|
|
860
|
+
<span class="cm">✔ public/contact.php</span>
|
|
861
|
+
<span class="cm">✔ public/assets/css/contact.css</span>
|
|
862
|
+
<span class="cm">✔ public/assets/js/contact.js</span></code></pre>
|
|
863
|
+
</div>
|
|
864
|
+
</li>
|
|
865
|
+
<li>
|
|
866
|
+
<div>
|
|
867
|
+
<strong>Create the API Endpoint</strong>
|
|
868
|
+
<pre><code>artiframe> <span class="fn">make:api</span> <span class="st">standart</span> <span class="st">api/contact/send.php</span></code></pre>
|
|
869
|
+
</div>
|
|
870
|
+
</li>
|
|
871
|
+
<li>
|
|
872
|
+
<div>
|
|
873
|
+
<strong>Code the HTML Form</strong><br>
|
|
874
|
+
Add the following form to <code class="ic">public/contact.php</code>:
|
|
875
|
+
<pre><code><form action="/api/contact/send.php" method="POST">
|
|
876
|
+
<?= <span class="fn">csrfField</span>() ?>
|
|
877
|
+
<input type="text" name="name" placeholder="Your Name">
|
|
878
|
+
<input type="email" name="email" placeholder="Email">
|
|
879
|
+
<textarea name="message" placeholder="Your Message"></textarea>
|
|
880
|
+
<button type="submit" data-js="<span class="st">contact-send</span>">Send</button>
|
|
881
|
+
</form></code></pre>
|
|
882
|
+
</div>
|
|
883
|
+
</li>
|
|
884
|
+
<li>
|
|
885
|
+
<div>
|
|
886
|
+
<strong>Connect Fetch API with JavaScript</strong><br>
|
|
887
|
+
In <code class="ic">public/assets/js/contact.js</code> file:
|
|
888
|
+
<pre><code>document.<span class="fn">querySelector</span>(<span class="st">'[data-js="contact-send"]'</span>).<span class="fn">addEventListener</span>(<span class="st">'click'</span>, async (e) => {
|
|
889
|
+
e.<span class="fn">preventDefault</span>();
|
|
890
|
+
<span class="kw">const</span> formData = <span class="kw">new</span> <span class="fn">FormData</span>(e.target.<span class="fn">closest</span>(<span class="st">'form'</span>));
|
|
891
|
+
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="st">'/api/contact/send.php'</span>, {
|
|
892
|
+
method: <span class="st">'POST'</span>,
|
|
893
|
+
body: formData
|
|
894
|
+
});
|
|
895
|
+
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
|
896
|
+
<span class="fn">console</span>.<span class="fn">log</span>(data);
|
|
897
|
+
});</code></pre>
|
|
898
|
+
</div>
|
|
899
|
+
</li>
|
|
900
|
+
<li>
|
|
901
|
+
<div>
|
|
902
|
+
<strong>Complete Server Logic (Backend)</strong><br>
|
|
903
|
+
In <code class="ic">public/api/contact/send.php</code> file:
|
|
904
|
+
<pre><code><span class="kw"><?php</span>
|
|
905
|
+
<span class="var">$allowedMethods</span> = [<span class="st">'POST'</span>];
|
|
906
|
+
<span class="fn">require_once</span> <span class="var">$_SERVER</span>[<span class="st">'DOCUMENT_ROOT'</span>] . <span class="st">'/../app/ApiControl.php'</span>;
|
|
907
|
+
<span class="kw">use</span> Bin\SystemMethod;
|
|
908
|
+
|
|
909
|
+
<span class="cm">// 1. CSRF Verification</span>
|
|
910
|
+
<span class="kw">if</span> (!<span class="fn">verifyCsrf</span>(<span class="var">$_POST</span>[<span class="st">'csrf_token'</span>] ?? <span class="st">''</span>)) {
|
|
911
|
+
<span class="fn">jsonResponse</span>([<span class="st">'error'</span> => <span class="st">'Invalid token.'</span>], <span class="nu">403</span>);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
<span class="cm">// 2. Sanitize Data</span>
|
|
915
|
+
<span class="var">$name</span> = <span class="fn">sanitizeString</span>(<span class="var">$_POST</span>[<span class="st">'name'</span>] ?? <span class="st">''</span>);
|
|
916
|
+
<span class="var">$email</span> = <span class="fn">sanitizeEmail</span>(<span class="var">$_POST</span>[<span class="st">'email'</span>] ?? <span class="st">''</span>);
|
|
917
|
+
<span class="var">$message</span> = <span class="fn">sanitizeString</span>(<span class="var">$_POST</span>[<span class="st">'message'</span>] ?? <span class="st">''</span>);
|
|
918
|
+
|
|
919
|
+
<span class="cm">// 3. Business Logic (Send mail, save to DB, etc.)</span>
|
|
920
|
+
<span class="cm">// ...</span>
|
|
921
|
+
|
|
922
|
+
<span class="cm">// 4. Send Response</span>
|
|
923
|
+
<span class="fn">jsonResponse</span>([<span class="st">'status'</span> => <span class="st">'success'</span>, <span class="st">'message'</span> => <span class="st">'Your message has been received.'</span>], <span class="nu">200</span>);</code></pre>
|
|
924
|
+
</div>
|
|
925
|
+
</li>
|
|
926
|
+
</ol>
|
|
927
|
+
|
|
928
|
+
<div class="alert a-success">
|
|
929
|
+
<strong>✅ Completed!</strong> You created a fully secure and standard-compliant form workflow with XSS protection, CSRF verification, HTTP method restriction, and data-js architecture.
|
|
930
|
+
</div>
|
|
931
|
+
</section>
|
|
932
|
+
|
|
933
|
+
</main>
|
|
934
|
+
|
|
935
|
+
<script>
|
|
936
|
+
const sections = document.querySelectorAll('section[id], header[id]');
|
|
937
|
+
const links = document.querySelectorAll('.sidebar nav a');
|
|
938
|
+
|
|
939
|
+
const observer = new IntersectionObserver((entries) => {
|
|
940
|
+
entries.forEach(entry => {
|
|
941
|
+
if (entry.isIntersecting) {
|
|
942
|
+
links.forEach(l => l.classList.remove('active'));
|
|
943
|
+
const active = document.querySelector(`.sidebar nav a[href="#${entry.target.id}"]`);
|
|
944
|
+
if (active) active.classList.add('active');
|
|
945
|
+
}
|
|
946
|
+
});
|
|
947
|
+
}, { rootMargin: '-20% 0px -70% 0px' });
|
|
948
|
+
|
|
949
|
+
sections.forEach(s => observer.observe(s));
|
|
950
|
+
</script>
|
|
951
|
+
</body>
|
|
952
|
+
</html>
|