Initial Commit
This commit is contained in:
commit
eb50f184b3
40 changed files with 4469 additions and 0 deletions
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
# Include your project-specific ignores in this file
|
||||
# Read about how to use .gitignore: https://help.github.com/articles/ignoring-files
|
||||
# Useful .gitignore templates: https://github.com/github/gitignore
|
||||
node_modules
|
||||
dist
|
||||
.cache
|
||||
/logs/
|
||||
/.idea/
|
||||
/vendor/
|
94
Dockerfile
Normal file
94
Dockerfile
Normal file
|
@ -0,0 +1,94 @@
|
|||
FROM php:8.3.1-fpm
|
||||
ARG WORKDIR=/var/www/html
|
||||
ENV DOCUMENT_ROOT=${WORKDIR}
|
||||
ENV DOMAIN=_
|
||||
ENV CLIENT_MAX_BODY_SIZE=15M
|
||||
ARG GROUP_ID=1000
|
||||
ARG USER_ID=1000
|
||||
ENV USER_NAME=www-data
|
||||
ARG GROUP_NAME=www-data
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
libmemcached-dev \
|
||||
libonig-dev \
|
||||
supervisor \
|
||||
libzip-dev \
|
||||
libpq-dev \
|
||||
zip \
|
||||
unzip \
|
||||
cron
|
||||
|
||||
# Install nginx
|
||||
RUN apt-get install -y nginx
|
||||
|
||||
# Clear cache
|
||||
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install PHP extensions zip, mbstring, exif, bcmath, intl
|
||||
RUN docker-php-ext-install zip mbstring pcntl opcache bcmath -j$(nproc)
|
||||
|
||||
# Install the php memcached extension
|
||||
RUN pecl install memcached && docker-php-ext-enable memcached
|
||||
|
||||
|
||||
# Download Composer
|
||||
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && \
|
||||
php composer-setup.php --install-dir=/usr/local/bin --filename=composer && \
|
||||
php -r "unlink('composer-setup.php');"
|
||||
|
||||
|
||||
# Create a log directory and give permissions
|
||||
RUN mkdir -p /var/www/html/logs && \
|
||||
chown -R www-data:www-data /var/www/html/logs
|
||||
|
||||
|
||||
# Set working directory
|
||||
WORKDIR $WORKDIR
|
||||
|
||||
ADD . $WORKDIR/
|
||||
|
||||
# Install PHP dependencies using Composer
|
||||
RUN composer install
|
||||
|
||||
ADD docker/php.ini $PHP_INI_DIR/conf.d/
|
||||
ADD docker/opcache.ini $PHP_INI_DIR/conf.d/
|
||||
ADD docker/supervisord.conf /etc/supervisor/supervisord.conf
|
||||
|
||||
COPY docker/entrypoint.sh /usr/local/bin/
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
RUN ln -s /usr/local/bin/entrypoint.sh /
|
||||
|
||||
|
||||
RUN rm -rf /etc/nginx/conf.d/default.conf
|
||||
RUN rm -rf /etc/nginx/sites-enabled/default
|
||||
RUN rm -rf /etc/nginx/sites-available/default
|
||||
|
||||
RUN rm -rf /etc/nginx/nginx.conf
|
||||
|
||||
COPY docker/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY docker/default.conf /etc/nginx/conf.d/
|
||||
|
||||
RUN usermod -u ${USER_ID} ${USER_NAME}
|
||||
RUN groupmod -g ${USER_ID} ${GROUP_NAME}
|
||||
|
||||
RUN mkdir -p /var/log/supervisor
|
||||
RUN mkdir -p /var/log/nginx
|
||||
RUN mkdir -p /var/cache/nginx
|
||||
|
||||
RUN chown -R ${USER_NAME}:${GROUP_NAME} /var/www && \
|
||||
chown -R ${USER_NAME}:${GROUP_NAME} /var/log/ && \
|
||||
chown -R ${USER_NAME}:${GROUP_NAME} /etc/supervisor/conf.d/ && \
|
||||
chown -R ${USER_NAME}:${GROUP_NAME} $PHP_INI_DIR/conf.d/ && \
|
||||
touch /var/run/nginx.pid && \
|
||||
chown -R $USER_NAME:$USER_NAME /var/cache/nginx && \
|
||||
chown -R $USER_NAME:$USER_NAME /var/lib/nginx/ && \
|
||||
chown -R $USER_NAME:$USER_NAME /var/run/nginx.pid && \
|
||||
chown -R $USER_NAME:$USER_NAME /var/log/supervisor && \
|
||||
chown -R $USER_NAME:$USER_NAME /etc/nginx/nginx.conf && \
|
||||
chown -R $USER_NAME:$USER_NAME /etc/nginx/conf.d/ && \
|
||||
chown -R ${USER_NAME}:${GROUP_NAME} /tmp
|
||||
|
||||
#USER ${USER_NAME}
|
||||
EXPOSE 80
|
||||
ENTRYPOINT ["entrypoint.sh"]
|
97
README.md
Normal file
97
README.md
Normal file
|
@ -0,0 +1,97 @@
|
|||
## PulseBridge Gateway Server
|
||||
|
||||
Welcome to the PulseBridge Gateway Server repository! This server acts as an SMS Gateway powered by the PulseBridge library.The PulseBridge Gateway Server is a powerful SMS Gateway software that allows you to send SMS messages seamlessly. Whether you're looking to integrate SMS functionality into your web applications or send messages from a centralized server, PulseBridge Gateway makes the process efficient and straightforward.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Getting Started](#getting-started)
|
||||
* [Building and Running Locally](#building-and-running-locally)
|
||||
* [Running with Docker Compose](#running-with-docker-compose)
|
||||
* [Manually Spinning Up Your Own Image](#manually-spinning-up-your-own-image)
|
||||
* [Licecnse](#license)
|
||||
* [Contribution](#contribution)
|
||||
|
||||
## Getting Started
|
||||
|
||||
Follow these three simple steps to get started with PulseBridge Gateway:
|
||||
|
||||
1. **Run PulseBridge Gateway Server App and click `setup credentials` button.
|
||||
Clone the repository, install dependencies, and run the server locally or using Docker Compose using the below instructions.[Building and Running Locally](#building-and-running-locally)
|
||||
2. [**Download**](https://app.download#) **PulseBridge Mobile App and set the URL in app provided by the server.**
|
||||
3. **Send SMS from the Server Frontend or API**
|
||||
|
||||
With the PulseBridge Gateway Server running, access the user-friendly interface at [http://localhost](http://localhost/) to send SMS messages. Alternatively, integrate the SMS functionality into your applications using the provided API.
|
||||
|
||||
|
||||
## Building and Running Locally
|
||||
|
||||
1. **Clone the Repository:**
|
||||
|
||||
```plaintext
|
||||
git clone https://github.com/aamitn/pulsebridge-gateway.git
|
||||
cd pulsebridge-gateway
|
||||
```
|
||||
|
||||
2. **Install Dependencies:**
|
||||
`*install composer from instructions here :` [`Composer (getcomposer.org)`](https://getcomposer.org/download/)
|
||||
|
||||
```plaintext
|
||||
composer install
|
||||
```
|
||||
|
||||
3. **Run the Server: \[run the dev server or copy the directory contents to web server of your choice\]**
|
||||
|
||||
```plaintext
|
||||
php -S localhost:8000 -t public
|
||||
```
|
||||
|
||||
|
||||
## Running with Docker Compose
|
||||
|
||||
* run command from the project root directory
|
||||
|
||||
1. **Run the Container:**
|
||||
|
||||
```plaintext
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
|
||||
## Build your own docker image
|
||||
|
||||
1. **Build the Docker Image::**
|
||||
|
||||
```plaintext
|
||||
docker build -t pulsebridge-gateway .
|
||||
```
|
||||
|
||||
2. **Run the Docker Container:**
|
||||
|
||||
```plaintext
|
||||
docker run -p 80:80 --name pulsebridge-gateway pulsebridge-gateway
|
||||
```
|
||||
|
||||
|
||||
## **License**
|
||||
|
||||
This project is licensed under the [MIT License](https://chat.openai.com/c/LICENSE).
|
||||
|
||||
|
||||
## **Contributions**
|
||||
|
||||
Contributions are welcome! If you'd like to contribute to PulseBridge Gateway, please follow our [Contribution Guidelines](https://chat.openai.com/c/CONTRIBUTING.md).
|
||||
Fork the repository and create your branch:
|
||||
|
||||
1. bashCopy code
|
||||
|
||||
`git clone https://github.com/aamitn/pulsebridge-gateway.git cd pulsebridge-gateway git checkout -b feature/your-feature`
|
||||
|
||||
2. Make your changes and commit them:
|
||||
|
||||
bashCopy code
|
||||
|
||||
`git add . git commit -m "Add your feature"`
|
||||
|
||||
3. Push to your fork and submit a pull request.
|
||||
4. Follow the code review process.
|
||||
5. Your contribution will be merged once approved.
|
21
composer.json
Normal file
21
composer.json
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "nmpl/pulsebridge",
|
||||
"description": "description",
|
||||
"minimum-stability": "stable",
|
||||
"license": "proprietary",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Amit Nandi",
|
||||
"email": "amit@bitmutex.com"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {"nmpl\\pulsebridge\\": "src/"}
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "11.0.3"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "phpunit"
|
||||
}
|
||||
}
|
1643
composer.lock
generated
Normal file
1643
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
1
data/91f92f994d4d3cb9/send/65e3290fd423b9-55994365.NEW
Normal file
1
data/91f92f994d4d3cb9/send/65e3290fd423b9-55994365.NEW
Normal file
|
@ -0,0 +1 @@
|
|||
{"id": "65e3290fd423b9-55994365", "to": "0123456789", "content": "Hello world"}
|
1
data/91f92f994d4d3cb9/send/65e3291be00f82-11680709.NEW
Normal file
1
data/91f92f994d4d3cb9/send/65e3291be00f82-11680709.NEW
Normal file
|
@ -0,0 +1 @@
|
|||
{"id": "65e3291be00f82-11680709", "to": "0123456789", "content": "Hello world"}
|
1
data/edd0e1a6691f1e7a/send/65e329235a0cc8-06863164.NEW
Normal file
1
data/edd0e1a6691f1e7a/send/65e329235a0cc8-06863164.NEW
Normal file
|
@ -0,0 +1 @@
|
|||
{"id": "65e329235a0cc8-06863164", "to": "0123456789", "content": "Hello world"}
|
1
data/edd0e1a6691f1e7a/send/65e32925058621-25075091.NEW
Normal file
1
data/edd0e1a6691f1e7a/send/65e32925058621-25075091.NEW
Normal file
|
@ -0,0 +1 @@
|
|||
{"id": "65e32925058621-25075091", "to": "0123456789", "content": "Hello world"}
|
16
docker-compose.yml
Normal file
16
docker-compose.yml
Normal file
|
@ -0,0 +1,16 @@
|
|||
version: '3.8'
|
||||
|
||||
services:
|
||||
pulsebridge-gateway-app:
|
||||
image: nmpl/pulsebridge:latest
|
||||
# build:
|
||||
# context: .
|
||||
container_name: pulsebridge-gateway
|
||||
networks:
|
||||
- pulsebridge-network
|
||||
ports:
|
||||
- "80:80"
|
||||
|
||||
networks:
|
||||
pulsebridge-network:
|
||||
driver: bridge
|
41
docker/default.conf
Normal file
41
docker/default.conf
Normal file
|
@ -0,0 +1,41 @@
|
|||
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
server_name _;
|
||||
# Add index.php to setup Nginx, PHP & PHP-FPM config
|
||||
index index.php index.html index.htm index.nginx-debian.html; error_log /var/log/nginx/error.log;
|
||||
access_log /var/log/nginx/access.log;
|
||||
root /var/www/html;
|
||||
# pass PHP scripts on Nginx to FastCGI (PHP-FPM) server
|
||||
location ~ \.php$ {
|
||||
try_files $uri =404;
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
# Nginx php-fpm config:
|
||||
fastcgi_pass 127.0.0.1:9000;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||
|
||||
}
|
||||
client_max_body_size 15M;
|
||||
server_tokens off;
|
||||
|
||||
# Hide PHP headers
|
||||
fastcgi_hide_header X-Powered-By;
|
||||
fastcgi_hide_header X-CF-Powered-By;
|
||||
fastcgi_hide_header X-Runtime;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
gzip_static on;
|
||||
}
|
||||
# deny access to Apache .htaccess on Nginx with PHP,
|
||||
# if Apache and Nginx document roots concur
|
||||
location ~ /\.ht {deny all;}
|
||||
location ~ /\.svn/ {deny all;}
|
||||
location ~ /\.git/ {deny all;}
|
||||
location ~ /\.hg/ {deny all;}
|
||||
location ~ /\.bzr/ {deny all;}
|
||||
}
|
98
docker/entrypoint.sh
Normal file
98
docker/entrypoint.sh
Normal file
|
@ -0,0 +1,98 @@
|
|||
#!/bin/sh
|
||||
|
||||
echo ""
|
||||
echo "***********************************************************"
|
||||
echo " Starting NGINX PHP-FPM Docker Container "
|
||||
echo "***********************************************************"
|
||||
|
||||
set -e
|
||||
set -e
|
||||
info() {
|
||||
{ set +x; } 2> /dev/null
|
||||
echo '[INFO] ' "$@"
|
||||
}
|
||||
warning() {
|
||||
{ set +x; } 2> /dev/null
|
||||
echo '[WARNING] ' "$@"
|
||||
}
|
||||
fatal() {
|
||||
{ set +x; } 2> /dev/null
|
||||
echo '[ERROR] ' "$@" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Enable custom nginx config files if they exist
|
||||
if [ -f /var/www/html/conf/nginx/nginx.conf ]; then
|
||||
cp /var/www/html/conf/nginx/nginx.conf /etc/nginx/nginx.conf
|
||||
info "Using custom nginx.conf"
|
||||
fi
|
||||
|
||||
if [ -f /var/www/html/conf/nginx/nginx-site.conf ]; then
|
||||
info "Custom nginx site config found"
|
||||
rm /etc/nginx/conf.d/default.conf
|
||||
cp /var/www/html/conf/nginx/nginx-site.conf /etc/nginx/conf.d/default.conf
|
||||
info "Start nginx with custom server config..."
|
||||
else
|
||||
info "nginx-site.conf not found"
|
||||
info "If you want to use custom configs, create config file in /var/www/html/conf/nginx/nginx-site.conf"
|
||||
info "Start nginx with default config..."
|
||||
rm -f /etc/nginx/conf.d/default.conf
|
||||
TASK=/etc/nginx/conf.d/default.conf
|
||||
touch $TASK
|
||||
cat > "$TASK" <<EOF
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
server_name $DOMAIN;
|
||||
# Add index.php to setup Nginx, PHP & PHP-FPM config
|
||||
index index.php index.html index.htm index.nginx-debian.html;
|
||||
error_log /var/log/nginx/error.log;
|
||||
access_log /var/log/nginx/access.log;
|
||||
root $DOCUMENT_ROOT;
|
||||
# pass PHP scripts on Nginx to FastCGI (PHP-FPM) server
|
||||
location ~ \.php$ {
|
||||
try_files \$uri =404;
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
# Nginx php-fpm config:
|
||||
fastcgi_pass 127.0.0.1:9000;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
|
||||
fastcgi_param PATH_INFO \$fastcgi_path_info;
|
||||
|
||||
}
|
||||
client_max_body_size $CLIENT_MAX_BODY_SIZE;
|
||||
server_tokens off;
|
||||
|
||||
# Hide PHP headers
|
||||
fastcgi_hide_header X-Powered-By;
|
||||
fastcgi_hide_header X-CF-Powered-By;
|
||||
fastcgi_hide_header X-Runtime;
|
||||
|
||||
location / {
|
||||
try_files \$uri \$uri/ /index.php\$is_args\$args;
|
||||
gzip_static on;
|
||||
}
|
||||
location ~ \.css {
|
||||
add_header Content-Type text/css;
|
||||
}
|
||||
location ~ \.js {
|
||||
add_header Content-Type application/x-javascript;
|
||||
}
|
||||
# deny access to Apache .htaccess on Nginx with PHP,
|
||||
# if Apache and Nginx document roots concur
|
||||
location ~ /\.ht {deny all;}
|
||||
location ~ /\.svn/ {deny all;}
|
||||
location ~ /\.git/ {deny all;}
|
||||
location ~ /\.hg/ {deny all;}
|
||||
location ~ /\.bzr/ {deny all;}
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
## Check if the supervisor config file exists
|
||||
if [ -f /var/www/html/conf/worker/supervisor.conf ]; then
|
||||
info "Custom supervisor config found"
|
||||
cp /var/www/html/conf/worker/supervisor.conf /etc/supervisor/conf.d/supervisor.conf
|
||||
fi
|
||||
## Start Supervisord
|
||||
supervisord -c /etc/supervisor/supervisord.conf
|
73
docker/nginx.conf
Normal file
73
docker/nginx.conf
Normal file
|
@ -0,0 +1,73 @@
|
|||
user www-data;
|
||||
worker_processes auto;
|
||||
error_log /var/log/nginx/error.log crit;
|
||||
pid /var/run/nginx.pid;
|
||||
include /etc/nginx/modules-enabled/*.conf;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
use epoll;
|
||||
multi_accept on;
|
||||
}
|
||||
http {
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
client_header_timeout 3m;
|
||||
client_body_timeout 3m;
|
||||
client_max_body_size 256m;
|
||||
client_header_buffer_size 4k;
|
||||
client_body_buffer_size 256k;
|
||||
large_client_header_buffers 4 32k;
|
||||
send_timeout 3m;
|
||||
keepalive_timeout 60 60;
|
||||
reset_timedout_connection on;
|
||||
server_names_hash_max_size 1024;
|
||||
server_names_hash_bucket_size 1024;
|
||||
ignore_invalid_headers on;
|
||||
connection_pool_size 256;
|
||||
request_pool_size 4k;
|
||||
output_buffers 4 32k;
|
||||
postpone_output 1460;
|
||||
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
|
||||
|
||||
# Compression gzip
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_disable "MSIE [1-6]\.";
|
||||
gzip_proxied any;
|
||||
gzip_min_length 512;
|
||||
gzip_comp_level 6;
|
||||
gzip_buffers 8 64k;
|
||||
gzip_types text/plain text/xml text/css text/js application/x-javascript application/xml image/png image/x-icon image/gif image/jpeg image/svg+xml application/xml+rss text/javascript application/atom+xml application/javascript application/json application/x-font-ttf font/opentype;
|
||||
|
||||
# Proxy settings
|
||||
proxy_redirect off;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
proxy_pass_header Set-Cookie;
|
||||
proxy_connect_timeout 300;
|
||||
proxy_send_timeout 300;
|
||||
proxy_read_timeout 300;
|
||||
proxy_buffers 32 4k;
|
||||
proxy_cache_path /var/cache/nginx levels=2 keys_zone=cache:10m inactive=60m max_size=512m;
|
||||
proxy_cache_key "$host$request_uri $cookie_user";
|
||||
proxy_temp_path /var/cache/nginx/temp;
|
||||
proxy_ignore_headers Expires Cache-Control;
|
||||
proxy_cache_use_stale error timeout invalid_header http_502;
|
||||
proxy_cache_valid any 1d;
|
||||
server_tokens off;
|
||||
|
||||
|
||||
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
}
|
135
docker/opcache.ini
Normal file
135
docker/opcache.ini
Normal file
|
@ -0,0 +1,135 @@
|
|||
[opcache]
|
||||
; Determines if Zend OPCache is enabled
|
||||
opcache.enable=1
|
||||
|
||||
; Determines if Zend OPCache is enabled for the CLI version of PHP
|
||||
;opcache.enable_cli=1
|
||||
|
||||
; The OPcache shared memory storage size.
|
||||
opcache.memory_consumption=512
|
||||
|
||||
; The amount of memory for interned strings in Mbytes.
|
||||
opcache.interned_strings_buffer=64
|
||||
|
||||
; The maximum number of keys (scripts) in the OPcache hash table.
|
||||
; Only numbers between 200 and 1000000 are allowed.
|
||||
;If you have multiple PHP sites on the server then consider the value 130986
|
||||
; for magento 2, keep 65406
|
||||
opcache.max_accelerated_files=50000
|
||||
|
||||
; The maximum percentage of "wasted" memory until a restart is scheduled.
|
||||
opcache.max_wasted_percentage=15
|
||||
|
||||
; When this directive is enabled, the OPcache appends the current working
|
||||
; directory to the script key, thus eliminating possible collisions between
|
||||
; files with the same name (basename). Disabling the directive improves
|
||||
; performance, but may break existing applications.
|
||||
;opcache.use_cwd=1
|
||||
|
||||
; When disabled, you must reset the OPcache manually or restart the
|
||||
; webserver for changes to the filesystem to take effect.
|
||||
; For Development / testing, keep 1
|
||||
; For performance / production, keep 0
|
||||
opcache.validate_timestamps=0
|
||||
|
||||
;opcache.revalidate_freq How often in seconds should the code
|
||||
;cache expire and check if your code has changed. 0 means it
|
||||
;checks your PHP code every single request IF YOU HAVE
|
||||
;opcache.validate_timestamps ENABLED. opcache.validate_timestamps
|
||||
;should not be enabled by default, as long as it's disabled then any value for opcache.
|
||||
;revalidate_freq will basically be ignored. You should really only ever enable
|
||||
;this during development, you don't really want to enable this setting for a production application.
|
||||
opcache.revalidate_freq=0
|
||||
|
||||
; Enables or disables file search in include_path optimization
|
||||
;opcache.revalidate_path=0
|
||||
|
||||
; If disabled, all PHPDoc comments are dropped from the code to reduce the
|
||||
; size of the optimized code.
|
||||
opcache.save_comments=1
|
||||
|
||||
; If enabled, a fast shutdown sequence is used for the accelerated code
|
||||
; Depending on the used Memory Manager this may cause some incompatibilities.
|
||||
opcache.fast_shutdown=1
|
||||
|
||||
; Allow file existence override (file_exists, etc.) performance feature.
|
||||
;opcache.enable_file_override=0
|
||||
|
||||
; A bitmask, where each bit enables or disables the appropriate OPcache
|
||||
; passes
|
||||
;opcache.optimization_level=0xffffffff
|
||||
|
||||
;opcache.inherited_hack=1
|
||||
;opcache.dups_fix=0
|
||||
|
||||
; The location of the OPcache blacklist file (wildcards allowed).
|
||||
; Each OPcache blacklist file is a text file that holds the names of files
|
||||
; that should not be accelerated. The file format is to add each filename
|
||||
; to a new line. The filename may be a full path or just a file prefix
|
||||
; (i.e., /var/www/x blacklists all the files and directories in /var/www
|
||||
; that start with 'x'). Line starting with a ; are ignored (comments).
|
||||
;opcache.blacklist_filename=
|
||||
|
||||
; Allows exclusion of large files from being cached. By default all files
|
||||
; are cached.
|
||||
;opcache.max_file_size=0
|
||||
|
||||
; Check the cache checksum each N requests.
|
||||
; The default value of "0" means that the checks are disabled.
|
||||
;opcache.consistency_checks=0
|
||||
|
||||
; How long to wait (in seconds) for a scheduled restart to begin if the cache
|
||||
; is not being accessed.
|
||||
;opcache.force_restart_timeout=180
|
||||
|
||||
; OPcache error_log file name. Empty string assumes "stderr".
|
||||
;opcache.error_log=
|
||||
|
||||
; All OPcache errors go to the Web server log.
|
||||
; By default, only fatal errors (level 0) or errors (level 1) are logged.
|
||||
; You can also enable warnings (level 2), info messages (level 3) or
|
||||
; debug messages (level 4).
|
||||
;opcache.log_verbosity_level=1
|
||||
|
||||
; Preferred Shared Memory back-end. Leave empty and let the system decide.
|
||||
;opcache.preferred_memory_model=
|
||||
|
||||
; Protect the shared memory from unexpected writing during script execution.
|
||||
; Useful for internal debugging only.
|
||||
;opcache.protect_memory=0
|
||||
|
||||
; Allows calling OPcache API functions only from PHP scripts which path is
|
||||
; started from specified string. The default "" means no restriction
|
||||
;opcache.restrict_api=
|
||||
|
||||
; Mapping base of shared memory segments (for Windows only). All the PHP
|
||||
; processes have to map shared memory into the same address space. This
|
||||
; directive allows to manually fix the "Unable to reattach to base address"
|
||||
; errors.
|
||||
opcache.mmap_base=0x20000000
|
||||
|
||||
; Enables and sets the second level cache directory.
|
||||
; It should improve performance when SHM memory is full, at server restart or
|
||||
; SHM reset. The default "" disables file based caching.
|
||||
;opcache.file_cache=
|
||||
|
||||
; Enables or disables opcode caching in shared memory.
|
||||
;opcache.file_cache_only=0
|
||||
|
||||
; Enables or disables checksum validation when script loaded from file cache.
|
||||
;opcache.file_cache_consistency_checks=1
|
||||
|
||||
; Implies opcache.file_cache_only=1 for a certain process that failed to
|
||||
; reattach to the shared memory (for Windows only). Explicitly enabled file
|
||||
; cache is required.
|
||||
opcache.file_cache_fallback=1
|
||||
|
||||
; Enables or disables copying of PHP code (text segment) into HUGE PAGES.
|
||||
; This should improve performance, but requires appropriate OS configuration.
|
||||
;opcache.huge_code_pages=1
|
||||
|
||||
; Validate cached file permissions.
|
||||
; opcache.validate_permission=0
|
||||
|
||||
; Prevent name collisions in chroot'ed environment.
|
||||
; opcache.validate_root=0
|
6
docker/php.ini
Normal file
6
docker/php.ini
Normal file
|
@ -0,0 +1,6 @@
|
|||
date.timezone=UTC
|
||||
display_errors=Off
|
||||
log_errors=On
|
||||
upload_max_filesize= 80M
|
||||
post_max_size= 80M
|
||||
memory_limit = 256M
|
32
docker/supervisord.conf
Normal file
32
docker/supervisord.conf
Normal file
|
@ -0,0 +1,32 @@
|
|||
[supervisord]
|
||||
nodaemon=true
|
||||
user=www-data
|
||||
logfile=/var/log/supervisor/supervisord.log
|
||||
logfile_maxbytes = 50MB
|
||||
pidfile=/tmp/supervisord.pid
|
||||
directory = /tmp
|
||||
|
||||
|
||||
[program:php-fpm]
|
||||
command=/usr/local/sbin/php-fpm
|
||||
numprocs=1
|
||||
autostart=true
|
||||
autorestart=true
|
||||
stderr_logfile=/var/log/supervisor/php-fpm.err.log
|
||||
stdout_logfile=/var/log/supervisor/php-fpm.out.log
|
||||
user=www-data
|
||||
priority=1
|
||||
|
||||
[program:nginx]
|
||||
command=/usr/sbin/nginx -g "daemon off;"
|
||||
numprocs=1
|
||||
autostart=true
|
||||
autorestart=true
|
||||
stderr_logfile=/var/log/nginx/nginx.err.log
|
||||
stdout_logfile=/var/log/nginx/nginx.out.log
|
||||
logfile_maxbytes = 50MB
|
||||
user=www-data
|
||||
priority=2
|
||||
|
||||
[include]
|
||||
files = /etc/supervisor/conf.d/*.conf
|
28
error.php
Normal file
28
error.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
// error.php
|
||||
|
||||
// Check if the error message is provided in the URL
|
||||
if (isset($_GET["message"])) {
|
||||
$errorMessage = $_GET["message"];
|
||||
|
||||
// Set the HTTP status code
|
||||
http_response_code(400); // You can change this to the appropriate HTTP status code
|
||||
|
||||
// Define the JSON response
|
||||
$response = [
|
||||
'error' => true,
|
||||
'message' => $errorMessage,
|
||||
];
|
||||
|
||||
// Set the content type to JSON
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Output the JSON response
|
||||
echo json_encode($response);
|
||||
exit();
|
||||
} else {
|
||||
// If no error message is provided, redirect to a generic error page or home page
|
||||
header("Location: /"); // You can change this to the appropriate URL
|
||||
exit();
|
||||
}
|
||||
?>
|
37
index.html
Normal file
37
index.html
Normal file
|
@ -0,0 +1,37 @@
|
|||
<!doctype html>
|
||||
<html class="no-js" lang="">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Redirecting to index.php</title>
|
||||
<link rel="stylesheet" href="resources/css/style.css">
|
||||
<meta name="description" content="">
|
||||
|
||||
<meta property="og:title" content="">
|
||||
<meta property="og:type" content="">
|
||||
<meta property="og:url" content="">
|
||||
<meta property="og:image" content="">
|
||||
|
||||
<link rel="icon" href="resources/favicon.ico" sizes="any">
|
||||
<link rel="icon" href="resources/logo.svg" type="image/svg+xml">
|
||||
<link rel="apple-touch-icon" href="logo.svg">
|
||||
|
||||
<link rel="manifest" href="site.webmanifest">
|
||||
<meta name="theme-color" content="#fafafa">
|
||||
|
||||
<script>
|
||||
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<!-- Add your site or application content here -->
|
||||
<p>Greetings from PulseBridge</p>
|
||||
<div id="error-container"></div>
|
||||
<script src="resources/js/app.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
155
index.php
Normal file
155
index.php
Normal file
|
@ -0,0 +1,155 @@
|
|||
<?php
|
||||
namespace nmpl\pulsebridge;
|
||||
|
||||
class App
|
||||
{
|
||||
|
||||
public function dependencyInstaller(): void
|
||||
{
|
||||
// Check if the autoload.php file exists
|
||||
$autoloadPath = __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
if (!file_exists($autoloadPath)) {
|
||||
|
||||
|
||||
echo '<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="PulseBridge - SMS Gateway Software">
|
||||
|
||||
<meta property="og:title" content="PulseBridge - SMS Gateway Software">
|
||||
<meta property="og:type" content="">
|
||||
<meta property="og:url" content="">
|
||||
<meta property="og:image" content="">
|
||||
|
||||
<link rel="icon" href="./resources/favicon.ico" sizes="any">
|
||||
<link rel="icon" href="./resources/logo.svg" type="image/svg+xml">
|
||||
<link rel="apple-touch-icon" href="./resources/logo.svg">
|
||||
|
||||
<link rel="manifest" href="site.webmanifest">
|
||||
<meta name="theme-color" content="#fafafa">
|
||||
|
||||
<title>Installation-PulseBridge</title>
|
||||
<link rel="icon" href="./resources/favicon.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" href="./resources/css/fontawesome.min.css" crossorigin="anonymous">
|
||||
</head>
|
||||
<body>
|
||||
<img src="./resources/logo.svg" alt="logo" style="display: block; margin: 10px auto; width: 120px; height: auto; border-radius: 10px; box-shadow: 0 4px 8px rgb(37, 150, 190); "/>
|
||||
';
|
||||
|
||||
|
||||
// Output the result of the shell command
|
||||
echo '<div style="max-width: 600px; margin: auto; padding: 20px; border: 2px solid #3498db; background-color: #ecf0f1; border-radius: 10px; text-align: center;">';
|
||||
echo "<h2 style='color: #3498db;'>Application Installation</h2>";
|
||||
echo "<p style='font-size: 18px; margin-bottom: 20px;'>Before installing, make sure you have Composer installed on your system. If not, follow the steps below:</p>";
|
||||
echo "<p style='font-size: small; color: #2c3e50;'>**Make sure you have shell_exec() enabled in your php.ini</p>";
|
||||
// Step1: Download Composer
|
||||
echo "<h3 style='color: #2c3e50;'>Step 1: Download Composer</h3>";
|
||||
|
||||
echo "<p style='font-size: 16px; color: #2c3e50;'>Visit <a href='https://getcomposer.org/download/' target='_blank' style='color: #3498db; text-decoration: none;'>Composer Download Page</a> to download and install Composer on your system or click the button below:</p>";
|
||||
|
||||
|
||||
// Check if composer.phar is not present in the project root
|
||||
if (!file_exists('composer.phar')) {
|
||||
// Display the form to download and install Composer
|
||||
echo '<form id="composerForm" method="post">';
|
||||
echo '<input type="submit" name="composerCommand" value="Download & Install Composer" style="background-color: #3498db; color: #fff; padding: 10px 15px; border: none; cursor: pointer; font-size: 16px; border-radius: 5px;">';
|
||||
echo '</form>';
|
||||
} else {
|
||||
// Display a success message with a completed icon
|
||||
echo '<div style="text-align: center; color: #2ecc71; font-size: 18px;">';
|
||||
echo '<i class="fa-solid fa-circle-check" style="color: #2ecc71; font-size: 24px;"></i> Composer is installed successfully!';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
echo "<hr style='border-color: #3498db; margin: 20px 0;'>";
|
||||
|
||||
// Step2: Install App
|
||||
echo "<h3 style='color: #2c3e50;'>Step 2: Install Application</h3>";
|
||||
|
||||
echo "<p style='font-size: 16px; color: #2c3e50;'>Once Composer is installed, run <strong>composer install</strong> in your terminal or click the button below:</p>";
|
||||
|
||||
// Output a professional-looking HTML button that triggers another shell command when clicked
|
||||
echo '<form id="executeForm" method="post">';
|
||||
echo '<input type="submit" name="executeCommand" value="Install Application" style="background-color: #3498db; color: #fff; padding: 10px 15px; border: none; cursor: pointer; font-size: 16px; border-radius: 5px;">';
|
||||
echo '</form>';
|
||||
|
||||
// Output PHP version and server info
|
||||
echo "<p style='font-size: 16px; margin-top: 20px; color: #2c3e50;'>Server Information: " . $_SERVER['SERVER_SOFTWARE'] . "</p>";
|
||||
|
||||
// Check if the button is clicked
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['executeCommand'])) {
|
||||
// Replace 'your-other-shell-command' with the actual shell command you want to execute
|
||||
$otherShellCommand = 'php composer.phar install';
|
||||
|
||||
// Execute the other shell command
|
||||
$otherOutput = shell_exec($otherShellCommand);
|
||||
|
||||
// Output the result of the other shell command
|
||||
echo "<p style='font-size: 16px; margin-top: 20px; color: #2c3e50;'>Application Successfully Installed! Redirecting in 5 Seconds <pre style='font-size: 14px; background-color: #ecf0f1; padding: 10px; border-radius: 5px; overflow-x: auto;'>$otherOutput</pre></p>";
|
||||
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['composerCommand'])) {
|
||||
|
||||
$command1 = "php -r \"copy('https://getcomposer.org/installer', 'composer-setup.php');\"";
|
||||
$command2 = "php composer-setup.php";
|
||||
$command3 = "php -r \"unlink('composer-setup.php');\"";
|
||||
|
||||
|
||||
// Execute the other shell command
|
||||
$otherOutput1 = shell_exec($command1);
|
||||
$otherOutput2 = shell_exec($command2);
|
||||
$otherOutput3 = shell_exec($command3);
|
||||
|
||||
echo "<p style='font-size: 16px; margin-top: 20px; color: #2c3e50;'>Installing...:
|
||||
<pre style='font-size: 14px; background-color: #ecf0f1; padding: 10px; border-radius: 5px; overflow-x: auto;'>$otherOutput1</pre>
|
||||
<pre style='font-size: 14px; background-color: #ecf0f1; padding: 10px; border-radius: 5px; overflow-x: auto;'>$otherOutput2</pre>
|
||||
<pre style='font-size: 14px; background-color: #ecf0f1; padding: 10px; border-radius: 5px; overflow-x: auto;'>$otherOutput3</pre>
|
||||
</p>";
|
||||
|
||||
header("Refresh:0");
|
||||
}
|
||||
|
||||
// You can choose to exit here or continue with the rest of your code
|
||||
echo '</div><footer style="text-align: center;">
|
||||
<p>Bitmutex Technologies © '. date ('Y') .' | <a href="mailto:amit@bitmutex.com">support@bitmutex.com</a></p>
|
||||
</footer>';
|
||||
|
||||
exit();
|
||||
}
|
||||
|
||||
// Include the autoload.php file
|
||||
require $autoloadPath;
|
||||
}
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$logger = new Logger(__DIR__ . '/logs/');
|
||||
|
||||
$driver = new Driver($logger);
|
||||
}
|
||||
}
|
||||
|
||||
// Instantiate and run the App
|
||||
$app = new App();
|
||||
|
||||
// If the button is clicked, wait for 2 seconds using JavaScript before submitting the form
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['executeCommand'])) {
|
||||
echo '<script>
|
||||
setTimeout(function() {
|
||||
document.getElementById("executeForm").submit();
|
||||
}, 2000);
|
||||
|
||||
setTimeout(function() {
|
||||
document.getElementById("composerForm").submit();
|
||||
}, 2000);
|
||||
</script>';
|
||||
}
|
||||
|
||||
$app->dependencyInstaller();
|
||||
$app->run();
|
25
phpunit.xml
Normal file
25
phpunit.xml
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
bootstrap="vendor/autoload.php"
|
||||
backupGlobals="true" colors="true"
|
||||
processIsolation="true" stopOnFailure="true"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.0/phpunit.xsd"
|
||||
cacheDirectory=".phpunit.cache">
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="Pulsebridge Test Suite">
|
||||
<directory>src\test</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<php>
|
||||
<env name="APP_ENV" value="testing"/>
|
||||
</php>
|
||||
|
||||
<source>
|
||||
<include>
|
||||
<directory suffix=".php">src/test/</directory>
|
||||
</include>
|
||||
</source>
|
||||
|
||||
</phpunit>
|
7
resources/css/bootstrap.min.css
vendored
Normal file
7
resources/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
9
resources/css/fontawesome.min.css
vendored
Normal file
9
resources/css/fontawesome.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
247
resources/css/style.css
Normal file
247
resources/css/style.css
Normal file
|
@ -0,0 +1,247 @@
|
|||
/*! HTML5 Boilerplate v9.0.0-RC1 | MIT License | https://html5boilerplate.com/ */
|
||||
|
||||
/* main.css 3.0.0 | MIT License | https://github.com/h5bp/main.css#readme */
|
||||
/*
|
||||
* What follows is the result of much research on cross-browser styling.
|
||||
* Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal,
|
||||
* Kroc Camen, and the H5BP dev community and team.
|
||||
*/
|
||||
|
||||
/* ==========================================================================
|
||||
Base styles: opinionated defaults
|
||||
========================================================================== */
|
||||
|
||||
html {
|
||||
color: #222;
|
||||
font-size: 1em;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove text-shadow in selection highlight:
|
||||
* https://twitter.com/miketaylr/status/12228805301
|
||||
*
|
||||
* Customize the background color to match your design.
|
||||
*/
|
||||
|
||||
::-moz-selection {
|
||||
background: #b3d4fc;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: #b3d4fc;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
/*
|
||||
* A better looking default horizontal rule
|
||||
*/
|
||||
|
||||
hr {
|
||||
display: block;
|
||||
height: 1px;
|
||||
border: 0;
|
||||
border-top: 1px solid #ccc;
|
||||
margin: 1em 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove the gap between audio, canvas, iframes,
|
||||
* images, videos and the bottom of their containers:
|
||||
* https://github.com/h5bp/html5-boilerplate/issues/440
|
||||
*/
|
||||
|
||||
audio,
|
||||
canvas,
|
||||
iframe,
|
||||
img,
|
||||
svg,
|
||||
video {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove default fieldset styles.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allow only vertical resizing of textareas.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Author's custom styles
|
||||
========================================================================== */
|
||||
|
||||
/* ==========================================================================
|
||||
Helper classes
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Hide visually and from screen readers
|
||||
*/
|
||||
|
||||
.hidden,
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/*
|
||||
* Hide only visually, but have it available for screen readers:
|
||||
* https://snook.ca/archives/html_and_css/hiding-content-for-accessibility
|
||||
*
|
||||
* 1. For long content, line feeds are not interpreted as spaces and small width
|
||||
* causes content to wrap 1 word per line:
|
||||
* https://medium.com/@jessebeach/beware-smushed-off-screen-accessible-text-5952a4c2cbfe
|
||||
*/
|
||||
|
||||
.visually-hidden {
|
||||
border: 0;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
white-space: nowrap;
|
||||
width: 1px;
|
||||
/* 1 */
|
||||
}
|
||||
|
||||
/*
|
||||
* Extends the .visually-hidden class to allow the element
|
||||
* to be focusable when navigated to via the keyboard:
|
||||
* https://www.drupal.org/node/897638
|
||||
*/
|
||||
|
||||
.visually-hidden.focusable:active,
|
||||
.visually-hidden.focusable:focus {
|
||||
clip: auto;
|
||||
height: auto;
|
||||
margin: 0;
|
||||
overflow: visible;
|
||||
position: static;
|
||||
white-space: inherit;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
* Hide visually and from screen readers, but maintain layout
|
||||
*/
|
||||
|
||||
.invisible {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/*
|
||||
* Clearfix: contain floats
|
||||
*
|
||||
* The use of `table` rather than `block` is only necessary if using
|
||||
* `::before` to contain the top-margins of child elements.
|
||||
*/
|
||||
|
||||
.clearfix::before,
|
||||
.clearfix::after {
|
||||
content: "";
|
||||
display: table;
|
||||
}
|
||||
|
||||
.clearfix::after {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
EXAMPLE Media Queries for Responsive Design.
|
||||
These examples override the primary ('mobile first') styles.
|
||||
Modify as content requires.
|
||||
========================================================================== */
|
||||
|
||||
@media only screen and (min-width: 35em) {
|
||||
/* Style adjustments for viewports that meet the condition */
|
||||
}
|
||||
|
||||
@media print,
|
||||
(-webkit-min-device-pixel-ratio: 1.25),
|
||||
(min-resolution: 1.25dppx),
|
||||
(min-resolution: 120dpi) {
|
||||
/* Style adjustments for high resolution devices */
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Print styles.
|
||||
Inlined to avoid the additional HTTP request:
|
||||
https://www.phpied.com/delay-loading-your-print-css/
|
||||
========================================================================== */
|
||||
|
||||
@media print {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
background: #fff !important;
|
||||
color: #000 !important;
|
||||
/* Black prints faster */
|
||||
box-shadow: none !important;
|
||||
text-shadow: none !important;
|
||||
}
|
||||
|
||||
a,
|
||||
a:visited {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a[href]::after {
|
||||
content: " (" attr(href) ")";
|
||||
}
|
||||
|
||||
abbr[title]::after {
|
||||
content: " (" attr(title) ")";
|
||||
}
|
||||
|
||||
/*
|
||||
* Don't show links that are fragment identifiers,
|
||||
* or use the `javascript:` pseudo protocol
|
||||
*/
|
||||
a[href^="#"]::after,
|
||||
a[href^="javascript:"]::after {
|
||||
content: "";
|
||||
}
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap !important;
|
||||
}
|
||||
|
||||
pre,
|
||||
blockquote {
|
||||
border: 1px solid #999;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
tr,
|
||||
img {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
p,
|
||||
h2,
|
||||
h3 {
|
||||
orphans: 3;
|
||||
widows: 3;
|
||||
}
|
||||
|
||||
h2,
|
||||
h3 {
|
||||
page-break-after: avoid;
|
||||
}
|
||||
}
|
||||
|
BIN
resources/favicon.ico
Normal file
BIN
resources/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
5
resources/js/app.js
Normal file
5
resources/js/app.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
window.onload = function() {
|
||||
setTimeout(function() {
|
||||
window.location.href = 'index.php';
|
||||
}, 0);
|
||||
}
|
7
resources/js/bootstrap.min.js
vendored
Normal file
7
resources/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
resources/js/jquery-3.6.4.slim.min.js
vendored
Normal file
2
resources/js/jquery-3.6.4.slim.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6
resources/js/popper.min.js
vendored
Normal file
6
resources/js/popper.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
0
resources/js/vendor/.gitkeep
vendored
Normal file
0
resources/js/vendor/.gitkeep
vendored
Normal file
10
resources/logo.svg
Normal file
10
resources/logo.svg
Normal file
|
@ -0,0 +1,10 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" fill="#2ecc71">
|
||||
<!-- Background Circle -->
|
||||
<circle cx="32" cy="32" r="30" fill="#3498db" />
|
||||
|
||||
<!-- SMS Icon -->
|
||||
<g fill="#fff">
|
||||
<rect x="25" y="22" width="14" height="4" rx="2" />
|
||||
<path d="M46.6 14.4a2 2 0 00-2.8 0L32 26.2l-8.8-8.8a2 2 0 00-2.8 0 2 2 0 000 2.8l10.6 10.6a2 2 0 002.8 0L46.6 17a2 2 0 000-2.8z" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 423 B |
5
robots.txt
Normal file
5
robots.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
# www.robotstxt.org/
|
||||
|
||||
# Allow crawling of all content
|
||||
User-agent: *
|
||||
Disallow:
|
12
site.webmanifest
Normal file
12
site.webmanifest
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"short_name": "",
|
||||
"name": "",
|
||||
"icons": [{
|
||||
"src": "icon.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
}],
|
||||
"start_url": "/?utm_source=homescreen",
|
||||
"background_color": "#fafafa",
|
||||
"theme_color": "#fafafa"
|
||||
}
|
41
src/Driver.php
Normal file
41
src/Driver.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace Nmpl\Pulsebridge;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
|
||||
class Driver
|
||||
{
|
||||
private $logger;
|
||||
|
||||
public function __construct(Logger $logger)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
|
||||
// Log a message indicating the initialization
|
||||
$this->logger->log('Driver initialization started.');
|
||||
|
||||
// Some tricks to load the Pulsebridge and PageRenderer classes in different situations
|
||||
if (!class_exists('Nmpl\Pulsebridge\Pulsebridge') || !class_exists('Nmpl\Pulsebridge\PageRenderer')) {
|
||||
if (file_exists('src\Pulsebridge.php') && file_exists('src\PageRenderer.php')) {
|
||||
// Quick load of Pulsebridge and PageRenderer without using composer
|
||||
require_once 'Pulsebridge.php';
|
||||
require_once 'PageRenderer.php';
|
||||
// Log a message indicating the successful loading of classes
|
||||
$this->logger->log('Pulsebridge and PageRenderer classes loaded without Composer.');
|
||||
} else {
|
||||
// Composer autoload
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
// Log a message indicating the use of Composer autoload
|
||||
$this->logger->log('Pulsebridge and PageRenderer classes loaded using Composer autoload.');
|
||||
}
|
||||
}
|
||||
|
||||
// Log a message indicating the completion of initialization
|
||||
$this->logger->log('Driver initialization completed.');
|
||||
|
||||
}
|
||||
|
||||
}
|
24
src/Logger.php
Normal file
24
src/Logger.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Nmpl\Pulsebridge;
|
||||
|
||||
class Logger
|
||||
{
|
||||
|
||||
private $logPath;
|
||||
|
||||
public function __construct($logPath)
|
||||
{
|
||||
$this->logPath = $logPath;
|
||||
if (!file_exists($logPath)) {
|
||||
mkdir($logPath, 0777, true);
|
||||
}
|
||||
}
|
||||
|
||||
public function log($message)
|
||||
{
|
||||
$logFile = $this->logPath . 'app_log_' . date('Y-m-d') . '.log';
|
||||
ini_set("error_log", $logFile);
|
||||
error_log('[' . date('Y-m-d H:i:s') . '] ' . $message . PHP_EOL, 3, $logFile);
|
||||
}
|
||||
}
|
583
src/PageRenderer.php
Normal file
583
src/PageRenderer.php
Normal file
|
@ -0,0 +1,583 @@
|
|||
<?php
|
||||
|
||||
namespace Nmpl\Pulsebridge;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
class PageRenderer
|
||||
{
|
||||
public static function renderHeader($title)
|
||||
{
|
||||
|
||||
if (!isset($smsgateway)) {
|
||||
$smsgateway = new Pulsebridge();
|
||||
$smsgateway->setDataPath(dirname(__DIR__) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR);
|
||||
|
||||
}
|
||||
|
||||
echo '<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="PulseBridge - SMS Gateway Software">
|
||||
|
||||
<meta property="og:title" content="PulseBridge - SMS Gateway Software">
|
||||
<meta property="og:type" content="">
|
||||
<meta property="og:url" content="">
|
||||
<meta property="og:image" content="">
|
||||
|
||||
<link rel="icon" href="./resources/favicon.ico" sizes="any">
|
||||
<link rel="icon" href="./resources/logo.svg" type="image/svg+xml">
|
||||
<link rel="apple-touch-icon" href="./resources/logo.svg">
|
||||
|
||||
<link rel="manifest" href="site.webmanifest">
|
||||
<meta name="theme-color" content="#fafafa">
|
||||
|
||||
<title>' . $title . '</title>
|
||||
<link rel="icon" href="./resources/favicon.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" href="./resources/css/bootstrap.min.css" >
|
||||
<link rel="stylesheet" href="./resources/css/fontawesome.min.css" crossorigin="anonymous">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<script src="./resources/js/jquery-3.6.4.slim.min.js" ></script>
|
||||
<script src="./resources/js/popper.min.js" ></script>
|
||||
<script src="./resources/js/bootstrap.min.js"></script>
|
||||
';
|
||||
|
||||
// Navbar
|
||||
echo '<header class="bg-dark text-white py-3">
|
||||
<div class="container">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<!-- Include the SVG logo -->
|
||||
<a class="navbar-brand" href="index.php">
|
||||
<img src="resources/logo.svg" width="30" height="30" class="d-inline-block align-top" alt="Logo">
|
||||
Pulsebridge
|
||||
</a>
|
||||
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#" data-toggle="modal" data-target="#faqModal">FAQ</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://wa.link/9exku8">Contact</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</header>';
|
||||
|
||||
// FAQ Modal
|
||||
echo '<div class="modal fade" id="faqModal" tabindex="-1" role="dialog" aria-labelledby="faqModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="faqModalLabel">Frequently Asked Questions</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- Add your FAQ content here -->
|
||||
<p><strong>Q: What is the SMS Gateway application?</strong></p>
|
||||
<p><strong>A:</strong> The SMS Gateway application is a software that runs on an Android mobile phone, serving as a bridge between web interfaces and SMS functionality.</p>
|
||||
|
||||
<p><strong>Q: How does it work?</strong></p>
|
||||
<p><strong>A:</strong> The SMS Gateway application on the mobile phone receives commands and messages from the web interface, allowing users to send and receive SMS messages programmatically.</p>
|
||||
|
||||
<p><strong>Q: Can I use the SMS Gateway for business purposes?</strong></p>
|
||||
<p><strong>A:</strong> Yes, the SMS Gateway is designed to support business use cases, enabling you to integrate SMS functionality into your applications, services, or processes.</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
echo '<div class="container mt-4">';
|
||||
echo '<h1 class="mt-4 display-4 text-center">PulseBridge<sup style="font-size: 30px;">' . $smsgateway->getVersion() . '</sup></h1>';
|
||||
echo '<p class="mt-4 display-4 text-center" style="font-size: large;">SMS gateway web-application with an HTTP interface to connect with a PulseBridge android <a href="#">app</a> and send/receive SMS. </p>';
|
||||
echo '<p class="mt-4 display-4 text-center" style="font-size: small;">App Version: ' . $smsgateway->getVersion() . '</p>';
|
||||
echo '<hr>';
|
||||
}
|
||||
|
||||
public static function renderFooter()
|
||||
{
|
||||
echo '</div>'; // Close the container div
|
||||
echo '<footer class="mt-5 text-center">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<strong>Server Time:</strong> <span id="serverTime"></span> -
|
||||
<strong>Client Time:</strong> <span id="clientTime"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<strong>PHP Run Mode:</strong> ' . php_sapi_name() . '
|
||||
<strong>Web Server:</strong> ' . $_SERVER['SERVER_SOFTWARE'] .
|
||||
(function_exists('apache_get_version') ? apache_get_version() : '') . '
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<hr>
|
||||
<small class="text-muted">Pulsebridge - Bitmutex Technologies © ' . date("Y") . '</small>
|
||||
</div>
|
||||
</div>
|
||||
</footer>';
|
||||
echo '<script>
|
||||
function updateServerTime() {
|
||||
var currentTime = new Date();
|
||||
var hours = currentTime.getHours();
|
||||
var minutes = currentTime.getMinutes();
|
||||
var seconds = currentTime.getSeconds();
|
||||
|
||||
// Add leading zero if needed
|
||||
minutes = (minutes < 10 ? "0" : "") + minutes;
|
||||
seconds = (seconds < 10 ? "0" : "") + seconds;
|
||||
|
||||
var formattedTime = hours + ":" + minutes + ":" + seconds;
|
||||
document.getElementById("serverTime").innerHTML = formattedTime;
|
||||
}
|
||||
|
||||
function updateClientTime() {
|
||||
var currentTime = new Date();
|
||||
var hours = currentTime.getHours();
|
||||
var minutes = currentTime.getMinutes();
|
||||
var seconds = currentTime.getSeconds();
|
||||
|
||||
// Add leading zero if needed
|
||||
minutes = (minutes < 10 ? "0" : "") + minutes;
|
||||
seconds = (seconds < 10 ? "0" : "") + seconds;
|
||||
|
||||
var formattedTime = hours + ":" + minutes + ":" + seconds;
|
||||
document.getElementById("clientTime").innerHTML = formattedTime;
|
||||
}
|
||||
|
||||
// Update server time every second
|
||||
setInterval(updateServerTime, 1000);
|
||||
|
||||
// Update client time every second
|
||||
setInterval(updateClientTime, 1000);
|
||||
|
||||
// Initial updates
|
||||
updateServerTime();
|
||||
updateClientTime();
|
||||
</script>';
|
||||
echo '</body></html>';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Create an Pulsebridge instance if not done yet, and define the flat-file data folder
|
||||
if (!isset($smsgateway)) {
|
||||
$smsgateway = new Pulsebridge();
|
||||
$smsgateway->setDataPath(dirname(__DIR__) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR);
|
||||
}
|
||||
|
||||
// Detect the URL with php file
|
||||
//$url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . ($_SERVER['PHP_SELF']);
|
||||
|
||||
// Detect the URL without php file
|
||||
$url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'];
|
||||
|
||||
// Retrieve some parameters
|
||||
$command = isset($_GET["m"]) ? "m" : (isset($_GET["i"]) ? "i" : (isset($_GET["e"]) ? "e" : ""));
|
||||
$h = isset($_GET["h"]) ? $_GET["h"] : "";
|
||||
$mid = isset($_GET["mid"]) ? $_GET["mid"] : "";
|
||||
|
||||
// Correct the international format of the phone number if needed
|
||||
$to = isset($_GET["to"]) ? $_GET["to"] : "";
|
||||
|
||||
// Validate the "to" field
|
||||
if (!empty($to) && !preg_match('/^\d{10}$/', $to)) {
|
||||
// Handle validation error (e.g., redirect to an error page)
|
||||
header("Location: error.php?message=Invalid phone number");
|
||||
exit();
|
||||
}
|
||||
|
||||
|
||||
if ("00" == substr($to, 0, 2)) {
|
||||
$to = "+" . substr($to, 2, strlen($to) - 2);
|
||||
}
|
||||
|
||||
|
||||
// Define a default message if needed
|
||||
$message = isset($_GET["message"]) ? $_GET["message"] : ""; // Hello World 😉
|
||||
|
||||
// Validate the "message" field
|
||||
if (!empty($message) && strlen($message) < 5) {
|
||||
// Handle validation error (e.g., redirect to an error page)
|
||||
header("Location: error.php?message=Message should be at least 5 characters");
|
||||
exit();
|
||||
}
|
||||
|
||||
|
||||
// Retrieve the device id
|
||||
$id = isset($_GET["id"]) ? $_GET["id"] : "";
|
||||
$device_id = $id;
|
||||
if ((!empty($to)) && empty($device_id)) {
|
||||
$device_id = substr(md5(uniqid("", true)), 0, 16);
|
||||
} elseif ((empty($to)) && (!empty($device_id)) && (!file_exists($smsgateway->getDataPath() . $device_id))) {
|
||||
$device_id = "";
|
||||
}
|
||||
|
||||
// Calculate the device hash based on the secret
|
||||
$device_h = $smsgateway->calculateAuthenticationHash($device_id);
|
||||
|
||||
// Check if device hash is valid for an existing device, otherwise flush the device id
|
||||
if ((!empty($id)) && ($h != $device_h)) {
|
||||
$device_id = "";
|
||||
} else {
|
||||
$smsgateway->updateDataStructure($id);
|
||||
}
|
||||
|
||||
if ((!empty($mid)) && (!empty($device_id))) {
|
||||
$message_state = "MISSING";
|
||||
$message_array = $smsgateway->readSentStatus($id, $mid);
|
||||
if (isset($message_array[0]['status'])) {
|
||||
$message_state = $message_array[0]['status'];
|
||||
}
|
||||
echo $message_state;
|
||||
} elseif ("e" == $command) {
|
||||
// An enhanced command can be implemented here
|
||||
} elseif (("m" == $command) && (!empty($device_id))) {
|
||||
PageRenderer::renderHeader('Pulsebridge App');
|
||||
//include 'header.php';
|
||||
echo '<body>';
|
||||
echo '<div class="container mt-4">';
|
||||
|
||||
// Back Button with Bootstrap styling and centered
|
||||
echo '<div class="text-center"><a href="'.$_SERVER['HTTP_REFERER'].'" class="btn btn-primary mx-auto" style="border-radius: 5px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); transition: background-color 0.3s;">Back</a></div>';
|
||||
|
||||
// Display messages resume for the "m" command
|
||||
echo '<div class="container mt-5">';
|
||||
|
||||
echo '<div id="accordion">';
|
||||
|
||||
// New SMS messages received
|
||||
echo '<div class="card mb-4">';
|
||||
echo '<div class="card-header" id="newMessagesHeading">';
|
||||
echo '<h2 class="mb-0">';
|
||||
echo '<button class="btn btn-link" data-toggle="collapse" data-target="#newMessagesCollapse" aria-expanded="true" aria-controls="newMessagesCollapse">';
|
||||
echo '<p style="color: #1d2124;font-size: large;">New SMS messages received <i class="fas fa-chevron-down float-right"></i></p>';
|
||||
echo '</button>';
|
||||
echo '</h2>';
|
||||
echo '</div>';
|
||||
echo '<div id="newMessagesCollapse" class="collapse show" aria-labelledby="newMessagesHeading" data-parent="#accordion">';
|
||||
echo '<div class="card-body">';
|
||||
$new_messages = $smsgateway->readNewMessages($id);
|
||||
if (count($new_messages) > 0) {
|
||||
foreach ($new_messages as $message) {
|
||||
echo '<div class="card mb-2">';
|
||||
echo '<div class="card-body">';
|
||||
echo '<p class="text-muted"><span class="badge">' . date("Y-m-d H:i:s", $message['sms_received'] / 1000) . '</span></p>';
|
||||
echo '<span class="badge badge-primary">' . $message['from'] . '</span>: ' . $message['content'];
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
}
|
||||
} else {
|
||||
echo '<p class="text-muted m-3">No messages to display.</p>';
|
||||
}
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
|
||||
// All SMS messages received
|
||||
echo '<div class="card mb-4">';
|
||||
echo '<div class="card-header" id="allMessagesHeading">';
|
||||
echo '<h2 class="mb-0">';
|
||||
echo '<button class="btn btn-link collapsed" data-toggle="collapse" data-target="#allMessagesCollapse" aria-expanded="false" aria-controls="allMessagesCollapse">';
|
||||
echo '<p style="color: #1d2124;font-size: large;">All SMS messages received <i class="fas fa-chevron-down float-right"></i></p>';
|
||||
echo '</button>';
|
||||
echo '</h2>';
|
||||
echo '</div>';
|
||||
echo '<div id="allMessagesCollapse" class="collapse show" aria-labelledby="allMessagesHeading" data-parent="#accordion">';
|
||||
echo '<div class="card-body">';
|
||||
$all_messages = $smsgateway->readAllMessages($id);
|
||||
if (count($all_messages) > 0) {
|
||||
foreach ($all_messages as $message) {
|
||||
echo '<div class="card mb-2">';
|
||||
echo '<div class="card-body">';
|
||||
echo '<p class="text-muted"><span class="badge">' . date("Y-m-d H:i:s", $message['sms_received'] / 1000) . '</span></p>';
|
||||
echo '<span class="badge badge-primary">' . $message['from'] . '</span>: ' . $message['content'];
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
}
|
||||
} else {
|
||||
echo '<p class="text-muted m-3">No messages to display.</p>';
|
||||
}
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
|
||||
|
||||
// All SMS messages sent
|
||||
echo '<div class="card mb-4">';
|
||||
echo '<div class="card-header" id="sentMessagesHeading">';
|
||||
echo '<h2 class="mb-0">';
|
||||
echo '<button class="btn btn-link collapsed" data-toggle="collapse" data-target="#sentMessagesCollapse" aria-expanded="false" aria-controls="sentMessagesCollapse">';
|
||||
echo '<p style="color: #1d2124;font-size: large;">All SMS messages Sent <i class="fas fa-chevron-down float-right"></i></p>';
|
||||
echo '</button>';
|
||||
echo '</h2>';
|
||||
echo '</div>';
|
||||
echo '<div id="sentMessagesCollapse" class="collapse show" aria-labelledby="sentMessagesHeading" data-parent="#accordion">';
|
||||
echo '<div class="card-body">';
|
||||
$sent_messages = $smsgateway->readAllSentStatus($id);
|
||||
if (count($sent_messages) > 0) {
|
||||
foreach ($sent_messages as $message) {
|
||||
echo '<div class="card mb-2">';
|
||||
echo '<div class="card-body">';
|
||||
echo '<p class="text-muted"><span class="badge">' . date("Y-m-d H:i:s", $message['last_update'] / 1000) . '</span></p>';
|
||||
$statusBadgeClass = ($message['status'] === 'DELIVERED') ? 'badge badge-success' : 'badge badge-secondary';
|
||||
echo '<span class="' . $statusBadgeClass . '">' . $message['status'] . '</span>';
|
||||
echo ' <a class="badge badge-info" href="' . $url . '?id=' . $device_id . '&h=' . $h . '&mid=' . $message['message_id'] . '" target="track_' . $message['message_id'] . '">Track</a>';
|
||||
echo ' : ' . $message['content'];
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
}
|
||||
} else {
|
||||
echo '<p class="text-muted m-3">No messages to display.</p>';
|
||||
}
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
|
||||
echo '</div>'; // Closing accordion container
|
||||
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
|
||||
//include 'footer.php';
|
||||
PageRenderer::renderFooter();
|
||||
echo '</body></html>';
|
||||
}
|
||||
|
||||
elseif (empty($device_id) || ("i" == $command)) {
|
||||
// Display basic usage info
|
||||
if ("" == $to) {
|
||||
$autofocus_to = "autofocus=\"autofocus\"";
|
||||
$autofocus_message = "";
|
||||
} else {
|
||||
$autofocus_to = "";
|
||||
$autofocus_message = "autofocus=\"autofocus\"";
|
||||
}
|
||||
//include header;
|
||||
PageRenderer::renderHeader('Pulsebridge App');
|
||||
|
||||
echo '
|
||||
<body>
|
||||
<div class="container mt-4">
|
||||
|
||||
<form action="' . htmlspecialchars($_SERVER["PHP_SELF"]) . '" method="get" class="mb-4" onsubmit="return validateForm()" name="smsForm">';
|
||||
|
||||
|
||||
if (!empty($id)) {
|
||||
// Back Button with Bootstrap styling and centered
|
||||
echo '<div class="text-center"><a href="'.$_SERVER['HTTP_REFERER'].'" class="btn btn-primary mx-auto" style="border-radius: 5px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); transition: background-color 0.3s;">Back</a></div>';
|
||||
|
||||
echo '<div class="form-group">';
|
||||
echo ' <label for="id">Device Identification:</label>';
|
||||
echo ' <input class="form-control form-control-sm font-weight-bold" size="18" type="text" name="id" placeholder="e.g. 01234567890abcdef" value="' . $id . '" readonly>';
|
||||
echo '</div>';
|
||||
if (!empty($h)) {
|
||||
echo '<div class="form-group">';
|
||||
echo ' <label for="h">Secret Hash for the Device:</label>';
|
||||
echo ' <input class="form-control form-control-sm font-weight-bold" size="8" type="text" name="h" placeholder="e.g. abcdef" value="' . $h . '" readonly>';
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
$outputForm = ' <div class="form-group">
|
||||
<label for="to">Destination mobile phone number:</label>
|
||||
<input class="form-control" size="20" ' . $autofocus_to . ' type="tel" name="to" placeholder="e.g. 00123456789012" value="' . $to . '">
|
||||
</div>';
|
||||
$outputForm .= '
|
||||
<div class="form-group">
|
||||
<label for="message">Message:</label>
|
||||
<textarea ' . $autofocus_message . ' class="form-control" columns="40" rows="5" placeholder="e.g. Please call me back asap !" name="message" autocomplete="on" maxlength="300" cols="80" wrap="soft">' . $message . '</textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success">Send SMS message</button>
|
||||
<button type="reset" class="btn btn-secondary">Reset</button>
|
||||
<p></p>';
|
||||
$outputForm .= '</form>
|
||||
<script>
|
||||
function goToLink() {
|
||||
window.location.href = \'index.php?to=0123456789&message=Tests 🙂\';
|
||||
}
|
||||
function validateForm() {
|
||||
var to = document.forms["smsForm"]["to"].value;
|
||||
var message = document.forms["smsForm"]["message"].value;
|
||||
|
||||
// Validate the "to" field
|
||||
if (to === "" || !/^\d{10}$/.test(to)) {
|
||||
alert("Enter a valid 10-digit phone number.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate the "message" field
|
||||
if (message === "" || message.length < 5) {
|
||||
alert("Message should be at least 5 characters.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
</script>';
|
||||
|
||||
if ("" == $h) {
|
||||
$outputForm .= ' <button class="btn btn-primary" onclick="goToLink()">Setup Credentials</button> <br>';
|
||||
$outputForm .= '... or send a first message calling this URL: ';
|
||||
$outputForm .= '<div style="display: flex; align-items: center;">';
|
||||
$outputForm .= '<div style="margin-right: 10px;">';
|
||||
$outputForm .= '<b><a href="' . $url . '?to=0123456789&message=Hello+world" target="_blank" data-toggle="tooltip" title="Send Your First SMS!"><i class="fas fa-link"></i> ' . $url . '?to=0123456789&message=Hello+world</a></b>';
|
||||
$outputForm .= '</div>';
|
||||
$outputForm .= '</div>';
|
||||
} else {
|
||||
$outputForm .= '... or send a direct message calling this URL: ';
|
||||
$outputForm .= '<div style="display: flex; align-items: center;">';
|
||||
$outputForm .= '<div style="margin-right: 10px;">';
|
||||
$outputForm .= '<b><a href="' . $url . '?id=' . $id . '&h=' . $h . '&to=' . (("" != $to) ? $to : "0123456789") . '&message=' . urlencode(("" != $message) ? $message : "Hello world 🙂") . '" target="_blank" data-toggle="tooltip" title="Send Direct Message"><i class="fas fa-envelope"></i> ' . $url . '?id=' . $id . '&h=' . $h . '&to=' . (("" != $to) ? $to : "0123456789") . '&message=' . urlencode(("" != $message) ? $message : "Hello world 🙂") . '</a></b>';
|
||||
$outputForm .= '</div>';
|
||||
$outputForm .= '</div>';
|
||||
}
|
||||
$outputForm .= '</div>';
|
||||
|
||||
echo $outputForm;
|
||||
PageRenderer::renderFooter();
|
||||
//include 'footer.php';
|
||||
echo ' </body> </html>';
|
||||
|
||||
}
|
||||
|
||||
elseif (!empty($to)) {
|
||||
// Push the message on the server
|
||||
$message_id = $smsgateway->sendMessage($device_id, $to, $message);
|
||||
|
||||
$outputSent = '
|
||||
<body>
|
||||
<div class="container mt-4">';
|
||||
if (empty($message_id)) {
|
||||
header('X-SMSGateway-State: FAILED');
|
||||
$outputSent .= '<meta name="X-SMSGateway-State" content="0">
|
||||
</head>
|
||||
<body>';
|
||||
} else {
|
||||
header('X-SMSGateway-State: NEW');
|
||||
header('X-SMSGateway-State-Url: ' . $url . '?id=' . $id . '&h=' . $h . '&mid=' . $message_id);
|
||||
header('X-SMSGateway-Message-Id: ' . $message_id);
|
||||
$outputSent .= '<meta name="X-SMSGateway-State" content="NEW">
|
||||
<meta name="X-SMSGateway-State-Url" content="' . $url . '?id=' . $id . '&h=' . $h . '&mid=' . $message_id . '">
|
||||
<meta name="X-SMSGateway-Message-Id" content="' . $message_id . '">
|
||||
</head>
|
||||
<body>';
|
||||
}
|
||||
|
||||
//include Header
|
||||
PageRenderer::renderHeader('Pulsebridge App');
|
||||
|
||||
// Display usage information
|
||||
$outputSent .= '<div class="container mt-4">';
|
||||
if (empty($messageId)) {
|
||||
$outputSent .= '<div class="alert alert-success" role="alert">';
|
||||
$outputSent .= 'Message : <strong>' . htmlspecialchars($message) . '</strong> to phone number : <strong> ' . htmlspecialchars($to) . '</strong> successfully pushed on the server.';
|
||||
$outputSent .= '</div>';
|
||||
|
||||
$outputSent .= '<div class="alert alert-info" role="alert">';
|
||||
$outputSent .= 'Device Code : <strong>' . htmlspecialchars($device_id) . '</strong> <br>Device Hash: <strong>' . htmlspecialchars($device_h) .'</strong>';
|
||||
$outputSent .= '</div>';
|
||||
} else {
|
||||
$outputSent .= '<div class="alert alert-danger" role="alert">';
|
||||
$outputSent .= 'Message <strong>' . htmlspecialchars($message) . '</strong> for ' . htmlspecialchars($to) . ' failed to push on the server.';
|
||||
$outputSent .= '</div>';
|
||||
}
|
||||
|
||||
$outputSent .= '<div class="row">';
|
||||
$outputSent .= '<div class="col-md-6">';
|
||||
$outputSent .= '<h2 class="h4 mb-3">Installation Instructions</h2>';
|
||||
$outputSent .= '<p>If not done yet, please install the Android Pulsebridge App by clicking the link below: </p>';
|
||||
$outputSent .= '<a href="https://github.com/medic/cht-gateway/releases/latest" target="_blank" class="btn btn-primary"><i class="fas fa-download"></i> Download SMSGatewayApp</a><br><br>';
|
||||
|
||||
$outputSent .= '<h2 class="h4 mb-3">App Configuration</h2>';
|
||||
$outputSent .= '<p>Set the following URL in the Settings of the Android App:</p>';
|
||||
$outputSent .= '<div class="input-group mb-3">';
|
||||
$outputSent .= '<input type="text" class="form-control" id="app-url" value="' . htmlspecialchars($url) . '?id=' . htmlspecialchars($device_id) . '&h=' . htmlspecialchars($device_h) . '" readonly>';
|
||||
$outputSent .= '<button class="btn btn-outline-info" type="button" onclick="copyToClipboard()">Copy URL</button>';
|
||||
$outputSent .= '</div>';
|
||||
$outputSent .= '<small class="text-muted">Click "Copy URL" to copy the configuration URL to the clipboard.</small><br>';
|
||||
$outputSent .= '<small class="text-muted">Open Pulsebridge <a href="#">app</a> -> Settings -> Paste url to pulsebride url field</small>';
|
||||
|
||||
$outputSent .= '</div>';
|
||||
$outputSent .= '<div class="col-md-6">';
|
||||
$outputSent .= '<h2 class="h4 mb-3">Actions</h2>';
|
||||
$outputSent .= '<p>Check SMS messages or send more SMS messages:</p>';
|
||||
$outputSent .= '<a href="' . htmlspecialchars($url) . '?id=' . htmlspecialchars($device_id) . '&h=' . htmlspecialchars($device_h) . '&m" class="btn btn-success mb-2">Check SMS Messages <i class="bi bi-arrow-right"></i></a>';
|
||||
$outputSent .= '<a href="' . htmlspecialchars($url) . '?id=' . htmlspecialchars($device_id) . '&h=' . htmlspecialchars($device_h) . '&to=&message=&i" class="btn btn-primary mb-2">Send More SMS Messages <i class="bi bi-arrow-right"></i></a>';
|
||||
|
||||
$outputSent .= '<h2 class="h4 mb-3">Dev Usage</h2>';
|
||||
$outputSent .= '<p>Send Messages using HTTP GET on this URL from your application::</p>';
|
||||
$outputSent .= '<div style="display: flex; align-items: center;">';
|
||||
$outputSent .= '<div style="margin-right: 10px;">';
|
||||
$outputSent .= '<b><a href="' . $url . '?id=' . htmlspecialchars($device_id) . '&h=' . htmlspecialchars($device_h) . '&to=' . (("" != $to) ? $to : "0123456789") . '&message=' . urlencode(("" != $message) ? $message : "Hello world 🙂") . '" target="_blank" data-toggle="tooltip" title="Send Direct Message"><i class="fas fa-envelope"></i> ' . $url . '?id=' . htmlspecialchars($device_id) . '&h=' . htmlspecialchars($device_h) . '&to=' . (("" != $to) ? $to : "0123456789") . '&message=' . urlencode(("" != $message) ? $message : "Your Message from pulsebridge🙂") . '</a></b>';
|
||||
$outputSent .= '</div>';
|
||||
$outputSent .= '</div>';
|
||||
|
||||
|
||||
$outputSent .= '</div>';
|
||||
$outputSent .= '</div>';
|
||||
|
||||
$outputSent .= '</div>';
|
||||
|
||||
$outputSent .= '<script>';
|
||||
$outputSent .= 'function copyToClipboard() {';
|
||||
$outputSent .= ' var input = document.getElementById("app-url");';
|
||||
$outputSent .= ' input.select();';
|
||||
$outputSent .= ' document.execCommand("copy");';
|
||||
$outputSent .= ' var alertContainer = document.createElement("div");';
|
||||
$outputSent .= ' alertContainer.style.padding = "10px";'; // Adjust the padding as needed
|
||||
$outputSent .= ' alertContainer.style.position = "fixed";';
|
||||
$outputSent .= ' alertContainer.style.top = "60px";'; // Adjust the top distance as needed
|
||||
$outputSent .= ' alertContainer.style.right = "20px";'; // Adjust the right distance as needed
|
||||
$outputSent .= ' alertContainer.style.zIndex = "1000";'; // Adjust the z-index as needed
|
||||
$outputSent .= ' alertContainer.className = "alert-container";'; // Added a class for styling
|
||||
$outputSent .= ' var alertDiv = document.createElement("div");';
|
||||
$outputSent .= ' alertDiv.className = "alert alert-success alert-dismissible fade show";';
|
||||
$outputSent .= ' alertDiv.innerHTML = "URL copied to clipboard!";';
|
||||
$outputSent .= ' var closeButton = document.createElement("button");';
|
||||
$outputSent .= ' closeButton.type = "button";';
|
||||
$outputSent .= ' closeButton.className = "close";';
|
||||
$outputSent .= ' closeButton.setAttribute("data-dismiss", "alert");';
|
||||
$outputSent .= ' closeButton.innerHTML = "×";'; // "×" is the HTML entity for the close symbol (X)
|
||||
$outputSent .= ' alertDiv.appendChild(closeButton);';
|
||||
$outputSent .= ' alertContainer.appendChild(alertDiv);';
|
||||
$outputSent .= ' document.body.appendChild(alertContainer);';
|
||||
$outputSent .= ' setTimeout(function() {';
|
||||
$outputSent .= ' alertContainer.remove();';
|
||||
$outputSent .= ' }, 3000);'; // 3000 milliseconds = 3 seconds
|
||||
$outputSent .= '}';
|
||||
$outputSent .= '</script>';
|
||||
|
||||
echo $outputSent;
|
||||
//include 'footer';
|
||||
PageRenderer::renderFooter();
|
||||
} else {
|
||||
// Run the API server
|
||||
$smsgateway->apiServer();
|
||||
}
|
||||
|
||||
|
916
src/Pulsebridge.php
Normal file
916
src/Pulsebridge.php
Normal file
|
@ -0,0 +1,916 @@
|
|||
<?php
|
||||
/**
|
||||
* @file Pulsebridge.class.php
|
||||
* @brief Pulsebridge - flat-file based SMS gateway
|
||||
* PHP class using an open source Android app
|
||||
*
|
||||
* @mainpage
|
||||
*
|
||||
* Pulsebridge PHP class
|
||||
*
|
||||
* https://github.com/multiOTP/SMSGateway
|
||||
*
|
||||
* The Pulsebridge PHP class is a flat-file based SMS gateway for sending and
|
||||
* receiving SMS on an Android device using an open source SMS Gateway app.
|
||||
* (https://github.com/medic/cht-gateway)
|
||||
*
|
||||
* The Readme file contains additional information.
|
||||
*
|
||||
* PHP 5.3.0 or higher is supported.
|
||||
*
|
||||
* @author Amit Nandi (Bitmutex Technologies) <amit@bitmutex.com>
|
||||
* @version 1.1.5
|
||||
* @date 2023-09-20
|
||||
* @since 2022-09-10
|
||||
* @copyright (c) 2022-2024 Bitmutex Technologies
|
||||
* @copyright Apache 2.0 License
|
||||
*
|
||||
*//*
|
||||
*
|
||||
* Usage
|
||||
*
|
||||
* Public methods available:
|
||||
* apiServer($new_message_callback = "",
|
||||
* $update_callback = "",
|
||||
* $timeout_callback = "",
|
||||
* $post_raw = "")
|
||||
* archiveSuccessMessages($device_id = "")
|
||||
* calculateAuthenticationHash($device_id)
|
||||
* getDataPath()
|
||||
* getDeviceFolder()
|
||||
* getDeviceFolderArchive()
|
||||
* getDeviceFolderLogs()
|
||||
* getDeviceFolderReceive()
|
||||
* getDeviceFolderSend()
|
||||
* getDeviceId()
|
||||
* getDevicePathArchive()
|
||||
* getDevicePathLogs()
|
||||
* getDevicePathReceive()
|
||||
* getDevicePathSend()
|
||||
* getDeviceTimeout()
|
||||
* getMessagesToSend($device_id = "")
|
||||
* getPurgeArchiveTime()
|
||||
* getSuccessArchiveTime()
|
||||
* getTimeoutDevices()
|
||||
* getVersion()
|
||||
* handleMessages($post_data)
|
||||
* handleUpdates($post_data)
|
||||
* purgeArchiveMessages($device_id = "")
|
||||
* reactivatePushedMessages($pushed_timeout = 0)
|
||||
* readAllMessages($device_id = "")
|
||||
* readAllSentStatus($device_id = "")
|
||||
* readAllArchivedStatus($device_id = "")
|
||||
* readMessage($device_id = "", $message_id = "*", $message_filter = "*")
|
||||
* readNewMessages($device_id = "")
|
||||
* readSentStatus($device_id = "", $message_id = "*", $message_filter = "*")
|
||||
* sendMessage($device_id, $to, $content)
|
||||
* setDataPath($data_path)
|
||||
* setDeviceFolder($device_folder)
|
||||
* setDeviceId($device_id)
|
||||
* setDeviceTimeout($device_timeout)
|
||||
* setPurgeArchiveTime($purge_archive_time)
|
||||
* setSuccessArchiveTime($success_archive_time)
|
||||
* updateDataStructure($device_id)
|
||||
* writeLog($log_message)
|
||||
*
|
||||
*
|
||||
* Examples
|
||||
*
|
||||
* // Example 1 - Send message using Android phone with "demo" id
|
||||
* use multiOTP\Pulsebridge\Pulsebridge;
|
||||
* require_once('Pulsebridge.php');
|
||||
* $smsgateway = new Pulsebridge();
|
||||
* $smsgateway->setDataPath(__DIR__ . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR);
|
||||
* $smsgateway->setSharedSecret("secret");
|
||||
* $device_id = "demo";
|
||||
* $to = "+1234567890";
|
||||
* $message = "Demo message";
|
||||
* $device_h = $smsgateway->calculateAuthenticationHash($device_id);
|
||||
* $message_id = $smsgateway->sendMessage($device_id, $to, $message);
|
||||
* echo "Full URL for Android app URL: https://......./?id=$device_id&h=$device_h";
|
||||
*
|
||||
*
|
||||
* // Example 2 - API server with call back function for new messages
|
||||
* use multiOTP\Pulsebridge\Pulsebridge;
|
||||
* require_once('Pulsebridge.php');
|
||||
* function new_message_handling($array) {
|
||||
* // Handling $array
|
||||
* //[["device_id" => "device_id",
|
||||
* // "message_id" => "message_id",
|
||||
* // "from" => "from_phone",
|
||||
* // "sms_sent" => "sms_sent_timestamp",
|
||||
* // "sms_received" => "sms_received_timestamp",
|
||||
* // "content" => "message_content",
|
||||
* // "last_update" => "last update timestamp (ms)",
|
||||
* // "status" => "message-status"
|
||||
* // ],
|
||||
* // [...]
|
||||
* //]
|
||||
* }
|
||||
* $smsgateway = new Pulsebridge();
|
||||
* $smsgateway->setDataPath(__DIR__ . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR);
|
||||
* $smsgateway->apiServer("new_message_handling");
|
||||
*
|
||||
*
|
||||
* External device needed
|
||||
*
|
||||
* Android phone with SMS Gateway app installed
|
||||
* (https://github.com/medic/cht-gateway/releases/latest)
|
||||
*********************************************************************/
|
||||
|
||||
namespace Nmpl\Pulsebridge;
|
||||
|
||||
/**
|
||||
* Pulsebridge - flat-file based SMS gateway PHP class using an open source Android app
|
||||
*
|
||||
* @author Andre Liechti (SysCo systemes de communication sa) <info@multiotp.net>
|
||||
*/
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
class Pulsebridge
|
||||
{
|
||||
/**
|
||||
* The Pulsebridge Version number.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const VERSION = '1.1.5';
|
||||
|
||||
/**
|
||||
* The device timeout in seconds.
|
||||
* Default of 5 minutes (300sec).
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $DeviceTimeout = 300;
|
||||
|
||||
/**
|
||||
* The success archive time in seconds.
|
||||
* Default of 1 day (1 * 86400 sec).
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $SuccessArchiveTime = 1 * 86400;
|
||||
|
||||
/**
|
||||
* The purge archive time in seconds.
|
||||
* Default of 90 days (90 * 86400 sec).
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $PurgeArchiveTime = 90 * 86400;
|
||||
|
||||
/**
|
||||
* The purge log time in seconds.
|
||||
* Default of 365 days (365 * 86400 sec).
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $PurgeLogTime = 365 * 86400;
|
||||
|
||||
/**
|
||||
* The flat-file based data path (with terminal directory separator).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $DataPath = '';
|
||||
|
||||
/**
|
||||
* The Android device id.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $DeviceId = '';
|
||||
|
||||
/**
|
||||
* The shared secret to calculate hash authentication.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $SharedSecret = 'secret';
|
||||
|
||||
/**
|
||||
* The flat-file based device folder (without terminal directory separator).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $DeviceFolder = '';
|
||||
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// Define a default data path in the system temporary folder
|
||||
$this->setDataPath(sys_get_temp_dir() . DIRECTORY_SEPARATOR);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Class destructor.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
// $this->...;
|
||||
}
|
||||
|
||||
public function getVersion()
|
||||
{
|
||||
return self::VERSION;
|
||||
}
|
||||
|
||||
private function getIPAddress() {
|
||||
|
||||
if(!empty($_SERVER['HTTP_CLIENT_IP'])) {
|
||||
$ip = $_SERVER['HTTP_CLIENT_IP'];
|
||||
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
||||
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
|
||||
} else {
|
||||
$ip = $_SERVER['REMOTE_ADDR'];
|
||||
}
|
||||
return $ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the flat-file data path
|
||||
*
|
||||
* @param string $data_path The flat-file data path (with terminal directory separator)
|
||||
*
|
||||
* @return bool true on success, false if folder is not available
|
||||
*/
|
||||
public function setDataPath(
|
||||
$data_path
|
||||
) {
|
||||
if (file_exists($data_path)) {
|
||||
if (substr($data_path, -strlen(DIRECTORY_SEPARATOR)) != DIRECTORY_SEPARATOR) {
|
||||
$data_path.= DIRECTORY_SEPARATOR;
|
||||
}
|
||||
$this->DataPath = $data_path;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the flat-file data path
|
||||
*
|
||||
* @return string The flat-file data path (with terminal directory separator)
|
||||
*/
|
||||
public function getDataPath()
|
||||
{
|
||||
return $this->DataPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set device id.
|
||||
*
|
||||
* @param string $device_id The Android device id (which is in the URL)
|
||||
*/
|
||||
public function setDeviceId(
|
||||
$device_id
|
||||
) {
|
||||
$this->DeviceId = $device_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get device id.
|
||||
*
|
||||
* @return string The Android device id (which is in the URL)
|
||||
*/
|
||||
public function getDeviceId()
|
||||
{
|
||||
return $this->DeviceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set shared secret.
|
||||
*
|
||||
* @param string $shared_secret The shared secret to calculate hash authentication
|
||||
*/
|
||||
public function setSharedSecret(
|
||||
$shared_secret
|
||||
) {
|
||||
$this->SharedSecret = $shared_secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shared secret.
|
||||
*
|
||||
* @return string The Android device id (which is in the URL)
|
||||
*/
|
||||
public function getSharedSecret()
|
||||
{
|
||||
return $this->SharedSecret;
|
||||
}
|
||||
|
||||
public function setDeviceTimeout(
|
||||
$device_timeout
|
||||
) {
|
||||
$this->DeviceTimeout = intval($device_timeout);
|
||||
}
|
||||
|
||||
public function getDeviceTimeout()
|
||||
{
|
||||
return intval($this->DeviceTimeout);
|
||||
}
|
||||
|
||||
public function setSuccessArchiveTime(
|
||||
$success_archive_time
|
||||
) {
|
||||
$this->SuccessArchiveTime = intval($success_archive_time);
|
||||
}
|
||||
|
||||
public function getSuccessArchiveTime()
|
||||
{
|
||||
return intval($this->SuccessArchiveTime);
|
||||
}
|
||||
|
||||
public function setPurgeArchiveTime(
|
||||
$purge_archive_time
|
||||
) {
|
||||
$this->PurgeArchiveTime = intval($purge_archive_time);
|
||||
}
|
||||
|
||||
public function getPurgeArchiveTime()
|
||||
{
|
||||
return intval($this->PurgeArchiveTime);
|
||||
}
|
||||
|
||||
public function setPurgeLogTime(
|
||||
$purge_log_time
|
||||
) {
|
||||
$this->PurgeLogTime = intval($purge_log_time);
|
||||
}
|
||||
|
||||
public function getPurgeLogTime()
|
||||
{
|
||||
return intval($this->PurgeLogTime);
|
||||
}
|
||||
|
||||
public function setDeviceFolder(
|
||||
$device_folder
|
||||
) {
|
||||
$this->DeviceFolder = $device_folder;
|
||||
}
|
||||
|
||||
public function getDeviceFolder()
|
||||
{
|
||||
return $this->DeviceFolder;
|
||||
}
|
||||
|
||||
public function getDeviceFolderLogs()
|
||||
{
|
||||
return $this->DeviceFolder . DIRECTORY_SEPARATOR . "logs";
|
||||
}
|
||||
|
||||
public function getDeviceFolderSend()
|
||||
{
|
||||
return $this->DeviceFolder . DIRECTORY_SEPARATOR . "send";
|
||||
}
|
||||
|
||||
public function getDeviceFolderReceive()
|
||||
{
|
||||
return $this->DeviceFolder . DIRECTORY_SEPARATOR . "receive";
|
||||
}
|
||||
|
||||
public function getDeviceFolderArchive()
|
||||
{
|
||||
return $this->DeviceFolder . DIRECTORY_SEPARATOR . "archive";
|
||||
}
|
||||
|
||||
public function getDevicePathLogs()
|
||||
{
|
||||
return $this->getDeviceFolderLogs() . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
public function getDevicePathSend()
|
||||
{
|
||||
return $this->getDeviceFolderSend() . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
public function getDevicePathReceive()
|
||||
{
|
||||
return $this->getDeviceFolderReceive() . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
public function getDevicePathArchive()
|
||||
{
|
||||
return $this->getDeviceFolderArchive() . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
public function handleMessages(
|
||||
$post_data
|
||||
) {
|
||||
$result_array = array();
|
||||
$extract_data = json_decode(str_replace(chr(13), "", $post_data), true);
|
||||
if (null != $extract_data) {
|
||||
if (isset($extract_data["messages"])) {
|
||||
foreach($extract_data["messages"] as $message) {
|
||||
if (isset($message["id"])) {
|
||||
$from = (isset($message["from"]) ? $message["from"] : "");
|
||||
$sms_sent = (isset($message["sms_sent"]) ? $message["sms_sent"] : "");
|
||||
$sms_received = (isset($message["sms_received"]) ? $message["sms_received"] : "");
|
||||
$content = (isset($message["content"]) ? $message["content"] : "");
|
||||
$message_data = "from:$from\n";
|
||||
$message_data.= "sms_sent:$sms_sent\n";
|
||||
$message_data.= "sms_received:$sms_received\n";
|
||||
$message_data.= $content;
|
||||
file_put_contents($this->getDevicePathReceive() . $message["id"] . ".UNREAD", $message_data);
|
||||
|
||||
array_push($result_array, ["device_id" => $this->getDeviceId(),
|
||||
"message_id" => $message["id"],
|
||||
"from" => $from,
|
||||
"sms_sent" => $sms_sent,
|
||||
"sms_received" => $sms_received,
|
||||
"content" => $content,
|
||||
"last_update" => time() . "000",
|
||||
"status" => "UNREAD"
|
||||
]);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result_array;
|
||||
}
|
||||
|
||||
public function handleUpdates(
|
||||
$post_data
|
||||
) {
|
||||
$result_array = array();
|
||||
$extract_data = json_decode(str_replace(chr(13), "", $post_data), true);
|
||||
if (null != $extract_data) {
|
||||
if (isset($extract_data["updates"])) {
|
||||
foreach($extract_data["updates"] as $update) {
|
||||
if (isset($update["id"])) {
|
||||
if (isset($update["status"])) {
|
||||
$message_array = glob($this->getDevicePathSend() . $update["id"] . ".*");
|
||||
if (1 == count($message_array)) {
|
||||
$extract_data = json_decode(str_replace(chr(13), "", file_get_contents($message_array[0])), true);
|
||||
$content = "";
|
||||
$to = "";
|
||||
if (null != $extract_data) {
|
||||
$content = isset($extract_data["content"]) ? $extract_data["content"] : "";
|
||||
$to = isset($extract_data["to"]) ? $extract_data["to"] : "";
|
||||
}
|
||||
array_push($result_array, ["device_id" => $this->getDeviceId(),
|
||||
"message_id" => $update["id"],
|
||||
"to" => $to,
|
||||
"content" => $content,
|
||||
"last_update" => filemtime($message_array[0]) . "000",
|
||||
"status" => $update["status"]
|
||||
]);
|
||||
$updated_message = $this->getDevicePathSend() . $update["id"] . "." . $update["status"];
|
||||
rename($message_array[0], $updated_message);
|
||||
touch($updated_message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result_array;
|
||||
}
|
||||
|
||||
public function updateDataStructure(
|
||||
$device_id,
|
||||
$touch = true
|
||||
) {
|
||||
$result = false;
|
||||
$this->setDeviceId($device_id);
|
||||
if (file_exists($this->getDataPath()) && (!empty($device_id))) {
|
||||
$this->setDeviceFolder($this->getDataPath() . $this->getDeviceId());
|
||||
if (!file_exists($this->getDeviceFolder())) {
|
||||
mkdir($this->getDeviceFolder());
|
||||
}
|
||||
if ($touch) {
|
||||
touch($this->getDeviceFolder());
|
||||
}
|
||||
if (!file_exists($this->getDeviceFolderLogs())) {
|
||||
mkdir($this->getDeviceFolderLogs());
|
||||
}
|
||||
if (!file_exists($this->getDeviceFolderSend())) {
|
||||
mkdir($this->getDeviceFolderSend());
|
||||
}
|
||||
if (!file_exists($this->getDeviceFolderReceive())) {
|
||||
mkdir($this->getDeviceFolderReceive());
|
||||
}
|
||||
if (!file_exists($this->getDeviceFolderArchive())) {
|
||||
mkdir($this->getDeviceFolderArchive());
|
||||
}
|
||||
$result = true;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function readNewMessages(
|
||||
$device_id = ""
|
||||
) {
|
||||
return $this->readMessage($device_id, "*", "UNREAD");
|
||||
}
|
||||
|
||||
public function readAllMessages(
|
||||
$device_id = ""
|
||||
) {
|
||||
return $this->readMessage($device_id);
|
||||
}
|
||||
|
||||
public function readMessage(
|
||||
$device_id = "",
|
||||
$message_id = "*",
|
||||
$message_filter = "*"
|
||||
) {
|
||||
$result_array = array();
|
||||
if (empty($device_id)) {
|
||||
$device_id = $this->getDeviceId();
|
||||
}
|
||||
if ($this->updateDataStructure($device_id)) {
|
||||
$messages_new_array = glob($this->getDevicePathReceive() . "$message_id.$message_filter");
|
||||
// Sort based on time, last update on the top
|
||||
usort($messages_new_array, function($a,$b){ return filemtime($b) - filemtime($a);});
|
||||
if (count($messages_new_array) > 0) {
|
||||
foreach($messages_new_array as $message) {
|
||||
$from = "";
|
||||
$sms_sent = "";
|
||||
$sms_received = "";
|
||||
$content = "";
|
||||
$line_count = 0;
|
||||
$file = fopen($message, "r");
|
||||
while(! feof($file)) {
|
||||
$line_count++;
|
||||
$line_content = fgets($file);
|
||||
if (1 == $line_count) {
|
||||
$from = str_replace("from:", "", $line_content);
|
||||
} elseif (2 == $line_count) {
|
||||
$sms_sent = str_replace("sms_sent:", "", $line_content);
|
||||
} elseif (3 == $line_count) {
|
||||
$sms_received = str_replace("sms_received:", "", $line_content);
|
||||
} else {
|
||||
if ($line_count > 4) {
|
||||
$content.= "\n";
|
||||
}
|
||||
$content.= $line_content;
|
||||
}
|
||||
}
|
||||
fclose($file);
|
||||
array_push($result_array, ["device_id" => $device_id,
|
||||
"message_id" => pathinfo($message)['filename'],
|
||||
"from" => $from,
|
||||
"sms_sent" => $sms_sent,
|
||||
"sms_received" => $sms_received,
|
||||
"content" => $content,
|
||||
"last_update" => filemtime($message) . "000",
|
||||
"status" => pathinfo($message)['extension']
|
||||
]);
|
||||
$message_read = str_replace(".UNREAD", ".READ", $message);
|
||||
if ($message_read != $message) {
|
||||
rename($message, $message_read);
|
||||
touch($message_read);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result_array;
|
||||
}
|
||||
|
||||
public function readAllArchivedStatus(
|
||||
$device_id = ""
|
||||
) {
|
||||
return $this->readSentStatus($device_id, "*", "*", $this->getDevicePathArchive());
|
||||
}
|
||||
|
||||
public function readAllSentStatus(
|
||||
$device_id = ""
|
||||
) {
|
||||
return $this->readSentStatus($device_id);
|
||||
}
|
||||
|
||||
public function readSentStatus(
|
||||
$device_id = "",
|
||||
$message_id = "*",
|
||||
$message_filter = "*",
|
||||
$message_folder = ""
|
||||
) {
|
||||
$result_array = array();
|
||||
if (empty($device_id)) {
|
||||
$device_id = $this->getDeviceId();
|
||||
}
|
||||
if (empty($message_folder)) {
|
||||
$message_folder = $this->getDevicePathSend();
|
||||
}
|
||||
if ($this->updateDataStructure($device_id)) {
|
||||
$messages_new_array = glob($message_folder . "$message_id.$message_filter");
|
||||
// Sort based on time, last update on the top
|
||||
usort($messages_new_array, function($a,$b){ return filemtime($b) - filemtime($a);});
|
||||
if (count($messages_new_array) > 0) {
|
||||
foreach($messages_new_array as $message) {
|
||||
$id = pathinfo($message)['filename'];
|
||||
$status = pathinfo($message)['extension'];
|
||||
$extract_data = json_decode(str_replace(chr(13), "", file_get_contents($message)), true);
|
||||
$content = "";
|
||||
$to = "";
|
||||
if (null != $extract_data) {
|
||||
$content = isset($extract_data["content"]) ? $extract_data["content"] : "";
|
||||
$to = isset($extract_data["to"]) ? $extract_data["to"] : "";
|
||||
} else {
|
||||
$content = "DEBUG: ".json_last_error_msg();
|
||||
}
|
||||
array_push($result_array, ["device_id" => $device_id,
|
||||
"message_id" => $id,
|
||||
"to" => $to,
|
||||
"content" => $content,
|
||||
"last_update" => filemtime($message) . "000",
|
||||
"status" => $status
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result_array;
|
||||
}
|
||||
|
||||
public function sendMessage(
|
||||
$device_id,
|
||||
$to,
|
||||
$content
|
||||
) {
|
||||
$message_id = "";
|
||||
if (empty($device_id)) {
|
||||
$device_id = $this->getDeviceId();
|
||||
}
|
||||
if ($this->updateDataStructure($device_id)) {
|
||||
$escape_content = addcslashes($content, "\\\"\n");
|
||||
$message_id = str_replace(".","-", uniqid("", true));
|
||||
$message_content = "{\"id\": \"$message_id\", \"to\": \"$to\", \"content\": \"$escape_content\"}";
|
||||
file_put_contents($this->getDevicePathSend() . $message_id . ".NEW", $message_content);
|
||||
}
|
||||
return $message_id;
|
||||
}
|
||||
|
||||
public function reactivatePushedMessages(
|
||||
$pushed_timeout = 0
|
||||
) {
|
||||
$messages_count = 0;
|
||||
if ($pushed_timeout > 0) {
|
||||
$messages_pushed_array = glob($this->getDevicePathSend() . "*.PUSHED");
|
||||
foreach($messages_pushed_array as $message_pushed) {
|
||||
if (time() > (filemtime($message_pushed) + $pushed_timeout)) {
|
||||
$message_new = str_replace(".PUSHED", ".NEW", $message_pushed);
|
||||
rename($message_pushed, $message_new);
|
||||
touch($message_new);
|
||||
$messages_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $messages_count;
|
||||
}
|
||||
|
||||
public function getMessagesToSend(
|
||||
$device_id = ""
|
||||
) {
|
||||
$result = "{";
|
||||
if (empty($device_id)) {
|
||||
$device_id = $this->getDeviceId();
|
||||
}
|
||||
if ($this->updateDataStructure($device_id)) {
|
||||
$messages_new_array = glob($this->getDevicePathSend() . "*.NEW");
|
||||
// Sort based on time, older on top
|
||||
usort($messages_new_array, function($a,$b){ return filemtime($a) - filemtime($b);});
|
||||
if (count($messages_new_array) > 0) {
|
||||
$result.= "\"messages\": [";
|
||||
foreach($messages_new_array as $message_new) {
|
||||
|
||||
$result.= file_get_contents($message_new) . ",";
|
||||
|
||||
$message_pushed = str_replace(".NEW", ".PUSHED", $message_new);
|
||||
rename($message_new, $message_pushed);
|
||||
touch($message_pushed);
|
||||
}
|
||||
$result = substr($result, 0, (strlen($result) - 1)) . "]";
|
||||
}
|
||||
}
|
||||
$result.= "}";
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getTimeoutDevices()
|
||||
{
|
||||
$result_array = array();
|
||||
if ($this->getDeviceTimeout() > 0) {
|
||||
$devices_array = glob($this->getDataPath() . "*");
|
||||
// Sort based on time, older on top
|
||||
usort($devices_array, function($a,$b){ return filemtime($a) - filemtime($b);});
|
||||
foreach($devices_array as $device) {
|
||||
if (is_dir($device) && ("." != $device) && (".." != $device)) {
|
||||
$last_update = filemtime($device);
|
||||
if (time() > ($last_update + $this->getDeviceTimeout())) {
|
||||
array_push($result_array, ["device_id" => pathinfo($device)['basename'],
|
||||
"last_update" => $last_update
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result_array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Archive successful messages, which are
|
||||
* DELIVERED sent messages and READ received messages
|
||||
*
|
||||
* @param string $device_id The Android device id (which is in the URL)
|
||||
*
|
||||
* @return int The number of messages archived
|
||||
*/
|
||||
public function archiveSuccessMessages(
|
||||
$device_id = ""
|
||||
) {
|
||||
$archived_messages = 0;
|
||||
if ($this->getSuccessArchiveTime() > 0) {
|
||||
if (empty($device_id)) {
|
||||
$device_id = $this->getDeviceId();
|
||||
}
|
||||
if ($this->updateDataStructure($device_id)) {
|
||||
$messages_array = glob($this->getDevicePathSend() . "*.DELIVERED");
|
||||
foreach($messages_array as $message) {
|
||||
if (time() > (filemtime($message) + $this->getSuccessArchiveTime())) {
|
||||
$message_archive = str_replace($this->getDevicePathSend(), $this->getDevicePathArchive(), $message);
|
||||
rename($message, $message_archive);
|
||||
$archived_messages++;
|
||||
}
|
||||
}
|
||||
$messages_array = glob($this->getDevicePathReceive() . "*.READ");
|
||||
foreach($messages_array as $message) {
|
||||
if (time() > (filemtime($message) + $this->getSuccessArchiveTime())) {
|
||||
$message_archive = str_replace($this->getDevicePathReceive(), $this->getDevicePathArchive(), $message);
|
||||
rename($message, $message_archive);
|
||||
$archived_messages++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $archived_messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge archived messages
|
||||
*
|
||||
* @param string $device_id The Android device id (which is in the URL)
|
||||
*
|
||||
* @return int The number of messages purged
|
||||
*/
|
||||
public function purgeArchiveMessages(
|
||||
$device_id = ""
|
||||
) {
|
||||
$purged_messages = 0;
|
||||
if ($this->getPurgeArchiveTime() > 0) {
|
||||
if (empty($device_id)) {
|
||||
$device_id = $this->getDeviceId();
|
||||
}
|
||||
if ($this->updateDataStructure($device_id)) {
|
||||
$messages_array = glob($this->getDevicePathArchive() . "*.*");
|
||||
foreach($messages_array as $message) {
|
||||
if (is_file($message) && (time() > (filemtime($message) + $this->getPurgeArchiveTime()))) {
|
||||
unlink($message);
|
||||
$purged_messages++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $purged_messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge log files
|
||||
*
|
||||
* @param string $device_id The Android device id (which is in the URL)
|
||||
*
|
||||
* @return int The number of messages purged
|
||||
*/
|
||||
public function purgeLogFiles(
|
||||
$device_id = ""
|
||||
) {
|
||||
$purged_files = 0;
|
||||
if ($this->getPurgeLogTime() > 0) {
|
||||
if (empty($device_id)) {
|
||||
$device_id = $this->getDeviceId();
|
||||
}
|
||||
if ($this->updateDataStructure($device_id)) {
|
||||
$log_array = glob($this->getDevicePathLogs() . "*.log");
|
||||
foreach($log_array as $log_file) {
|
||||
if (is_file($log_file) && (time() > (filemtime($log_file) + $this->getPurgeLogTime()))) {
|
||||
unlink($log_file);
|
||||
$purged_files++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $purged_files;
|
||||
}
|
||||
|
||||
public function writeLog(
|
||||
$log_message
|
||||
) {
|
||||
$result = false;
|
||||
if (("" != $this->getDeviceFolder()) && file_exists($this->getDeviceFolderLogs())) {
|
||||
file_put_contents($this->getDevicePathLogs() . date("Y-m-d").".log",
|
||||
date("Y-m-d H:i:s") . " " . $this->getIPAddress() . " " . $log_message."\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
$result = true;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function calculateAuthenticationHash(
|
||||
$device_id
|
||||
) {
|
||||
return substr(strtolower(md5($this->getSharedSecret() . "#salt@" . $device_id)), 0, 6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main API server, which displays directly the necessary information
|
||||
*
|
||||
* @param string $new_message_callback Callback action for new message
|
||||
* @param string $update_callback Callback action for updated status
|
||||
* @param string $timeout_callback Callback action for timeout detection
|
||||
* @param string $post_raw Forced raw post data (mainly for debugging and tests)
|
||||
*/
|
||||
public function apiServer(
|
||||
$new_message_callback = NULL,
|
||||
$update_callback = NULL,
|
||||
$timeout_callback = NULL,
|
||||
$post_raw = ""
|
||||
) {
|
||||
|
||||
$response_code = 200;
|
||||
$response = "";
|
||||
|
||||
$device_id = isset($_GET["id"]) ? $_GET["id"] : '';
|
||||
$device_h = isset($_GET["h"]) ? $_GET["h"] : '';
|
||||
|
||||
if ("" == $device_id) {
|
||||
$response_code = 404;
|
||||
} elseif (($device_h != $this->calculateAuthenticationHash($device_id))) {
|
||||
$response_code = 401;
|
||||
$device_id = "";
|
||||
}
|
||||
|
||||
if ($this->updateDataStructure($device_id)) {
|
||||
|
||||
//Json Response
|
||||
header('Content-Type: application/json');
|
||||
//ob_end_clean();
|
||||
$response = json_encode(["pulsebridge-gateway" => true]); // API Bridge Word
|
||||
|
||||
//manually json encoded response
|
||||
// $response = "{\"pulsebridge-gateway\": true}"; // API Bridge Word
|
||||
|
||||
if (!empty($post_raw)) {
|
||||
$post_data = $post_raw;
|
||||
} else {
|
||||
$post_data = file_get_contents("php://input");
|
||||
}
|
||||
|
||||
if (!empty($post_data)) {
|
||||
$this->writeLog($post_data);
|
||||
|
||||
$new_messages_array = $this->handleMessages($post_data);
|
||||
$updates_array = $this->handleUpdates($post_data);
|
||||
$this->reactivatePushedMessages($this->getDeviceTimeout());
|
||||
|
||||
$response = $this->getMessagesToSend();
|
||||
if (is_callable($new_message_callback)) {
|
||||
if (count($new_messages_array) > 0) {
|
||||
$new_message_callback($new_messages_array);
|
||||
foreach($new_messages_array as $message) {
|
||||
$this->readMessage("", $message["message_id"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_callable($update_callback)) {
|
||||
if (count($updates_array) > 0) {
|
||||
$update_callback($updates_array);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// Ordering and cleaning for the current device id
|
||||
$this->archiveSuccessMessages();
|
||||
$this->purgeArchiveMessages();
|
||||
$this->purgeLogFiles();
|
||||
}
|
||||
|
||||
if (is_callable($timeout_callback)) {
|
||||
if ($this->getDeviceTimeout() > 0) {
|
||||
$timeout_callback($this->getTimeoutDevices());
|
||||
}
|
||||
}
|
||||
|
||||
http_response_code($response_code);
|
||||
echo $response;
|
||||
}
|
||||
|
||||
}
|
31
src/test/PageRendererTest.php
Normal file
31
src/test/PageRendererTest.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
|
||||
use Nmpl\Pulsebridge\Logger;
|
||||
use Nmpl\Pulsebridge\PageRenderer;
|
||||
use Nmpl\Pulsebridge\Pulsebridge;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class PageRendererTest extends TestCase
|
||||
{
|
||||
public function testConstructor()
|
||||
{
|
||||
// Mock objects for Pulsebridge and Logger
|
||||
$pulsebridgeMock = $this->createMock(Pulsebridge::class);
|
||||
$loggerMock = $this->createMock(Logger::class);
|
||||
|
||||
// Expect the setDataPath method to be called on Pulsebridge
|
||||
$pulsebridgeMock->expects($this->once())
|
||||
->method('setDataPath');
|
||||
|
||||
// Expect the log method to be called on Logger
|
||||
$loggerMock->expects($this->once())
|
||||
->method('log')
|
||||
->with($this->equalTo('Renderer Instantiated'));
|
||||
|
||||
// Create PageRenderer instance with mock objects
|
||||
$renderer = new PageRenderer($pulsebridgeMock, $loggerMock);
|
||||
|
||||
}
|
||||
|
||||
}
|
50
src/test/PulsebridgeTest.php
Normal file
50
src/test/PulsebridgeTest.php
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Nmpl\Pulsebridge\Pulsebridge;
|
||||
|
||||
class PulsebridgeTest extends TestCase
|
||||
{
|
||||
private $smsgateway;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->smsgateway = new Pulsebridge();
|
||||
$this->smsgateway->setDataPath(__DIR__ . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR);
|
||||
$this->smsgateway->setSharedSecret("secret");
|
||||
}
|
||||
|
||||
public function testInitialization()
|
||||
{
|
||||
$this->assertInstanceOf(Pulsebridge::class, $this->smsgateway);
|
||||
}
|
||||
|
||||
public function testSetAndGetDeviceId()
|
||||
{
|
||||
$deviceId = "testDevice";
|
||||
$this->smsgateway->setDeviceId($deviceId);
|
||||
$this->assertEquals($deviceId, $this->smsgateway->getDeviceId());
|
||||
}
|
||||
|
||||
public function testSetAndGetSharedSecret()
|
||||
{
|
||||
$sharedSecret = "newSecret";
|
||||
$this->smsgateway->setSharedSecret($sharedSecret);
|
||||
$this->assertEquals($sharedSecret, $this->smsgateway->getSharedSecret());
|
||||
}
|
||||
|
||||
// Add tests for other getters and setters...
|
||||
|
||||
public function testSendMessage()
|
||||
{
|
||||
$to = "+1234567890";
|
||||
$content = "Test message content";
|
||||
$messageId = $this->smsgateway->sendMessage("testDevice", $to, $content);
|
||||
|
||||
// Assert that the message ID is not empty and follows the expected format
|
||||
$this->assertNotEmpty($messageId);
|
||||
$this->assertMatchesRegularExpression('/^[a-zA-Z0-9-]+$/', $messageId);
|
||||
|
||||
// Add more assertions for the sent message...
|
||||
}
|
||||
}
|
BIN
webfonts/fa-solid-900.ttf
Normal file
BIN
webfonts/fa-solid-900.ttf
Normal file
Binary file not shown.
BIN
webfonts/fa-solid-900.woff2
Normal file
BIN
webfonts/fa-solid-900.woff2
Normal file
Binary file not shown.
Loading…
Reference in a new issue