T Toutadminالتوثيق

تثبيت إصدار PHP

يُوضَع إصدار PHP كما هو على استضافة مشتركة: لا Composer، ولا مدير حزم، ولا خدمة تُبقى عاملة. مجلد واحد، وملف إعداد واحد، ومهمة مجدولة واحدة. يمضي هذا الدليل من مجلد فارغ إلى نسخة في الإنتاج، بما في ذلك ما تتركه أكثر الأدلة جانبًا: الصلاحيات، وTLS، والمهمة المجدولة، والنسخ الاحتياطية، والترقية.

ما يلزم#

العنصرالإصدارلماذا
PHP8.1 أو أحدثأنواع الاتحاد وenum وreadonly وnever: الشفرة تستعملها.
pdo_sqliteقاعدة البيانات كلها. وبدونه لا يعمل شيء.
mbstringمعالجة سليمة للنصوص في ست عشرة لغة.
opensslمستحسنتعمية الأسرار في القاعدة (كلمات مرور SMTP، ورموز الإيداع الخارجي).
curlمستحسنخطافات الويب الصادرة وإيداع النسخ الاحتياطية لدى طرف ثالث.
intlمستحسنتواريخ مكتوبة كما في إصدار Node. وبدونه تأتي أسماء الأشهر من القواميس.

يتحقق معالج التثبيت من هذا كله ويعرضه على الشاشة قبل أن يدعك تمضي: ما كان مانعًا يوقف التثبيت، وما كان اختياريًّا يُشار إليه دون منع.

لا اعتماديات

ليس ثمة ما يُثبَّت بـcomposer: قارئ IMAP، وأرشيف tar.gz، وPDF، وCSV، ورمز QR، وعميل HTTP مكتوبة كلها داخل المنتج. وهذا ما يتيح وضعه على استضافة لا سطر أوامر لك فيها.

١. رفع الملفات#

احصل على أرشيف إصدار PHP ثم ارفعه. المجلد يبدو هكذا:

toutadmin/
├── app/            الشفرة (لا يقدّمها خادم الويب أبدًا)
│   ├── Core/       النواة: التوجيه، والقاعدة، والجلسات، والأمان
│   ├── Modules/    الأعمال: الموارد البشرية، والمالية، والمشاريع، والجودة…
│   ├── Controllers/
│   ├── views/      القوالب
│   ├── locales/    القواميس الستة عشر
│   └── schema.sql  المخطط، وهو عين مخطط إصدار Node
├── public/         ← جذر الويب يشير إلى هنا، ولا إلى سواه
│   ├── index.php   المتحكم الأمامي
│   ├── css/ js/
├── data/           القاعدة، والخزنة، وملف التوقيع، والمستندات الواردة، والنسخ
├── tools/          cron.php، check-keys.php
├── tests/
├── config.sample.php
└── VERSION
جذر الويب يشير إلى public/ لا إلى المجلد

هذه أهم نقطة في الصفحة. فإن قدّم الخادم المجلد كله، صار data/app.sqlite قابلًا للتنزيل من أي أحد: القاعدة كلها، وفيها بصمات كلمات المرور والخزنة. ومجلد data/ فوق public/ عن قصد.

على استضافة مشتركة#

تظهر حالتان. فإن أتاح لك مزوّدك اختيار جذر النطاق (cPanel وPlesk وأكثر الاستضافات في وضع «مجلد الجذر»)، فضع المجلد خارج www/ ووجّه النطاق إلى toutadmin/public.

وإن كان الجذر مفروضًا — وغالبًا www/ أو public_html/ — فضع فيه محتوى public/ والباقي فوقه، ثم صحّح المسار في رأس public/index.php:

www/                  ← الجذر المفروض
├── index.php
├── css/  js/
toutadmin/            ← الباقي، خارج متناول الويب
├── app/
├── data/
└── config.php
// www/index.php — السطر الذي يُعدَّل
require_once __DIR__ . '/../toutadmin/app/bootstrap.php';

وفي آخر المطاف، إن تعذّر عليك وضع أي شيء فوق الجذر، فملف .htaccess المرفق داخل data/ يرفض الوصول أصلًا. وهو حزام لا حل: فضّل دائمًا المجلد خارج الجذر.

٢. كتابة الإعداد#

cp config.sample.php config.php

ثم افتح config.php:

<?php
return [
    // قاعدة SQLite ومجلد البيانات: خارج جذر الويب.
    'db_path'  => __DIR__ . '/data/app.sqlite',
    'data_dir' => __DIR__ . '/data',

    // مفتاح خاص بهذه النسخة. يُولَّد مرة واحدة:
    //   php -r "echo bin2hex(random_bytes(32));"
    'session_secret' => '…',

    // رمز التثبيت: انظر الخطوة التالية.
    'install_token' => '',

    // العنوان العام، للروابط المرسَلة بالبريد.
    'base_url' => 'https://intranet.mithal.com',

    // لا تفعّله إلا خلف وسيط موثوق.
    'trust_proxy' => false,

    // السقوف: محاولات الدخول لكل ربع ساعة ولكل عنوان،
    // والطلبات في الدقيقة ولكل عنوان.
    'login_rate_limit'  => 10,
    'global_rate_limit' => 300,

    // الانتهاء: الخمول، ثم مدة مطلقة لا يمدّدها أي نشاط.
    'session_idle_minutes' => 60,
    'session_max_hours'    => 12,
];
مفتاح النسخة

يختم توقيعات ملف التوقيع ويعمّي الأسرار في القاعدة. وضياعه يُبطل توقيعات. فإن تركت القيمة النموذجية، ولّد المنتج واحدًا من تلقاء نفسه في data/.instance-key (صلاحيات 0600) — فتذكّر عندئذٍ أن تحفظ هذا الملف مع البقية.

ويمكنك أيضًا الاحتفاظ بالإعداد في مكان آخر: متغير البيئة TOUTADMIN_CONFIG يشير إلى ملف آخر.

٣. وضع رمز تثبيت#

بين رفع الملفات ومرورك بالمعالج، تكون النسخة لمن يجدها: أول قادم يُنشئ حساب الإدارة. والنافذة قصيرة لكنها تكفي. فضع إذًا قيمة عشوائية قبل النشر:

php -r "echo bin2hex(random_bytes(16));"
# ثم في config.php:
'install_token' => 'c3f1…',

يطلبه المعالج، ويقارنه في زمن ثابت — فمقارنة عادية تتيح تخمين الرمز حرفًا بحرف — ويقيّد كل رفض. وبلا الرمز لا يُنشَأ شيء. وما إن تُثبَّت النسخة حتى يُغلق المعالج نفسه: فليبقَ الرمز، لم يعد يفيد.

٤. الصلاحيات#

المسارالصلاحياتمن يكتب
data/0770خادم الويب، وهو وحده
data/app.sqlite0660يُنشئه التثبيت
config.php0640لا أحد — قراءة فقط للخادم
app/ وpublic/0755لا أحد — قراءة فقط
# مثال على خادم يعمل فيه PHP بهوية www-data
sudo chown -R انت:www-data /var/www/toutadmin
sudo find /var/www/toutadmin -type d -exec chmod 750 {} \;
sudo find /var/www/toutadmin -type f -exec chmod 640 {} \;
sudo chmod 770 /var/www/toutadmin/data
sudo chmod 750 /var/www/toutadmin/public

ويكتب SQLite كذلك ملفات مجاورة (-wal و-shm): فالذي يجب أن يكون قابلًا للكتابة هو المجلد، لا القاعدة وحدها.

٥. خادم الويب#

Nginx#

server {
    listen 443 ssl http2;
    server_name intranet.mithal.com;

    root /var/www/toutadmin/public;   # ولا شيء غيره
    index index.php;

    ssl_certificate     /etc/letsencrypt/live/intranet.mithal.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/intranet.mithal.com/privkey.pem;

    client_max_body_size 20M;          # الإيداعات في الخزنة، والمستندات الواردة

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

    # الملفات المودَعة لا تُقدَّم مباشرةً أبدًا: تمرّ عبر مسار
    # يتحقق من الجلسة ومن البصمة.
    location ~ ^/(data|app|tools|tests)/ { deny all; }
}

server {
    listen 80;
    server_name intranet.mithal.com;
    return 301 https://$host$request_uri;
}

Apache#

<VirtualHost *:443>
    ServerName intranet.mithal.com
    DocumentRoot /var/www/toutadmin/public

    SSLEngine on
    SSLCertificateFile    /etc/letsencrypt/live/intranet.mithal.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/intranet.mithal.com/privkey.pem

    <Directory /var/www/toutadmin/public>
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

وملف public/.htaccess المرفق يعيد أصلًا كل العناوين إلى index.php. وعلى استضافة مشتركة بـApache، هذا كل ما يلزم.

TLS#

يضع المنتج ترويسة Strict-Transport-Security ما إن يصير الاتصال معمّى، ولا يضعها في العلن أبدًا: فلو أُعلنت من صفحة غير معمّاة لما قُرئت، ولحبست تجربة محلية في https ستة أشهر. وعلى خادم تديره أنت:

sudo certbot --nginx -d intranet.mithal.com

وخلف وسيط أو موزّع حِمل ينهي TLS، ضع 'trust_proxy' => true — وهناك وحده: وإلا استطاع أي أحد أن يعلن في ترويسة عنوان IP والبروتوكول اللذين يشاء.

٦. المهمة المجدولة#

موقع PHP لا يعمل إلا لحظة الطلب. وما يفعله إصدار Node في مسحه الساعي — ثماني عمليات — يجري هنا من مجدول الاستضافة:

* * * * * /usr/bin/php /var/www/toutadmin/tools/cron.php >> /var/log/toutadmin-cron.log 2>&1
ماذا تفعل المهمةبأي وتيرة
تغلق الحسابات التي انتهت عقودهافي كل مرور
تصدر فواتير الاشتراكات التي حلّ أجلهافي كل مرور، دون أن تفوتر مرتين أبدًا
تحوّل الاستحقاقات إلى إشعاراتفي كل مرور، بلا تكرار
تنظّف الإشعارات المقروءة وسجل التدقيقوفق مدة الحفظ المختارة
تسحب صندوق البريد المحاسبي (IMAP)إن كان السحب مُعدًّا
تفرّغ طابور خطافات الويب بإعادات محاولاتهفي كل مرور
تنشئ النسخة التلقائية وترسلها خارجًامتى انقضت المدة الفاصلة

واستدعاء النص البرمجي أكثر من المدة المضبوطة لا يعني نسخًا أكثر: فلا شيء ينطلق قبل أجله. وعلى استضافة تحدّ المجدول بمرور كل ربع ساعة، استبدل * * * * * بـ */15 * * * * — وستخرج خطافات الويب بهذا التأخير فحسب.

على استضافة بلا مجدول

بعض الباقات المبتدئة لا تقدّمه. ولا يكفي نداء HTTP من خدمة خارجية (حارس الإتاحة وما شابهه): فـcron.php يرفض التنفيذ خارج سطر الأوامر، تحديدًا كي لا يستطيع عنوان عام أن يُطلق نسخة احتياطية. وفي هذه الحال تُطلَق النسخة يدويًّا من شاشة النسخ الاحتياطية.

٧. المعالج#

افتح نطاقك. وما دام لا حساب موجودًا، يعيد كل عنوان التوجيه إلى /installation. والمعالج يتّسع لصفحة واحدة:

  1. المتطلبات، مفحوصةً ومعروضة — وما يمنع مُعلَّم بذلك.
  2. رمز التثبيت، إن كان مضبوطًا.
  3. الشركة: الاسم الظاهر في كل مكان، وعدد أيام الإجازة السنوية الممنوح لكل موظف جديد غير مستقل.
  4. اللغة الافتراضية للنسخة، من بين 16 لغة.
  5. حساب الإدارة: عنوان وكلمة مرور من اثني عشر محرفًا على الأقل.

ويُكتب كل شيء في معاملة واحدة. وما إن يوجد حساب حتى يعيد /installation التوجيه إلى صفحة الدخول: فالمعالج أغلق نفسه، ولا ملف يُحذف يدويًّا.

٨. التأكد من أن كل شيء يعمل#

# الاختبارات، على الجهاز (لا تكتب في قاعدتك)
php tests/run.php

# الترجمات: لا شاشة تستعمل مفتاحًا غائبًا عن القواميس
php tools/check-keys.php

ثم في الواجهة: أنشئ عضوًا، وادخل به، وأودع مرفقًا، وأطلق نسخة احتياطية يدويًّا وتحقّق منها من شاشة النسخ الاحتياطية. هذه الحركات الأربع تمسّ القاعدة والملفات والصلاحيات والأرشيف — أي كل ما ينبغي للاستضافة أن تحسنه.

٩. الترقية#

# ١. نسخة احتياطية أولًا، دائمًا
php tools/cron.php                     # أو شاشة النسخ الاحتياطية

# ٢. استبدل الشفرة لا البيانات
#    app/ public/ tools/ tests/ VERSION  ← تُستبدل
#    data/ config.php                    ← تبقى كما هي

# ٣. القاعدة تحدّث نفسها عند أول طلب

يتطوّر المخطط بهجرات متماثلة القوة: فإعادة تشغيل الترقية لا تكسر شيئًا. ونسخة من إصدار Node ونسخة من إصدار PHP تتقاسمان المخطط نفسه — 142 جدولًا و 1311 عمودًا — وكلمات المرور نفسها: فقاعدة تنتقل من إحداهما إلى الأخرى بلا تحويل.

١٠. النسخ الاحتياطي حقًّا#

يحمل أرشيف tar.gz القاعدةَ (منسوخةً بـVACUUM INTO، فهي متماسكة حتى أثناء كتابة) ومجلداتِ الملفات الخمسة: صور الملفات الشخصية، والسير الذاتية الواردة، والخزنة، وملف التوقيع، والمستندات الواردة. ويحمل كل ملف فيه بصمته SHA-256، ويُتحقَّق منها عند الاستعادة.

أرشيف يبقى على الخادم الذي يحميه لا يحمي شيئًا: فاضبط وجهةً بعيدة (FTPS أو Google Drive) من شاشة النسخ الاحتياطية. والتفصيل في صفحة النسخ الاحتياطي والاستعادة.

جرّبه محليًّا أولًا#

cp config.sample.php config.php
php -S localhost:8000 -t public public/index.php

خادم PHP المدمج يكفي لاكتشاف المنتج. وهو لا يصلح للإنتاج: سيرورة واحدة، وبلا TLS، وبلا حدّ للحِمل.

توثيق Toutadmin — بُني في 2026-09-13. موقع مستقل، منفصل عن البرنامج.