Oc-windows.ru

IT Новости из мира ПК
0 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Web push php

Push Notification System with PHP & MySQL

Web Push Notification is a feature to send customized clickable message to display in subscribed user’s web browsers like Chrome, Firefox, Safari etc. The push notifications are useful to update user’s with specific news, chat, new email and time bound information like offers etc. So if you’re thinking about implementing web notification system with PHP, you’re here at right place. In this tutorial you will learn how to implement web push notification system with PHP and MySQL. We will cover this tutorial with live demo to save notification message with settings to display to particular users when users logged in.

Here in this example, administrator will create web push notifications with many options and broadcast to logged in users in their browser with many options like:

  • The notification may displayed many times according to settings.
  • The user can also define interval time for the next notification to be displayed.
  • The system will check for the notification every time according to given time
  • The notification will be closed after given time time.

As we will cover this tutorial with live example to implement web push notification system with PHP and MySQL, so the major files for this example is following.

  • index.php
  • login.php
  • manage_notification.php
  • notification.js
  • notification.php
  • Push.php
  • logout.php

Step1: Create Database Tables
First we will create MySQL database table notif_user to store users for login to show notification message to logged in users.

CREATE TABLE `notif_user` (
`id` int(11) NOT NULL,
`username` varchar(100) NOT NULL,
`password` varchar(100) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

We will also create a table notif to store notification details.

CREATE TABLE `notif` (
`id` int(11) NOT NULL,
`title` varchar(250) NOT NULL,
`notif_msg` text NOT NULL,
`notif_time` datetime DEFAULT NULL,
`notif_repeat` int(11) DEFAULT ‘1’,
`notif_loop` int(11) NOT NULL DEFAULT ‘1’,
`publish_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`username` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Step2: Create User Login
Now we will create user login page to allow user login to show notifications to logged in users.

User Login:

we will implement user login functionality on form lsubmit.

loginUsers($_POST[‘username’], $_POST[‘pwd’]);
if(!empty($user)) <
$_SESSION[‘username’] = $user[0][‘username’];
header(«Location:index.php»);
> else <
$loginError = «Invalid username or password!»;
>
>
?>

Step3: Add Notification and Display List
In manage_notification.php file, we will create HTML for adding new notification by Administrator.

Add New Notification:

we will implement functionality to save new notifications to database table on form submit.
saveNotification($msg,$time,$loop,$loop_every,$user);
if($isSaved) <
echo ‘* save new notification success’;
> else <
echo ‘error save data’;
>
> else <
echo ‘* completed the parameter above’;
>
>
?>

Now we will display list of add notifications.

Notifications List:

listNotification();
foreach ($notifList as $key) <
?>

NoNext ScheduleMessageRemainsUser

Step4: Broadcast Notification
In notification.js file, we will create function showNotification() to make Ajax request to notification.php to get notification details for logged in user and execute notification.

function showNotification() <
if (!Notification) <
$(‘body’).append(‘

*Browser does not support Web Notification

Then we will call function showNotification() to execute notification on every 20 seconds. When user logged in, it will make ajax request to check for logged in user and display notification message accordingly.

Step5: Get Notification Details
In notification.php file, we will get logged in user’s notification details and returned as json response as this file called by Ajax request.
listNotificationUser($_SESSION[‘username’]);
$record = 0;
foreach ($notifList as $key) <
$data[‘title’] = $key[‘title’];
$data[‘msg’] = $key[‘notif_msg’];
$data[‘icon’] = ‘https://phpzag.com/demo/push-notification-system-with-php-mysql-demo/avatar.png’;
$data[‘url’] = ‘https://phpzag.com’;
$rows[] = $data;
$nextime = date(‘Y-m-d H:i:s’,strtotime(date(‘Y-m-d H:i:s’))+($key[‘notif_repeat’]*60));
$push->updateNotification($key[‘id’],$nextime);
$record++;
>
$array[‘notif’] = $rows;
$array[‘count’] = $record;
$array[‘result’] = true;
echo json_encode($array);
?>

Step6: Create Get and Set Push Notification functions
In Push.php file, we will create functions to perform push notification functionality like get notification details, save notification, update etc.
dbConnect) <
$conn = new mysqli($this->host, $this->user, $this->password, $this->database);
if($conn->connect_error) <
die(«Error failed to connect to MySQL: » . $conn->connect_error);
>else <
$this->dbConnect = $conn;
>
>
>
private function getData($sqlQuery) <
$result = mysqli_query($this->dbConnect, $sqlQuery);
if(!$result) <
die(‘Error in query: ‘. mysqli_error());
>
$data= array();
while ($row = mysqli_fetch_array($result, MYSQL_ASSOC)) <
$data[]=$row;
>
return $data;
>
public function listNotification() <
$sqlQuery = ‘SELECT * FROM ‘.$this->notifTable;
return $this->getData($sqlQuery);
>
public function listNotificationUser($user) <
$sqlQuery = «SELECT * FROM «.$this->notifTable.» WHERE username=’$user’ AND notif_loop > 0 AND notif_time getData($sqlQuery);
>
public function listUsers() <
$sqlQuery = «SELECT * FROM «.$this->userTable.» WHERE username != ‘admin'»;
return $this->getData($sqlQuery);
>
public function loginUsers($username, $password) <
$sqlQuery = «SELECT id as userid, username, password FROM «.$this->userTable.» WHERE username=’$username’ AND password=’$password'»;
return $this->getData($sqlQuery);
>
public function saveNotification($msg, $time, $loop, $loop_every, $user) <
$sqlQuery = «INSERT INTO «.$this->notifTable.»(notif_msg, notif_time, notif_repeat, notif_loop, username) VALUES(‘$msg’, ‘$time’, ‘$loop’, ‘$loop_every’, ‘$user’)»;
$result = mysqli_query($this->dbConnect, $sqlQuery);
if(!$result) <
return (‘Error in query: ‘. mysqli_error());
> else <
return $result;
>
>
public function updateNotification($id, $nextTime) <
$sqlUpdate = «UPDATE «.$this->notifTable.» SET notif_time = ‘$nextTime’, publish_date=CURRENT_TIMESTAMP(), notif_loop = notif_loop-1 WHERE )»;
mysqli_query($this->dbConnect, $sqlUpdate);
>
>
?>

You can view the live demo from the Demo link and can download the full script from the Download link below.
Demo

Web push php

WebPush can be used to send notifications to endpoints which server delivers Web Push notifications as described in the Web Push protocol. As it is standardized, you don’t have to worry about what server type it relies on.

  • PHP 7.1+
    • gmp
    • mbstring
    • curl
    • openssl

PHP 7.2+ is recommended for better performance.

There is no support and maintenance for older PHP versions, however you are free to use the following compatible versions:

Use composer to download and install the library and its dependencies.

composer require minishlink/web-push

Full examples of Web Push implementations

  • An example with web-push-php: Minishlink/web-push-php-example
  • Matthew Gaunt’s Web Push Book — a must read
  • Mozilla’s ServiceWorker Cookbooks (don’t mind the server.js file: it should be replaced by your PHP server code with this library)
  • Google’s introduction to push notifications (as of 03-20-2016, it doesn’t mention notifications with payload)
  • you may want to take a look at my own implementation: sw.js and app.js

Browsers need to verify your identity. A standard called VAPID can authenticate you for all browsers. You’ll need to create and provide a public and private key for your server. These keys must be safely stored and should not change.

You can specify your authentication details when instantiating WebPush. The keys can be passed directly (recommended), or you can load a PEM file or its content:

In order to generate the uncompressed public and secret key, encoded in Base64, enter the following in your Linux bash:

If you can’t access a Linux bash, you can print the output of the createVapidKeys function:

On the client-side, don’t forget to subscribe with the VAPID public key as the applicationServerKey : ( urlBase64ToUint8Array source here)

Reusing VAPID headers

VAPID headers make use of a JSON Web Token (JWT) to verify your identity. That token payload includes the protocol and hostname of the endpoint included in the subscription and an expiration timestamp (usually between 12-24h), and it’s signed using your public and private key. Given that, two notifications sent to the same push service will use the same token, so you can reuse them for the same flush session to boost performance using:

Notifications and default options

Each notification can have a specific Time To Live, urgency, and topic. You can change the default options with setDefaultOptions() or in the constructor:

Time To Live (TTL, in seconds) is how long a push message is retained by the push service (eg. Mozilla) in case the user browser is not yet accessible (eg. is not connected). You may want to use a very long time for important notifications. The default TTL is 4 weeks. However, if you send multiple nonessential notifications, set a TTL of 0: the push notification will be delivered only if the user is currently connected. For other cases, you should use a minimum of one day if your users have multiple time zones, and if they don’t several hours will suffice.

Urgency can be either «very-low», «low», «normal», or «high». If the browser vendor has implemented this feature, it will save battery life on mobile devices (cf. protocol).

Similar to the old collapse_key on legacy GCM servers, this string will make the vendor show to the user only the last notification of this topic (cf. protocol).

If you send tens of thousands notifications at a time, you may get memory overflows due to how endpoints are called in Guzzle. In order to fix this, WebPush sends notifications in batches. The default size is 1000. Depending on your server configuration (memory), you may want to decrease this number. Do this while instanciating WebPush or calling setDefaultOptions . Or, if you want to customize this for a specific flush, give it as a parameter : $webPush->flush($batchSize) .

You can see what the browser vendor’s server sends back in case it encountered an error (push subscription expiration, wrong parameters. ). sendNotification() (with $flush as true ) and flush() always returns a Generator with MessageSentReport objects, even if you just send one notification. To loop through the results, just pass it into foreach . You can also use iterator_to_array to check the contents while debugging.

PLEASE NOTE: You can only iterate once over the Generator object.

Firefox errors are listed in the autopush documentation.

Payload length, security, and performance

Payloads are encrypted by the library. The maximum payload length is theoretically 4078 bytes (or ASCII characters). For compatibility reasons though, your payload should be less than 3052 bytes long.

The library pads the payload by default. This is more secure but it decreases performance for both your server and your user’s device.

Why is it more secure?

When you encrypt a string of a certain length, the resulting string will always have the same length, no matter how many times you encrypt the initial string. This can make attackers guess the content of the payload. In order to circumvent this, this library adds some null padding to the initial payload, so that all the input of the encryption process will have the same length. This way, all the output of the encryption process will also have the same length and attackers won’t be able to guess the content of your payload.

Why does it decrease performance?

Encrypting more bytes takes more runtime on your server, and also slows down the user’s device with decryption. Moreover, sending and receiving the packet will take more time. It’s also not very friendly with users who have limited data plans.

How can I disable or customize automatic padding?

You can customize automatic padding in order to better fit your needs.

Here are some ideas of settings:

  • (default) Encryption::MAX_COMPATIBILITY_PAYLOAD_LENGTH (3052 bytes) for compatibility purposes with Firefox for Android
  • Encryption::MAX_PAYLOAD_LENGTH (4078 bytes) for maximum security
  • false for maximum performance
  • If you know your payloads will not exceed X bytes, then set it to X for the best balance between security and performance.

Customizing the HTTP client

WebPush uses Guzzle. It will use the most appropriate client it finds, and most of the time it will be MultiCurl , which allows to send multiple notifications in parallel.

You can customize the default request options and timeout when instantiating WebPush:

Is there any plugin/bundle/extension for my favorite PHP framework?

The following are available:

Feel free to add your own!

Is the API stable?

Not until the Push API spec is finished.

What about security?

Payload is encrypted according to the Message Encryption for Web Push standard, using the user public key and authentication secret that you can get by following the Web Push API specification.

Internally, WebPush uses the WebToken framework or OpenSSL to handle encryption keys generation and encryption.

Here are some ideas:

  1. Upgrade to PHP 7.2
  2. Make sure MultiCurl is available on your server
  3. Find the right balance for your needs between security and performance (see above)
  4. Find the right batch size (set it in defaultOptions or as parameter to flush() )

How to solve «SSL certificate problem: unable to get local issuer certificate»?

Your installation lacks some certificates.

  1. Download cacert.pem.
  2. Edit your php.ini : after [curl] , type curl.cainfo = /path/to/cacert.pem .

You can also force using a client without peer verification.

How to solve «Bad key encryption key length» or «Unsupported key type»?

Disable mbstring.func_overload in your php.ini .

How to solve «Class ‘MinishlinkWebPushWebPush’ not found»

Make sure to require Composer’s autoloader.

I must use PHP 5.4 or 5.5. What can I do?

You won’t be able to send any payload, so you’ll only be able to use sendNotification($subscription) . Install the library with composer using —ignore-platform-reqs . The workaround for getting the payload is to fetch it in the service worker (example).

I lost my VAPID keys!

I’m using Firebase push notifications, how do I use this library?

This library is not designed for Firebase push notifications. You can still use it for your web projects (for standard WebPush notifications), but you should forget any link to Firebase while using the library.

I need to send notifications to native apps. (eg. APNS for iOS)

WebPush is for web apps. You need something like RMSPushNotificationsBundle (Symfony).

Реализуем пуш-уведомления на фронтенде и бэкенде

В предыдущей статье об iss-observer.com я обещал рассказать о технической стороне реализации пуш-уведомлений. Изначально я планировал сконцентрироваться на проблемах, с которыми пришлось иметь дело в процессе работы над iss-observer.com. Теперь я думаю, будет полезнее посвятить материал базовым вопросам, и уточнять детали, где это необходимо. Обращаю ваше внимание, что фронтенд частично опирается на этот урок.

Если вы хотите углубиться в исходный код, то смотрите в GitHub-репозиторий и на пример , демонстрирующий его работу.

Фронтенд

Начнем с клиентской части. Первое, что нужно сделать — убедиться, что браузер поддерживает пуш-уведомления. Если да, загружаем наш JavaScript.

Прежде чем писать код, выполним ряд требований. Нам потребуются Application Server Keys (VAP >web-push. Кстати, она нам еще потребуется для бэкенд-части. Устанавливаем библиотеку: npm install -g web-push , генерируем ключи: web-push generate-vapid-keys . В независимости от способа в результате у вас должны быть закрытый ключ (private key) и открытый ключ (public key). Сохраните их в надежном месте.

Посмотрим на push.js. Здесь мы регистрируем сервис-воркер и подписываемся на уведомления:

В первую очередь создаем константу addServiceKey : ей присваиваем значение с открытым ключ VAP >urlB64ToUint8Array() : она понадобится для конвертации ключа из base64 в Uint8Array.

Затем декларируем функцию updatePushButton() . Мы будем вызывать её каждый раз при изменении статуса уведомлений, чтобы обновить отвечающие за него элементы интерфейса.

Далее видим функцию регистрации подписки subscribeUser() . Как вы наверно заметили, в начале скрипта была объявлена переменная let serviceWorkerRegistration . Она содержит результат регистрации сервис-воркера: посмотрите в конец файла, мы записываем его в переменную в момент регистрации.

Метод subscribe() возвращает промис, а в качестве аргумента принимает объект с двумя свойствами:

  • userVisibilityOnly : булево значение. Параметр сообщает, что подписка будет использоваться только для сообщений, эффект которых виден для пользователя. Устанавливаем значение true .
  • applicationServiceKey : номер открытого ключа, он используется сервером для отправки уведомлений. Так как ключ должен быть в формате UInt8Attay, используем уже знакомую нам функцию.

Теперь отправляем данные подписки на сервер. Смело используем Fetch API, так как все браузеры, которые поддерживают пуш-уведомления, поддерживают и Fetch. После получения ответа выводим результат — завершена ли подписка успешно.

Этот этап прекрасно подходит для уточнения нужных параметров у пользователя. Например, на iss-observer.com он может выбрать время получения уведомлений (утро и/или вечер). Я также отправляю на сервер данные о стране, регионе и городе, по которому пользователь хочет получать уведомления; данные сохраняются и могут потребоваться при отправке уведомлений.

После этого, создаем функцию отписки unsubscribeUser() . Используем метод getSubscription() объекта PushManager , с его помощью получаем детали подписки, которые отправляем на сервер (снова Fetch API). На этот раз, чтобы удалить её базы.

Записываем функцию initPush() . В ней — событие для кнопки pushButton, которое вызывает функцию подписки или отписки в зависимости от текущего состояния. После, не забываем обновить это состояние. Использованный в примере код можно найти на GitHub.

Последний шаг — регистрация сервис-воркера.

Сервис-воркер

В этой части мы разберем два необходимых для реализации пуш-уведомлений события: push и notificationonclick .

Начнем с push . Проверяем содержимое объекта notificationData (свойства title , body и icon ), и, если не находим их, присваиваем дефолтные значения. После вызываем метод showNotification , он покажет уведомление пользователю.

В дополнение к трём перечисленным свойствам могут использоваться и другие, например, badge , tag , vibrate . На момент написания этой статьи (февраль 2017) многие из них поддерживались только некоторыми браузерами. title , body и icon , доступных во всех браузерах, поэтому ограничимся ими.

Событие notificationOnClick срабатывает в момент клика по уведомлению. Сначала закрываем уведомление. Затем проверяем, открыт ли наш сайт в текущей вкладке браузера, если нет, то открываем его с помощью openWindow() .

Бэкенд

Переходим к серверной части, в которой мы используем web-push library. В нашем случае это реализация библиотеки для Node.js, но версии для PHP, Java и C# также доступны.

Я предполагаю, что у вас есть базовые знания Node.js и опыт использования Express. В ином случае, я рекомендую вам ознакомиться с ними прежде чем продолжить.

Итак, в первую очередь:

  • устанавливаем библиотеку командой npm install web-push —save ,
  • получаем доступ к ней с помощью require : const webPush = require(‘web-push’);

Теперь передаем данные VAP >mailto: ) либо URL сайта. Контактные данные могут потребоваться сервису для связи с вами. Обратите внимание на комментарии: я предпочел сохранить ключи в переменную окружения. Вы можете поступить так же или выбрать свой метод, но главное помните, закрытый ключ должен быть всегда защищен от обращений извне. Собственно поэтому он так и назван.

Переходим к функции подписки:

В функции отправки подписки на сервер, получаем доступ к объекту subscription . В нем — значение endpoint и ключи доступа. Здесь я не буду останавливаться на вопросах работы с базой данных. Для примера укажу только, что для демо использована Mongo DB.

Прим. переводчика: endpoint — это уникальный URI, создаваемый для каждого пользователя индивидуально в соответствии с паттерном : p256dh — открытый ключ, auth — закрытый ключ.

Затем получаем наше первое уведомление — то, которое приветствует подписавшегося пользователя. Метод sendNotification() принимает три аргумента:

  • данные подписки, получаемые от браузера;
  • информацию для пользователя (заголовок, сообщение, иконка — свойства title , body , icon соответственно);
  • объект options , см. подробнее.

TTL (Time To Live) — срок жизни уведомления —по умолчанию четыре недели. Это значит, что оно будет ожидать появления пользователя онлайн в течение этого срока. Например, если вы отправили уведомление пользователю в оффлайне, и он подключится к сети только через две недели, сообщение все равно будет доставлено. В моем случае разумно изменить TTL на более короткий срок.

Если пользователь отменяет подписку, удаляем информацию о ней из базы.

Дополнительно

Вероятно, каждый из вас столкнется со своим случаем применения пуш-уведомлений, и просто скопировать код не получится. Однако я надеюсь, что этот урок поможет вам реализовать искомую функциональность как на стороне клиента, так и на стороне сервера.

Демо проекта, исходный код опубликован на GitHub.

Для более глубокого погружения в тему рекомендую бесплатную книгу Web Push Book и примеры на servicewore.rs.

Если вам есть что спросить, или есть что добавить, пишите в Twitter или по электронной почте.

Как добавить Push-уведомления на сайт

Без Push-уведомления сейчас не может работать не один серьезный ресурс, да, мы знаем что на нашем сайте их пока нет, но мы обязательно в ближайшее время все прикрутим. Спасибо Танечке за подробную инструкцию.

Telegram CPARIP — Подписка на наш Telegram канал обязательна!

Push-уведомления работают только если на сайте есть HTTPS.

Без валидного SSL сертификата запустить не получится. То есть сначала надо купить домен, на котором будет размещаться сайт, потом подключить к нему SSL сертификат (у многих хостеров эта услуга предоставляется бесплатно) и только потом надо начинать повторять наш гайд

Все файлы и папки, которые я упоминаю в гайде прикреплены в архивах к посту.

Файлы, которые нам понадобятся server.zip и land.zip

Создание проекта в Firebase

1. В качестве сервера сообщений мы будем использовать Firebase Cloud Messaging, поэтому мы начинаем с регистрации и создания проекта на Firebase.

  • Заходим на сайт;
  • Регистрируемся;
  • Жмём кнопку Create new project или Import Google project, если у вас уже есть проект;
  • При создании указываем название проекта и страну;
  • После создания проекта попадаем на его dashboard;
  • В меню наводим на колесико рядом с Overview и выбираем Project settings;

2. На открывшейся странице переходим во вкладку Cloud Messaging;

Нас интересует Ключ сервера, который будет использоваться для отправки сообщений с сервера и Сертификат для Web Push который будет использоваться для получения сообщений на стороне клиента

3. Затем на вкладке Общие нажимаем Добавить Firebase в свое веб-приложение.

4. В открывшемся окне будет код для инициализации нашего веб-приложения. Нам понадобится не весь, только тот, который выделен на скриншоте.

Приступаем к настройке лендинга

Копируем содержимое папки land в корень сайта — то есть в ту папку, где у вас лежит index.html

Файл firebase-messaging-sw.js обязательно должен находиться в корне проекта, то есть доступен по адресу https://example.com/firebase-messaging-sw.js

1. Заменяем содержимое файла firebase/init.js кодом инициализации, полученным в ЛК firebase

2. В 3 строке файла firebase/firebase_subscribe.js добавляем ключ сертификата, полученный в ЛК.

3. Открываем файл send_subscribe.php и прописываем URL, по которому будет находиться наш скрипт, сохраняющий подписки.

4. Подключаем все скрипты к лендингу. Перед закрывающим тэгом body добавляем след. код:

Настройка сохранения подписок

В данном примере мы будем сохранять идентификаторы устройств подписавшихся пользователей в файл.

Для реального проекта желательно использовать базу данных для хранения подписок.

В этом примере скрипт для сохранения подписок будет находиться на том же сервере, где и лендинг, но если есть необходимость централизованного сбора подписок с разных сайтов, то можно разместить его на отдельном сервере, отведенном для обработки подписок.

Итак, для продолжения настройки необходимо скопировать содержимое папки server на хостинг, где будет располагаться система сохранения и обработки подписок. В данном случае просто скопируем в папку с лендингом.

В файле save_push.php находится скрипт, который сохраняет id устройств, подписавшихся пользователей в текстовый файл push.txt

В файле send_push.php представлен скрипт для отправки 1 push-уведомления.
Для его работы необходимо добавить ключ сервера, который нужно взять из ЛК firebase. (3 строка файла)

Пробная подписка

После того, как все настроено, необходимо совершить пробную подписку.Для этого надо просто зайти на свой сайт, куда был установлен скрипт и подписаться

После этого в файле push.txt должна появиться скрока вида

Это идентификатор устройства. Он необходим для отправки уведомлений.

Отправка уведомлений

Чтобы отправить тестовое уведомление, нужно открыть в браузере скрипт send_push.php и передать ему в параметре token идентификатор устройства.

http://УРЛ_НА_КОТОРОМ_НАХОДИТСЯ_СКРИПТ?token=ИД_УСТРОЙСТВА
Например:
https://push.site-guard.info/send_push.php?token=cwU1aVjaw0w:APA91bEICUeF-ASm8nVYfXb6eiaUxgIkBffq5As1bgq6lCIm3_6Z4ArIN3QnZb5zEHftp2CqpnYWsCayHZ0Dvy7hTNfymxW1FGNeGUWLDnxoS346-Y3YkJMT4PUIvM9OfTlcLBotETOH

После этого на экране появится ответ от сервиса уведомлений и если уведомление было отправлено корректно, то оно должно прийти на устройство, с которого была совершена подписка.

Некоторые моменты

1. Если в момент отправки подписки, в браузере открыта страница сайта, на котором была совершена подписка, то уведомление не будет показано.

2. Скрипт работает корректно, но на некоторых устройствах и браузерах могут возникать неполадки. Например, на моем последнем Mac у меня не показываются пуши в хроме, хотя во всех остальных браузерах — работают. При этом не понятно в чем причина, в маке или в хроме, потому что на моем старом макбуке пуши показываются в хроме и во всех остальных браузерах.

3. Push на iPhone не работают, только Android телефоны.

Читать еще:  Php get encoding
Ссылка на основную публикацию
Adblock
detector