سيكو هذا الموضوع إن شاء الله، أساسا خاص بأكواد البرمجة الخاصة بـمشروع : إنشاء موقع بالإعتماد على إطار العمل الشهير بوتستراب ليس هذا فقط، بل يمكن تعديل الأكواد و إدراجها على حسب إحتياجاتك في مشاريعك ككل. يمكن كذلك لمن يريد المشاركة، أن يعدلها (الأكواد) إلى الأفضل إن أمكن ذلك. و يدرج ما قام بالتعديل عليه هنا في هذا الموضوع لتعم الفائدة. هناك أكثر من طريقة للبرمجة كما هو معلوم. لمن ليس له دراية ببرمجة المواقع، أقترح عليه زيارة هذا المنتدى الذي يعج بمواضيع ستكون مفيدة بإذن الله. شخصيا، أعتبره ممتاز يــتــبــع
إنشاء ملف "autoload.php" سيكون الملف، هو المسؤول عن تحميل جميع الفئات التي سننشأها إن شاء الله، تلقائيا. يعني هو ملف تحميل تلقائي كما يدل اسمه. الفئات (classes)، ستكون مسبوقة باسم المساحة (namespace) itabcode\classes لهذا، الملف سيكون مسؤول على استدعاء كل فئة مسبوقة باسم المساحة المذكور فقط. لقد تم بالفعل إنشاء الملف المذكور في وقت سابق هنا ، و تم إدراجه على المسار "C:/xampp/htdocs/itabcode/vendor" كود PHP: <?php /* دالة التحميل التلقائي ستشمل الفئات المسبوقة باسم المساحة itabcode\classes */ spl_autoload_register(function ($class) { // بادئة مساحة الاسم $prefix = 'itabcode\\classes'; // التحقق مما إذا كانت فئة من المشروع $len = strlen($prefix); if (strncmp($prefix, $class, $len) !== 0) return; // الفئة ليست من المشروع // الحصول على اسم فئة نسبي وإزالة اسم مساحة الاسم $className = substr($class, $len); // مجلد الفئات الأساسية $baseDir = dirname(__DIR__) . '/classes/'; // parent بما في ذلك $file = $baseDir . str_replace('\\', DIRECTORY_SEPARATOR, $className) . '.php'; // إذا كان الملف موجودًا نقوم باستدعائه if (file_exists($file)) { require $file; } });?> يــتــبــع
إنشاء ملفات الفئات "Database.php" و "Validation.php" و ملف "register_login_submit.php" الذي سيكون المسؤول عن عمليات التسجيل و تسجيل الدخول. سنعتبر إنشاء المجلدين "vendor" و "classes" قد تم على المسار التالي: "C:/xampp/htdocs/itabcode/classes" سنقوم بإنشاء ملفات الفئات المذكورة، حيث يتم إدراجها كما يلي: "C:/xampp/htdocs/itabcode/classes/Database.php" "C:/xampp/htdocs/itabcode/classes/Vٍalidation.php" ثم نقوم بإنشاء الملف "register_login_submit.php" و نقوم بإدراجه في المجلد الرئيسي للمشروع، على المسار "C:/xampp/htdocs/itabcode/register_login_submit.php" عملية إرسال و استقبال البيانات ستتم عن طريق الـ "Ajax" داخل الملف "C:/xampp/htdocs/itabcode/js/main.js" الذي تم إدراجه أسفل الصفحات "register.php" و "login.php" مثل ما هو مبين هنا أما عملية التحقق من الخانات الفارغة و غيرها، تتم داخل الملف "C:/xampp/htdocs/itabcode/register_login_submit.php" ملف "register_login_submit.php" كود PHP: <?phpsession_start();require_once realpath(__DIR__ . '/vendor/autoload.php');use itabcode\classes\Validation;use itabcode\classes\Database;if (strtoupper($_SERVER['REQUEST_METHOD']) === 'POST') { $user = null; $db = null; $json = []; $validation = new Validation(); $reg = $validation->post('reg_btn'); if ($reg === 'reg') { $isValid = function () use ($validation): bool { $validation->requireField('f_name', 'الإسم الأول مطلوب') ->requireField('l_name', 'إسم العائلة مطلوب') ->requireField('r_email', 'البريد الالكتروني مطلوب'); $validation->validateEmail('r_email','الرجاء إدخال عنوان بريد إلكتروني صالح') ->isUniqueEmail('r_email', 'قم بإدخال بريد إلكتروني مختلف'); $validation->requireField('uname', 'إسم المستخدم مطلوب') ->isUniqueUname('uname', 'قم بإدخال إسم مستخدم آخر'); $validation->requireField('pwd', 'كلمة المرور مطلوبة') ->requireField('cpass', 'أعد كلمة المرور في خانة الإعادة') ->requireField('therms', 'يجب أن توافق على الشروط والأحكام الخاصة بنا'); return $validation->passes(); }; if ($isValid()) { $db = new Database(); $birth = $validation->post('birthday'); $gender = $validation->post('gender'); if ($birth) $db->data('birthday', $birth); if ($birth) $db->data('gender', $gender); $db->data('f_name', $validation->post('f_name')) ->data('l_name', $validation->post('l_name')) ->data('email', $validation->post('r_email')) ->data('uname', $validation->post('uname')) ->data('password', password_hash($validation->post('pwd'), PASSWORD_DEFAULT)) ->data('created', time()) ->insert('users'); $json['success'] = 'تم إنشاء حسابك بنجاح'; $json['redirectTo'] = 'index.php'; } else { $json['errors'] = $validation->flattenMessages(); } } else { $isValid = function () use ($validation): bool { $validation->requireField('l_email', 'البريد الإلكتروني مطلوب') ->validateEmail('l_email', 'الرجاء إدخال عنوان بريد إلكتروني صالح') ->requireField('pass', 'كلمة المرور مطلوبة'); return $validation->passes(); }; if ($isValid()) { $email = $validation->post('l_email'); $password = $validation->post('pass'); $db = new Database(); $user = $db->where('email=?', $email)->fetch('users'); if (!$user or !(password_verify($password, $user->password))) { $_SESSION['uname'] = ''; $json['errors'] = 'بيانات تسجيل الدخول غير متطابقة'; } else { $_SESSION['uname'] = $user->uname; $json['success'] = 'أهلا بك ' . $_SESSION['uname']; $json['redirectTo'] = 'index.php'; } }else { $json['errors'] = $validation->flattenMessages(); } } echo json_encode($json);} ملف "Database.php" كود PHP: <?phpdeclare(strict_types=1);namespace itabcode\classes;use PDO;use PDOException;use PDOStatement;class Database{ private static $connection; private $selects = [], $wheres = [], $table, $data = [], $bindings = []; function __construct() { if (!$this->isConnected()) { $this->connect(); } } private function isConnected(): bool { return static::$connection instanceof PDO; } private function connect(): void { $options = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ, PDO::ATTR_EMULATE_PREPARES => false, PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8' ]; try { static::$connection = new PDO('mysql:host=localhost;dbname=itabcode', 'root', '', $options); } catch (PDOException $exception) { exit('فشل الاتصال بقاعدة البيانات<br>' . $exception->getMessage()); } } function select(...$selects): Database { //PHP >=5.6 (...) يمكنك استخدام عامل التشغيل //وإلا ، استخدم السطر التالي للحصول على كافة الوسائط التي تم تمريرها //$selects = func_get_args(); $this->selects = array_merge($this->selects, $selects); return $this; } private function fetchStatement(): string { $sql = 'SELECT '; if ($this->selects) { $sql .= implode(',', $this->selects); } else { $sql .= '*'; } $sql .= ' FROM ' . $this->table . ' '; if ($this->wheres) { $sql .= ' WHERE ' . implode(' ', $this->wheres) . ' '; } return $sql; } function table($table): Database { $this->table = $table; return $this; } function from($table): Database { return $this->table($table); } function fetch($table = null): mixed { if ($table) { $this->table($table); } $sql = $this->fetchStatement(); $result = $this->query($sql, $this->bindings)->fetch(); return $result; } function fetchAll($table = null): mixed { if ($table) { $this->table($table); } $sql = $this->fetchStatement(); $query = $this->query($sql, $this->bindings); $results = $query->fetchAll(); return $results; } function query(): PDOStatement { $bindings = func_get_args(); $sql = array_shift($bindings); if (count($bindings) == 1 && is_array($bindings[0])) { $bindings = $bindings[0]; } try { $query = static::$connection->prepare($sql); foreach ($bindings as $key => $value) { $query->bindValue($key + 1, $value); } $query->execute(); return $query; } catch (PDOException $e) { echo $sql; die($e->getMessage()); } } function addToBindings($value): void { if (is_array($value)) { $this->bindings = array_merge($this->bindings, array_values($value)); } else { $this->bindings[] = $value; } } private function setFields(): string { $sql = ''; foreach (array_keys($this->data) as $key) { $sql .= '`' . $key . '` = ? , '; } $sql = rtrim($sql, ', '); return $sql; } function data($key, $value = null): Database { if (is_array($key)) { $this->data = array_merge($this->data, $key); $this->addToBindings($key); } else { $this->data[$key] = $value; $this->addToBindings($value); } return $this; } function insert($table = null): Database { if ($table) { $this->table($table); } $sql = 'INSERT INTO ' . $this->table . ' SET '; $sql .= $this->setFields(); $this->query($sql, $this->bindings); return $this; } function where(...$bindings): Database { //$bindings = func_get_args(); $sql = array_shift($bindings); $this->addToBindings($bindings); $this->wheres[] = $sql; return $this; } function __destruct() { static::$connection = null; }} أخيرا و ليس آخر، ملف تسجيل الخروج "logout.php" كود PHP: <?phpsession_start();session_destroy();if (!headers_sent()) header('Location: index.php');?> كودات البرمجة تمت لتكون متوافقة مع آخر إصدار للـ ¨PHP"
التعديل على الملفات سنقوم اليوم بالتعديل على جميع الملفات الـ PHP المدرجة، بكيفية تكون داعمة لكل الحقول الموجودة داخل اسنمارة التسجيل و تسجيل الدخول. أولا، نقوم بالتعديل على الملف "itabcode_sql" و تحديدا على الأعمدة "uname" و "birth" . العمود "birth" سيكون ياسم "birthday" و سيكون من نوع "varchar" . والعمود uname سيكون هو أيظا unique مثل العمود "email" . نص الإستعلام كامل لإنشاء الجدول "users" كود PHP: CREATE TABLE `users` ( `id` int(10) UNSIGNED NOT NULL, `f_name` varchar(50) NOT NULL, `l_name` varchar(50) NOT NULL, `email` varchar(155) NOT NULL, `uname` varchar(50) NOT NULL, `password` varchar(255) NOT NULL, `birthday` varchar(20) DEFAULT NULL, `gender` tinytext DEFAULT NULL, `avatar` varchar(155) DEFAULT NULL, `created` int(11) NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;ALTER TABLE `users` ADD PRIMARY KEY (`id`), ADD UNIQUE KEY `email` (`email`), ADD UNIQUE KEY `uname` (`uname`); بعد إتمام هذه العملية، ننتقل إلى اتعديل على الملفات ملف التحميل التلقائي: "C:/xampp/htdocs/itabcode/vendor/autoload.php" سنضيف لهذا الملف تفقد و مراقبة اسم المساحة "namespace" بطريقة لا تسمح باستعمال أسماء مساحة متعددة. كود PHP: if (strpos($class, $prefix) !== 0) { throw new Exception( 'أداة التحميل التلقائي لتسجيلات متعددة لمساحات الأسماء غير ممكنة' );} الملف بعد التعديل: كود PHP: /* دالة التحميل التلقائي ستشمل الفئات المسبوقة باسم المساحة itabcode\classes */ spl_autoload_register(function ($class) { // بادئة مساحة الاسم $prefix = 'itabcode\\classes'; if (strpos($class, $prefix) !== 0) { throw new Exception( 'أداة التحميل التلقائي لتسجيلات متعددة لمساحات الأسماء غير ممكنة' ); } // التحقق مما إذا كانت فئة من المشروع $len = strlen($prefix); if (strncmp($prefix, $class, $len) !== 0) return; // الفئة ليست من المشروع // الحصول على اسم فئة نسبي وإزالة اسم مساحة الاسم $className = substr($class, $len); // مجلد الفئات الأساسية $baseDir = dirname(__DIR__) . '/classes/'; // parent y compris $file = $baseDir . str_replace('\\', DIRECTORY_SEPARATOR, $className) . '.php'; // إذا كان الملف موجودًا نقوم باستدعائه if (file_exists($file)) { require $file; } }); ملف "C:/xampp/htdocs/itabcode/classes/Database.php" بعد التعديل كود PHP: <?phpdeclare(strict_types=1);namespace itabcode\classes;use PDO;use PDOException;use PDOStatement;class Database{ private static $connection; private $selects = [], $wheres = [], $table, $data = [], $bindings = []; /** * باني الفئة Constructor */ function __construct() { if (!$this->isConnected()) { $this->connect(); } } /** * تحقق مما إذا كان الاتصال نشطًا * * @return boolean */ private function isConnected(): bool { return static::$connection instanceof PDO; } /** * تحقيق الإتصال بقاعدة البيانات * * @return void */ private function connect(): void { $options = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ, PDO::ATTR_EMULATE_PREPARES => false, PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8' ]; try { static::$connection = new PDO('mysql:host=localhost;dbname=itabcode', 'root', '', $options); } catch (PDOException $exception) { exit('فشل الاتصال بقاعدة البيانات<br>' . $exception->getMessage()); } } /** * تعيين بند التحديد * * @param mixed ...$selects * @return Database */ function select(mixed ...$selects): Database { //PHP >=5.6 (...) يمكنك استخدام عامل التشغيل //وإلا ، استخدم السطر التالي للحصول على كافة الوسائط التي تم تمريرها //$selects = func_get_args(); $this->selects = array_merge($this->selects, $selects); return $this; } /** * إعداد بيان التحديد * * @return string */ private function fetchStatement(): string { $sql = 'SELECT '; if ($this->selects) { $sql .= implode(',', $this->selects); } else { $sql .= '*'; } $sql .= ' FROM ' . $this->table . ' '; if ($this->wheres) { $sql .= ' WHERE ' . implode(' ', $this->wheres) . ' '; } return $sql; } /** * قم بتعيين اسم الجدول * * @param string $table * @return Database */ function table(string $table): Database { $this->table = $table; return $this; } /** * قم بتعيين اسم الجدول * * @param string $table * @return Database */ function from(string $table): Database { return $this->table($table); } /** * جدول الجلب. سيؤدي هذا إلى إرجاع سجل واحد فقط * * @param mixed $table * @return mixed */ function fetch(mixed $table = null): mixed { if ($table) { $this->table($table); } $sql = $this->fetchStatement(); $result = $this->query($sql, $this->bindings)->fetch(); return $result; } /** * إحضار كل السجلات من الجدول * * @param mixed $table * @return mixed */ function fetchAll(mixed $table = null): mixed { if ($table) { $this->table($table); } $sql = $this->fetchStatement(); $query = $this->query($sql, $this->bindings); $results = $query->fetchAll(); return $results; } /** * SQL تنفيذ البيان المحدد * * @return PDOStatement */ function query(): PDOStatement { $bindings = func_get_args(); $sql = array_shift($bindings); if (count($bindings) == 1 && is_array($bindings[0])) { $bindings = $bindings[0]; } try { $query = static::$connection->prepare($sql); foreach ($bindings as $key => $value) { $query->bindValue($key + 1, $value); } $query->execute(); return $query; } catch (PDOException $e) { echo $sql; die($e->getMessage()); } } /** * أضف القيمة المعطاة إلى الارتباطات * * @param mixed $value * @return void */ function addToBindings(mixed $value): void { if (is_array($value)) { $this->bindings = array_merge($this->bindings, array_values($value)); } else { $this->bindings[] = $value; } } /** * قم بتعيين الحقول للإدراج والتحديث * * @return string */ private function setFields(): string { $sql = ''; foreach (array_keys($this->data) as $key) { $sql .= '`' . $key . '` = ? , '; } $sql = rtrim($sql, ', '); return $sql; } /** * قم بتعيين البيانات التي سيتم تخزينها في جدول قاعدة البيانات * * @param mixed $key * @param mixed $value * @return Database */ function data(mixed $key, mixed $value = null): Database { if (is_array($key)) { $this->data = array_merge($this->data, $key); $this->addToBindings($key); } else { $this->data[$key] = $value; $this->addToBindings($value); } return $this; } /** * أدخل البيانات في قاعدة البيانات * * @param mixed $table * @return Database */ function insert(mixed $table = null): Database { if ($table) { $this->table($table); } $sql = 'INSERT INTO ' . $this->table . ' SET '; $sql .= $this->setFields(); $this->query($sql, $this->bindings); return $this; } /** * "Where" إضافة العبارة الجديدة * * @param mixed ...$bindings * @return Database */ function where(mixed ...$bindings): Database { //$bindings = func_get_args(); $sql = array_shift($bindings); $this->addToBindings($bindings); $this->wheres[] = $sql; return $this; } function __destruct() { static::$connection = null; }} ملف" C:/xampp/htdocs/itabcode/register_login_submit.php" بعد النعديل كود PHP: <?phpsession_start();require_once realpath(__DIR__ . '/vendor/autoload.php');use itabcode\classes\Validation;use itabcode\classes\Database;if (strtoupper($_SERVER['REQUEST_METHOD']) === 'POST') { $user = null; $db = null; $json = []; $validation = new Validation(); $reg = $validation->post('reg_btn'); if ($reg === 'reg') { // التسجيل $isValid = function () use ($validation): bool { $validation->requireField('f_name', 'الإسم الأول مطلوب') ->requireField('l_name', 'إسم العائلة مطلوب') ->requireField('r_email', 'البريد الالكتروني مطلوب'); $validation->validateEmail('r_email', 'الرجاء إدخال عنوان بريد إلكتروني صالح') ->isUniqueEmail('r_email', 'قم بإدخال بريد إلكتروني مختلف'); $validation->requireField('uname', 'إسم المستخدم مطلوب') ->isUniqueUname('uname', 'قم بإدخال إسم مستخدم آخر'); $validation->requireField('pwd', 'كلمة المرور مطلوبة') ->requireField('cpass', 'أعد كلمة المرور في خانة الإعادة') ->requireField('therms', 'يجب أن توافق على الشروط والأحكام الخاصة بنا'); return $validation->passes(); }; if ($isValid()) { // لا اخطاء $birth = $validation->post('birthday'); $gender = $validation->post('gender'); $avatar = $validation->emptyImage('r_avatar'); $db = new Database(); if ($birth) $db->data('birthday', $birth); if ($gender) $db->data('gender', $gender); if (!is_null($avatar)) { $img_name = $validation->saveImage('r_avatar', true); if (!is_null($img_name)) { $db->data('avatar', $img_name); } } $db->data('f_name', $validation->post('f_name')) ->data('l_name', $validation->post('l_name')) ->data('email', $validation->post('r_email')) ->data('uname', $validation->post('uname')) ->data('password', password_hash($validation->post('pwd'), PASSWORD_DEFAULT)) ->data('created', time()) ->insert('users'); $json['success'] = 'تم إنشاء حسابك بنجاح'; $json['redirectTo'] = 'index.php'; } else { $json['errors'] = $validation->flattenMessages(); } } else { // تسجيل الدخول $isValid = function () use ($validation): bool { $validation->requireField('l_email', 'البريد الإلكتروني مطلوب') ->validateEmail('l_email', 'الرجاء إدخال عنوان بريد إلكتروني صالح') ->requireField('pass', 'كلمة المرور مطلوبة'); return $validation->passes(); }; if ($isValid()) { // لا اخطاء $email = $validation->post('l_email'); $password = $validation->post('pass'); $db = new Database(); $user = $db->where('email=?', $email)->fetch('users'); if (!$user or !(password_verify($password, $user->password))) { $_SESSION['uname'] = ''; $_SESSION['avatar'] = ''; $json['errors'] = 'بيانات تسجيل الدخول غير متطابقة'; } else { $_SESSION['uname'] = $user->uname; $_SESSION['avatar'] = $user->avatar; $json['success'] = 'أهلا بك ' . $_SESSION['uname']; $json['redirectTo'] = 'index.php'; } } else { $_SESSION['uname'] = ''; $_SESSION['avatar'] = ''; $json['errors'] = $validation->flattenMessages(); } } echo json_encode($json);} ملف "C:/xampp/htdocs/itabcode/classes/Validation.php" بعد التعديل كود PHP: <?phpdeclare(strict_types=1);namespace itabcode\classes;class Validation{ private ?array $errors = []; private $error; /** * تمكن من الحصول على قيمة للمفتاح الممرر كإعدادات * * @param string $key * @param mixed $default * @return string|null */ function post(string $key, mixed $default = null): mixed { $value = isset($_POST[$key]) ? $_POST[$key] : $default; if(!is_null($value)) { if (is_array($value)) { // أزل أي مسافة بيضاء إذا كانت هناك قيمة $value = array_filter($value); } elseif (is_string($value)) { $value = addslashes($value); $value = trim($value); $value = htmlentities($value); } else { $value = htmlentities($value); } } return $value; } /** * السماح للحصول على امتداد الملف المرفوع * * @param string $str * @return string */ function getExtension(string $str): string { $i = strrpos($str, "."); if (!$i) { return ""; } $l = strlen($str) - $i; $ext = substr($str, $i + 1, $l); return $ext; } /** * يمكن التحقق مما إذا كان ملف الإدخال فارغًا * * @param mixed $key * @return mixed */ function emptyImage(mixed $key): mixed { if ($_FILES[$key]['size'] == 0 && $_FILES[$key]['name'] == '') { $this->addError('r_avatar', 'الصورة الرمزية مطلوبة'); return null; } return $_FILES[$key]; } /** * احفظ الصورة التي تم تحميلها * * @param mixed $key * @param string $uploaddir * @param boolean $res * @return mixed */ function saveImage(mixed $key, bool $res = false): mixed { $valid_formats = [ 'jpg', 'jpeg', 'pjpeg', 'png', 'gif', 'jpeg', 'webp', 'x-webp', ]; $image = $this->emptyImage($key); if (is_null($image)) { return null; } $this->error = $image['error']; if ($this->error != UPLOAD_ERR_OK) { return null; } $old_image_name = $image['name']; $size = $image['size']; if (strlen($old_image_name)) { $ext = strtolower($this->getExtension($old_image_name)); if ( strpos($image['type'], 'image/') === 0 and in_array($ext, $valid_formats) ) { if ($size < (1024 * 1024)) { // حجم الصورة 1 ميغا بايت كحد أقصى $usename = $this->post('uname'); $niew_image_name = strtolower($usename) . '.' . $ext; $uploadedfile = $image['tmp_name']; if (move_uploaded_file($uploadedfile, dirname(__DIR__) . '/images/avatars/' . $niew_image_name)) { if ($res) { $resize = new Image(\dirname(__DIR__) . '/images/avatars/' . $niew_image_name); $err_resize = $resize->getErrors(); $err_resize = (array) $err_resize; if (!empty($err_resize)) { foreach ($err_resize as $key => $value) { $err_resize[$key] = $value; $this->addError('r_avatar', $value); } } else { if ($resize->resizeTo(100, 100)) { $resize->saveImg(dirname(__DIR__) . '/images/avatars/' . $niew_image_name); } } unset($resize); } return $niew_image_name; } else { $this->addError('r_avatar', 'تعذر تنزيل الصورة'); } } else { $this->addError('r_avatar', 'حجم ملف الصورة بحد أقصى 1 ميجا بايت'); } } else { $this->addError('r_avatar', 'تنسيق غير صالح'); } } else { $this->addError('r_avatar', 'لم تقم بإدخال ملف'); } return null; } /** * تحقق مما إذا كانت حاوية الخطأ تحتوي على الاسم المحدد * * @param string $inputName * @return boolean */ private function hasErrors(string $inputName): bool { return array_key_exists($inputName, $this->errors)? true:false; } /** * أضف اسم خطأ محدد إلى حاوية الخطأ * * @param string $inputName * @param string $errMsg * @return void */ private function addError(string $inputName, string $errMsg): void { $this->errors[$inputName] = $errMsg . ' ️'; } /** * أضف التزامًا إلى الحقل المحدد * * @param mixed $inputName * @param mixed $custErrMsg * @return Validation */ function requireField(mixed $inputName, mixed $custErrMsg = null): Validation { if ($this->hasErrors($inputName)) { return $this; } $inputValue = $this->post($inputName); if ($inputValue === '' || is_null($inputValue)) { $msg = $custErrMsg ?: sprintf('%s مطلوب', ucfirst($inputName)); $this->addError($inputName, $msg); } return $this; } /** * تحقق مما إذا كان البريد الإلكتروني المعطى بريدًا إلكترونيًا صالحًا * * @param string $inputName * @param mixed $customErrorMessage * @return Validation */ function validateEmail(string $inputName, mixed $customErrorMessage = null): Validation { if ($this->hasErrors($inputName)) { return $this; } $inputValue = $this->post($inputName); if (!filter_var($inputValue, FILTER_VALIDATE_EMAIL)) { $message = $customErrorMessage ?: sprintf('%s ليس بريدًا إلكترونيًا صالحًا', ucfirst($inputName)); $this->addError($inputName, $message); } return $this; } /** * تحقق مما إذا كان البريد الإلكتروني المحدد فريدًا في قاعدة البيانات * * @param string $inputName * @param mixed $customErrorMessage * @return boolean */ function isUniqueEmail(string $inputName, mixed $customErrorMessage = null) { if ($this->hasErrors($inputName)) { return $this; } $inputValue = $this->post($inputName); $db = new Database(); $result = $db->select('email') ->from('users') ->where('email= ?', $inputValue) ->fetch(); if ($result) { $message = $customErrorMessage ?: sprintf('%s أدخل بريد آخر غير ', ucfirst($inputValue)); $this->addError($inputName, $message); } } /** * النحقق من إذا كان اسم المستخدم فريد في قاعدة البيانات * * @param string $inputName * @param mixed $customErrorMessage * @return boolean */ function isUniqueUname(string $inputName, mixed $customErrorMessage = null) { if ($this->hasErrors($inputName)) { return $this; } $inputValue = $this->post($inputName); $db = new Database(); $result = $db->select('uname') ->from('users') ->where('uname= ?', $inputValue) ->fetch(); if ($result) { $message = $customErrorMessage ?: printf('%s قم بإدخال إسم مسنخدم غير ', ucfirst($inputValue)); $this->addError($inputName, $message); } } /** * تفكيك الأخطاء ز عرضها * * @return string */ function flattenMessages(): string { return implode('<br>', $this->errors); } /** * تحقق مما إذا كانت حاوية الخطأ فارغة * * @return boolean */ function passes(): bool { return empty($this->errors) ? true : false; }} ملاحظة: الملف الأخير سيكون فيه شرط يتطلب فئة "class image" سينم إدراجها في و قت لاحق. يــتــبــع
عفوا نسيت إدراج ملف "C:/xampp/htdocs/itabcode/js/main.js" الدي هو بدوره تم تعديله كود PHP: $(function () { 'use strict'; // Array.isArray() إذا كان المنصفح لا يدعم الدالة، نقوم بإنشائها if (!Array.isArray) { Array.isArray = function(arg) { return Object.prototype.toString.call(arg) === '[object Array]'; }; } // معرض الصور const gallery = document.getElementById('lightgallery'); if (gallery) lightGallery(gallery); // خاص بمراقبة الصورة إطا ما نم الرفغ const img = document.getElementById('avatar'); if (img) img.addEventListener('change', selectSingleFile); function selectSingleFile(e) { var file = e.target.files; // كائن "FileList" var f = file[0]; var output = document.getElementById('output'); // معالجة ملفات الصور فقط if (!f.type.match('image.*')) { output.classList.add('alert', 'alert-danger'); output.innerHTML = ''; output.innerHTML = '<p>يجب أن يكون الملف الذي تريد تحميله صورة صالحة</p>'; return false; } else { output.classList.remove('alert', 'alert-danger'); $('#form-results').removeClass().addClass('d-none'); } var reader = new FileReader(); // الإغلاق لالتقاط معلومات الملف reader.onload = (function (file) { return function (e) { // عرض الصورة المصغرة var span = document.createElement('span'); span.innerHTML = ['<img class="small_avatar" src="', e.target.result, '" alt="', encodeURI(file.name), '" title="', encodeURI(file.name), '"/>'].join(''); output.innerHTML = ""; output.insertBefore(span, null); }; })(f); //"url" قراءة ملف الصورة كعنوان للبيانات reader.readAsDataURL(f); return true; } /* تعطيل عمليات إرسال النموذج إذا كانت هناك حقول غير صالحة */ // تحديد جميع النماذج التي نريد تطبيق أنماط تحقق مخصصة عليها var frms = document.querySelectorAll('.needs-validation'); if (typeof(frms) !== 'undefined') { //نقوم بإنشاء حلقة دوران و نمنع بعث البيانات Array.prototype.slice.call(frms) .forEach(function(frm) { frm.addEventListener('submit', function(event) { if (!frm.checkValidity()) { event.preventDefault(); event.stopPropagation(); } frm.classList.add('was-validated'); }, false) }); } // تغيير الأيقونة داخل خانة كلمة المرور عند الضفط عليها $('.toggle-password').on('click', function () { $(this).toggleClass('fa-lighte fa-eye-slash'); const input = $($(this).data('id')); if (input.attr('type') == 'password') { input.attr('type', 'text'); } else { input.attr('type', 'password'); } }); // register/login forms $(document).on('click', '.submit-btn', function (e) { e.preventDefault(); var btn = $(this), frm = btn.parents('.form'), url = frm.attr('action'), src_avatar = frm.data('loader'), flag = false, formResults = frm.find('#form-results'); if (flag === true) { return false; } const data = new FormData(frm[0]); $.ajax({ url: url, data: data, type: 'POST', dataType: 'json', beforeSend: function () { flag = true; btn.attr('disabled', true); formResults.removeClass().addClass('alert alert-info').html('<p class="text-center"><img src="' + src_avatar + '" alt="Loading..."></p>'); }, success: function (results) { setTimeout(function () { if (results.errors) { $('.required').each(function(){ $(this).removeClass("has-error"); if($(this).val() == '') { $(this).addClass("has-error"); } }); formResults.removeClass().addClass('alert alert-danger').html(results.errors); btn.removeAttr('disabled'); flag = false; } else if (results.success) { formResults.removeClass().addClass('alert alert-success').html(results.success); } }, 1000); if (results.redirectTo) { setTimeout(function () { window.location.href = results.redirectTo; }, 4000); } }, cache: false, processData: false, contentType: false, }); }); // إن وجد AOS plugin تفعيل الـ const allDiv = document.querySelectorAll('div[data-aos]'); if (typeof(allDiv) != 'undefined' && allDiv != null) { allDiv.forEach(div => { AOS.init({ mirror: true }); }); } // (tooltip) تفعيل تلميح أداة التمهيد let btns_toggle = document.querySelectorAll('[data-bs-toggle="tooltip"]'); btns_toggle.forEach(function (btn) { if (btn) { // التحقق مما إذا كان العنصر موجودًا var tooltipBtn = new bootstrap.Tooltip(btn); btn.addEventListener('mouseleave', function () { // إخفاء التلميح بشكل صريح ، لأنه بعد النقر عليه يظل مركزًا (لأنه زر) ، لذلك سيظل تلميح الأداة مرئيًا حتى يتم نقل التركيز بعيدًا tooltipBtn.hide(); }); } });}); لا ننسي إدراج الملف أسفل الصفحات
إنشاء ملف الفئة "C:/xampp/htdocs/itabcode/classes/Image.php" سيكون هذا الملف المسؤول عن كل عمليات الصور. حالياـ سنستعين به لمراقبة الملف المرفوع و لتصغير حجم الصورة بعد رفعها. كود PHP: <?phpdeclare(strict_types=1);namespace itabcode\classes;class Image{ private $mime, $image, $newImage, $errors = []; private $origWidth, $origHeight, $resizeWidth, $resizeHeight; /** * Constructor * * @param string $filename */ function __construct(string $filename) { if (file_exists($filename)) { $this->populateImg($filename); // throw new \Exception('المعلن عنه،لم يتم العثور عليه' . $filename . ' ملف'); } else { $this->errors['resize'] ='المعلن عنه،لم يتم العثور عليه' . $filename . ' ملف'; } return (!empty($this->errors))? $this:false; } /** * تعيين الصورة * * @param string $filename * @return void */ private function populateImg(string $filename): void { $size = getimagesize($filename); $this->mime = $size['mime']; switch ($this->mime) { // JPG أو JPEG الصورة بتنسيق case 'image/jpg': case 'image/jpeg': case 'image/pjpeg': $this->image = imagecreatefromjpeg($filename); break; // GIF الصورة بتنسيق case 'image/gif': $this->image = imagecreatefromgif($filename); break; // PNG الصورة بتنسيق case 'image/png': case 'image/x-png': $this->image = imagecreatefrompng($filename); break; // web الصورة بتنسيق case 'image/webp': case 'image/x-webp': $this->image = imagecreatefromwebp($filename); break; // النتسيق غير موجود default: $this->errors['resize'] = 'نوع الملف غير مسموح به ، الرجاء استخدام نوع ملف آخر '; } $this->origWidth = imagesx($this->image); $this->origHeight = imagesy($this->image); } /** * تغيير حجم الصورة إلى قيم معينة جديدة * * @param mixed $width * @param mixed $height * @return boolean */ function resizeTo(mixed $width, mixed $height): bool { $this->resizeWidth = intval($width); $this->resizeHeight = intval($height); $this->newImage = imagecreatetruecolor($this->resizeWidth, $this->resizeHeight); if ($this->newImage) { if (imagecopyresampled($this->newImage, $this->image, 0, 0, 0, 0, $this->resizeWidth, $this->resizeHeight, intval($this->origWidth), intval($this->origHeight))) { return true; } } return false; } /** * حفظ الصورة بالقيم الجديدة * * @param string $savePath * @param integer $imageQuality * @return void */ function saveImg(string $savePath, int $imageQuality = 100) { defined('IMG_WEBP') or define('IMG_WEBP', 32); switch ($this->mime) { case 'image/jpg': case 'image/jpeg': case 'image/pjpeg': // التأكد من أن لغة البرمجة تدعم هذا النوع من الملفات if (imagetypes() & IMG_JPG) { imagejpeg($this->newImage, $savePath, 85); } break; case 'image/gif': if (imagetypes() & IMG_GIF) { imagegif($this->newImage, $savePath); } break; case 'image/png': case 'image/x-png': $invertScaleQuality = 9 - round(($imageQuality / 100) * 9); if (imagetypes() & IMG_PNG) { imagepng($this->newImage, $savePath, (int) $invertScaleQuality); } break; case 'image/webp': case 'image/x-webp': if (imagetypes() & IMG_WEBP) { imagewebp($this->newImage, $savePath, 85); } break; default: imagejpeg($this->newImage, $savePath, 85); break; } imagedestroy($this->newImage); return $this; } /** * الحصول على كافة الأخطاء * * @return mixed */ function getErrors(): mixed { return $this->errors; } function __destruct() { $this->mime = null; $this->origWidth = null; $this->origHeight = null; }} مجلد المشروع تم التعديل عليه و إدراجه كمرفقات هنا . إلى هنا نكون قد أتممنا تقريبا أكثر من 90 بالمائة من المشروع. كل ما تبقى ملف sendmail.php الذي سيكون مسؤول عن تحليل ملف "contact.php" و تنفيذ الإتصال. يــتــبــع
إدراج مجلد "C:/xampp/sendmail" هو المجلد المسؤول عن بعث الرسائل محليا "on loclal" . يعني من خلال السرفر المحلي "XAMPP" . هناك طريقتان لفعل ذلك - إما أن يكون المجلد مدرج أو منصب عند أول تنصيب السرفر المحلي (هذا يتم عن طريق خانة الاختيار) - أو نقوم بتحميله و تنصيبه يدويا على المسار "C:/xampp/sendmail" رابط النحميل: كود: https://sourceforge.net/projects/send-mail/files/latest/download العملية تمت بنجاح. و بهذا نكون هيئنا البيئة لإنشاء ملفنا "C:/xampp/htdocs/itabcode/sendmail.php" الخطوة التالية بإذن الله، ستكون وضع الإعدادات الازمة في بعض ملفات المجلد يــتــبــع للتذكير كل ما أقوم به خاص بنظام التشغيل "windows"
إعدادات "xampp" و "sendmail" الخاصة بحسابات "GMAIL" في هذه المرحلة سنقوم بالتعديل على الملفين: "C:/xampp/php/php.ini" و "C:/xampp/sendmail/sendmail.ini" في إعدادات الملف "C:/xampp/sendmail/sendmail.ini" ، سنكون مطالبين بـ auth_username و auth_password لذا، نقوم بتسجيل الدخول على حسابنا gmail ، و من ثم نضغط على الصورة الرمزية من أعلى اليمين. نختار خيار إدارة حساب جوجل الخاص بك "Manage your google account" أو بالفرنسية "Gérer votre compte google" ستفتح على المتصفح نافذة مستقلة من على اليسار، توجد خيارات. نقوم بالختيار "securité" أو "security" ثم من على اليمين، يجب أن يكون خيار التحقق بخطوتين مفعل. إن لم يكن كذلك، قم بتفعيله. ثم نختار "Application Passwords" أو "Mots de passe des applications" على هذه النافذة، إن لم يكن لك كلمات مرور خاصة بالتطبيقات، قم بتحرير واحدة. عند الضغط على "Application Passwords"، ستفتح لك نافدة كما هو مبين في الصورة إختر "Messaging" أو "Messagerie" (الخانة رقم 1) ثم اختر "windows computer" أو " Ordinateur windows" (الخانة رقم 2) ثم إضعط على زر "Generate" أو " Générer" تمت العملية بنجاح، و أصبح لدينا كلمة مرور خاصة بالتطبيقات نقوم بنسخ و الصاق كلمة المرور و حفظها في مكان آمن، ثم قم بالضغط على الزر "ok" في المرحلة التالية، سنبدأ بإذن الله التعديل على الملفات المذكورة أعلاه. يــتــبــع
إعدادات "xampp" و "sendmail" الخاصة بحسابات "GMAIL" أولا، سنقوم بالتعديل على ملف "C:/xampp/php/php.ini" للقيام بذلك، كما تبين الصورة في هذه المشاركة. بعد تشغيل الخادم "XAMPP" ، من خلال قائمة هذا الأخير نقوم بالنقر فوق علامة التبويب "config" . ثم و من قائمة الخيارات نختر (php.ini) سيتم فتح ملف في المحرر الافتراضي للنظام. في هذا الملف المفتوح ، ابحث عن "mail function" نبحث عن الأسطر: "smtp_port, smtp" و "sendmail_path" ثم نقوم بالتعديل عليها كما يلي: كود HTML: SMTP=smtp.gmail.com smtp_port=587 sendmail_path = "\"C:\xampp\sendmail\sendmail.exe\" -t" السطر "sendmail_from = [email protected]" يبقى إختياري إن أراد أحدا تفعيله كخيار، يضع مكان البريد الإلكتروني الإفتراضي، بريده هو. نقوم بعملية الحفظ، و ننتقل إلى الملف "C:/xampp/sendmail/sendmail.ini" نقوم بفتحه على المحرر المضل لديك، ثم نقوم بالتعديلات التالية على الأسطر المعنية: كود HTML: smtp_server=smtp.gmail.com smtp_port=587 smtp_ssl=auto error_logfile=error.log debug_logfile=debug.log auth_username=بريدك الإلكتروني auth_password=كلمة المرور الخاصة بالنطبيقات المحررة مؤخرا بعد التعديل و الحفظ، أصبج و الحمد لله، لدينا سرفر محلي قادر على إرسال الرسائل الإلكترونية عن طريق "GMAIL" المرحلة التالية، ستكون مخصصة لإنشاء ملف "sendmail.php" يــتــبــع
إنشاء ملف "C:/xampp/htdocs/itabcode/sendmail.php" هذا الملف، سيكون بإذن الله داعم لإرسال ملف مرفق مع رسالة، و مسؤول عن تحليل محتوى نصي داخل بطاقة الإشعار (HTML textarea tag) الموجدة بإستمارة داخل ملف "C:/xampp/htdocs/itabcode/contact.php" و من ثمه يقوم ببعث الرسالة و إرجاع محتوى رسالة تأكيد على شكل "JSON" . كود PHP: <?phpdeclare(strict_types=1);function escp_input(string $data): string{ $data = trim($data); $data = stripslashes($data); $data = htmlspecialchars($data); return $data;}if ($_SERVER['REQUEST_METHOD'] == 'POST') { $json = []; $errors = []; $subject = escp_input($_POST['s_subject']); $from = escp_input($_POST['s_email']); $message = escp_input($_POST['s_msg']); $to = ''; // عنوان الوجهة if (empty($to)) { $errors['to'] = 'لقد نسيت أن تعلن عن البريد الإلكتروني للمستلم ️'; $json['errors'] = 'لقد نسيت أن تعلن عن البريد الإلكتروني للمستلم ️'; } if (empty($subject)) { $errors['subject'] = 'عنوان الرسالة مطلوب ️'; $json['errors'] = 'عنوان الرسالة مطلوب ️'; } if (empty($from)) { $errors['email'] = 'البريد الإلكتروني مطلوب ️'; $json['errors'] = 'البريد الإلكتروني مطلوب ️'; } else { // إزالة الأحرف غير القانونية $sanitized_email = filter_var($from, FILTER_SANITIZE_EMAIL); if (!filter_var($sanitized_email, FILTER_VALIDATE_EMAIL)) { $errors['email'] = 'عنوان البريد الإلكتروني غير صالح ️'; $json['errors'] = 'عنوان البريد الإلكتروني غير صالح ️'; } } if (empty($message)) { $errors['message'] = 'الرجاء إدخال نص الرسالة ️'; $json['errors'] = 'الرجاء إدخال نص الرسالة ️'; } else { $message = nl2br($message); } // نقوم بتصفية الخوادم التي بها أخطاء if (!preg_match("#^[a-z0-9._-][email protected](hotmail|live|msn).[a-z]{2,4}$#", $from)) { $pass_ligne = "\r\n"; } else { $pass_ligne = "\n"; } //===== HTML إعلان الرسالة بتنسيق الـ $msg_html = '<html><head></head><body>'; $msg_html .= '<div dir="rtl" style="border-style: solid; border-width: thin;border-color: #818ea7; border-radius: 8px;padding: 40px 20px;"> <h1 style="text-align:center;color:Blue;">أكتب كود لعلوم الحاسوب</h1> <h2 style="text-align:center;color:Blue;">هناك رسالة واردة</h2>'; $msg_html .= '<div style="border-style: solid; border-width:thin;border-color:#818ea7; padding:10px 5px;">'; $msg_html .= '<p> <strong style="font-size:16px;color:red;">نص الرسالة:</strong><br><br>'; $msg_html .= '<em style="font-size:18px;">' . $message . '</em> </p>'; $msg_html .= '</div> </div>'; $msg_html .= '</body></html>'; //========== //===== قراءة المرفق و تنسيقه $file = fopen("images/img-1.jpg", "r"); $attachement = fread($file, filesize("images/img-1.jpg")); $attachement = chunk_split(base64_encode($attachement)); fclose($file); //========== //===== حدود الرسالة $boundary = "-----=" . md5((string) rand()); $boundary_alt = "-----=" . md5((string) rand()); //===== Headers إنشاء الـ $header = "From: Sender <$from>" . $pass_ligne; $header .= "Reply-to: Sender <$from>" . $pass_ligne; $header .= "MIME-Version: 1.0" . $pass_ligne; $header .= "Content-Type: multipart/mixed;" . $pass_ligne . " boundary=\"$boundary\"" . $pass_ligne; //========== //===== إنشاء الرسالة $msg = $pass_ligne . "--" . $boundary . $pass_ligne; $msg .= "Content-Type: multipart/alternative;" . $pass_ligne . " boundary=\"$boundary_alt\"" . $pass_ligne; $msg .= $pass_ligne . "--" . $boundary_alt . $pass_ligne; $msg .= $pass_ligne . "--" . $boundary_alt . $pass_ligne; $msg .= "Content-Type: text/html; charset=\"ISO-8859-1\"" . $pass_ligne; $msg .= "Content-Transfer-Encoding: 8bit" . $pass_ligne; $msg .= $pass_ligne . $msg_html . $pass_ligne; //========== //===== غلق الجدود البديلة $msg .= $pass_ligne . "--" . $boundary_alt . "--" . $pass_ligne; //========== $msg .= $pass_ligne . "--" . $boundary . $pass_ligne; //===== إضافة المرفق $msg .= "Content-Type: image/jpeg; name=\"image.jpg\"" . $pass_ligne; $msg .= "Content-Transfer-Encoding: base64" . $pass_ligne; $msg .= "Content-Disposition: attachment; filename=\"image.jpg\"" . $pass_ligne; $msg .= $pass_ligne . $attachement . $pass_ligne . $pass_ligne; $msg .= $pass_ligne . "--" . $boundary . "--" . $pass_ligne; //========== //===== إرسال الرسالة مع إظهار خطأ في حالة عدم الإرسال إو إعلام بالنجاح if (empty($errors)) { if (mail($to, $subject, $msg, $header)) { $json['success'] = 'تم الإرسال بنجاح'; } else { $json['errors'] = 'خطأ: لم يتم إرسال الرسالة!'; } } else { $json['errors'] = implode('<br>' . PHP_EOL, $errors); } //========== echo json_encode($json);} لا ننسى التعديل على السطر الآني بإدخالد بريد المستلم. كود PHP: $to = ''; // عنوان الوجهة لا تقلق على كل حال. تم وضع شرط لمراقبة حسن إتمام الأمر، و سيتم إعلامك في صورة النسيان أو اخطأ. إلى هنا، نكون قد أتممنا كل المراحل و الحمد الله. سيتم رفع مجلد المشروع هنا بإذن الله في انتظار تفاعلكم و مقتراحاتكم و نقدكم أيضا