This commit is contained in:
Meylis Gazakow 2022-02-06 17:17:52 +03:00
commit cf1e57fa1c
99 changed files with 2512 additions and 123 deletions

View File

@ -30,7 +30,8 @@
"october/system": "1.1.*",
"october/backend": "1.1.*",
"october/cms": "1.1.*",
"laravel/framework": "~6.0"
"laravel/framework": "~6.0",
"franzose/laravel-smpp": "^1.4"
},
"require-dev": {
"phpunit/phpunit": "^8.4|^9.3.3",

View File

@ -155,7 +155,7 @@ return [
'providers' => array_merge(include(base_path('modules/system/providers.php')), [
// 'Illuminate\Html\HtmlServiceProvider', // Example
'LaravelSmpp\LaravelSmppServiceProvider',
'System\ServiceProvider',
]),

81
config/laravel-smpp.php Normal file
View File

@ -0,0 +1,81 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default SMPP settings
|--------------------------------------------------------------------------
|
| 1. "sender" is the SMS message sender, either phone number or something like ABCDEF.
| 2. "source_ton" is the sender's type of number
| 3. "source_npi" is the sender's numbering plan identification
| 4. "destination_ton" is the receiver's type of number
| 5. "destination_npi" is the receiver's numbering plan identification
|
| Usually SMPP providers provide these settings to their clients.
| Please refer to official SMPP protocol specification v3.4 to learn more about TON and NPI settings.
|
*/
'defaults' => [
'sender' => env('SMPP_SENDER','0773'),
'source_ton' => env('SMPP_SOURCE_TON',SMPP::TON_ALPHANUMERIC),
'source_npi' => env('SMPP_SOURCE_NPI',SMPP::NPI_UNKNOWN),
'destination_ton' => env('SMPP_DESTINATION_TON',SMPP::TON_NATIONAL),
'destination_npi' => env('SMPP_DESTINATION_NPI',SMPP::NPI_NATIONAL)
],
/*
|--------------------------------------------------------------------------
| Custom SMPP provider settings
|--------------------------------------------------------------------------
|
| Most of the time, settings shown under the "example" key are be provided by your SMPP provider.
| So if you don't have any of these settings, please contact your SMPP provider.
|
*/
'default' => env('SMPP_DEFAULT_PROVIDER','example'),
'providers' => [
'example' => [
'host' => '217.174.228.218',
'port' => 5019,
'timeout' => 10000,
'login' => 'birja',
'password' => 'Birj@1'
]
],
/*
|--------------------------------------------------------------------------
| SMPP transport settings
|--------------------------------------------------------------------------
|
| For all SMPP errors listed in "transport.catchables", exceptions
| thrown by SMPP will be suppressed and just logged.
|
*/
'transport' => [
'catchables' => [
SMPP::ESME_RBINDFAIL,
SMPP::ESME_RINVCMDID,
SMPP::ESME_RINVPARLEN
],
'force_ipv4' => true,
'debug' => true
],
/*
|--------------------------------------------------------------------------
| SMPP client settings
|--------------------------------------------------------------------------
*/
'client' => [
'system_type' => 'default',
'null_terminate_octetstrings' => false,
'debug' => true
]
];

View File

@ -195,6 +195,7 @@ return [
'name' => 'Name',
'surname' => 'Surname',
'mobile' => 'Phone number',
'username' => 'Phone number',
],
];

View File

@ -195,6 +195,7 @@ return [
'name' => 'Имя',
'surname' => 'Фаимилия',
'mobile' => 'Номер телефона',
'username' => 'Номер телефона',
],
];

View File

@ -197,6 +197,7 @@ return [
'name' => 'At',
'surname' => 'Familiýa',
'mobile' => 'Telefon belgiňiz',
'username' => 'Telefon belgi',
],
];

View File

@ -0,0 +1,60 @@
<?php namespace AhmadFatoni\ApiGenerator\Controllers\API;
use Cms\Classes\Controller;
use Illuminate\Http\Request;
use AhmadFatoni\ApiGenerator\Helpers\Helpers;
use Illuminate\Support\Facades\Validator;
use TPS\Birzha\Models\Contactmessage;
use TPS\Birzha\Models\Settings;
class ContactFormApiController extends Controller
{
protected $helpers;
public function __construct(Helpers $helpers)
{
parent::__construct();
$this->helpers = $helpers;
}
public function sendContactForm(Request $request) {
$rules = [
'name' => 'required|max:100',
'surname' => 'required|max:100',
'mobile' => 'required|min:6',
'email' => 'required|email|max:100',
'content' => 'required'
];
$data = $request->all();
$validator = Validator::make($data, $rules);
if($validator->fails()) {
return $this->helpers->apiArrayResponseBuilder(400, 'fail', $validator->errors() );
}
$contactMessage = new Contactmessage();
$contactMessage->fill($data);
$contactMessage->save();
$vars = [
'firstname' => $data['name'],
'lastname' => $data['surname'],
'mobile' => $data['mobile'],
'email' => $data['email'],
'content' => $data['content']
];
$admin_email = Settings::getValue('admin_email');
if($admin_email) {
\Mail::send('tps.birzha::mail.message', $vars, function($message) {
$message->to(Settings::getValue('admin_email'), 'Birzha Admin');
$message->subject('Контактная форма');
});
}
return response()->json('Contact message sent', 201);
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace AhmadFatoni\ApiGenerator\Controllers\API;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class EmailVerificationController extends KabinetAPIController
{
public function sendEmailVerificationLink()
{
if(!$this->user->email_verified) {
$code = sha1(time());
$vars = [
'verification_link' => url('verify-email', ['code' => $code])
];
try {
\Mail::send('rainlab.user::mail.email_verification', $vars, function($message) {
$message->to($this->user->email, 'Birzha User');
$message->subject('Подтверждение Email');
});
} catch(Throwable $th) {
\Log::info($th);
return response()->json('Cannot verify. Invalid email address.', 400);
}
$this->user->email_activation_code = $code;
$this->user->save();
return response()->json('Verification link has been sent. Log in to tmex.gov.tm before checking your email.', 201);
} else {
return response()->json('You have already verified your email address.', 200);
}
}
}

View File

@ -0,0 +1,51 @@
<?php namespace AhmadFatoni\ApiGenerator\Controllers\API;
use Illuminate\Http\Request;
use TPS\Birzha\Models\Settings;
class ExchangeRequestsController extends KabinetAPIController
{
protected $helpers;
public function __construct()
{
parent::__construct();
}
public function withdrawFromBalance(Request $request) {
$exRequest = $this->user->exchangerequests()->create([
'content' => 'Exchange creating a request',
'payed_for_request' => $request->get('fee'),
'status' => 'failed', // before transaction is saved
'currency' => $request->get('currency'),
'total_price' => $request->get('total_price'),
'converted_to_tmt' => $request->get('fee')
]);
if(!is_null($exRequest->transaction)) {
$exRequest->update(['status' => 'success']);
}
$vars = array_merge($request->all(), [
'phone' => $this->user->username,
'status' => $exRequest->status,
'withdraw_from_balance' => $exRequest->transaction->amount
]);
$admin_email = Settings::getValue('admin_email');
if($admin_email) {
\Mail::send('tps.birzha::mail.requests', $vars, function($message) use ($admin_email){
$message->to($admin_email, 'Birzha Admin');
$message->subject('Биржа - Запрос пользователя (раздел Импортные цены)');
});
}
return response()->json([
'status' => 201,
'response' => $exRequest,
'message' => 'Successfully created response'
], 201);
}
}

View File

@ -272,7 +272,10 @@ class MessagesapiController extends Controller
// return response()->json(['chatrooms' => $seller->chatrooms]);
// return response()->json(['users' => $seller->chatrooms->users]);
// return response()->json(['chatroomNeeded' => $chatroomNeeded]);
return $this->helpers->apiArrayResponseBuilder(200, 'success', ['messages' => $this->getMessagesFromChatroom($chatroomNeeded, $currentUser)]);
return $this->helpers->apiArrayResponseBuilder(200, 'success', [
'chatroom_id' => $chatroomNeeded->id,
'messages' => $this->getMessagesFromChatroom($chatroomNeeded, $currentUser)
]);
}

View File

@ -8,10 +8,15 @@ use Illuminate\Support\Facades\Validator;
class NotificationsApiController extends Controller
{
public function index(Request $request) {
public function __construct()
{
if (!$user = \JWTAuth::parseToken()->authenticate()) {
return response()->json('Unauthorized', 401);
}
}
public function index(Request $request) {
$validator = Validator::make($request->all(), [
'records_per_page' => 'numeric',
@ -39,10 +44,6 @@ class NotificationsApiController extends Controller
*/
public function markAsRead($id)
{
if (!$user = \JWTAuth::parseToken()->authenticate()) {
return response()->json('Unauthorized', 401);
}
$notification = $user->notifications()
->applyUnread()
->find($id);
@ -65,4 +66,4 @@ class NotificationsApiController extends Controller
],
], 300);
}
}
}

View File

@ -0,0 +1,101 @@
<?php
namespace AhmadFatoni\ApiGenerator\Controllers\API;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use LaravelSmpp\SmppServiceInterface;
use TPS\Birzha\Classes\SMPP as SMPPV2;
use TPS\Birzha\Classes\SMS;
class SmsController extends KabinetAPIController
{
public function index(SmppServiceInterface $smpp) {
// $smpp->sendOne(99363432211, 'Hi, this SMS was send via SMPP protocol');
$tx=new SMPPV2('217.174.228.218', 5019); // make sure the port is integer
// $tx->debug=true;
$tx->bindTransmitter("birja","Birj@1");
// dump('bind transmitter');
$result = $tx->sendSMS("0773",'99363432211','message');
// dump('send sms attempt');
// echo $tx->getStatusMessage($result);
$tx->close();
unset($tx);
return $result;
}
public function sendSmsCode()
{
if($this->user->dial_code != '+993') {
return response()->json([
'dial_code' => $this->user->dial_code,
'message' => 'This user is not a resident of Turkmenistan.'
], 400);
}
if($this->user->phone_verified && $this->user->dial_code == '+993') {
return response()->json('User phone already verified', 200);
}
$code = random_int(1000, 9999);
$result = SMS::send(str_replace(array('+', ' ', '(' , ')', '-'), '', $this->user->username), $code);
// $result = 0;
switch ($result) {
case 0:
$this->user->phone_activation_code = $code;
$this->user->save();
return response()->json([
'result' => $result,
'message' => 'Message has been succesfully sent'
], 201);
break;
case 1:
return response()->json([
'result' => $result,
'message' => 'Error'
], 500);
break;
default:
return response()->json([
'result' => $result,
'message' => 'Error'
], 500);
break;
}
}
public function checkSmsCode(Request $request)
{
if($this->user->dial_code != '+993') {
return response()->json([
'dial_code' => $this->user->dial_code,
'message' => 'This user is not a resident of Turkmenistan.'
], 400);
}
if($this->user->phone_verified && $this->user->dial_code == '+993') {
return response()->json('User phone already verified', 200);
}
$validator = Validator::make($request->all(), [
'sms_code' => 'required|digits:4',
]);
if($validator->fails()) {
return response()->json($validator->errors(), 400);
}
if($this->user->phone_activation_code == $request->get('sms_code')) {
$this->user->phone_verified = true;
$this->user->phone_activation_code = null;
$this->user->save();
return response()->json('User phone has been succesfully verified', 201);
} else {
return response()->json('Wrong sms code', 400);
}
}
}

View File

@ -10,6 +10,7 @@ Route::group(['prefix' =>'api/v1','namespace' =>'AhmadFatoni\ApiGenerator\Contro
Route::get('products', ['as' => 'products.index', 'uses' => 'ProductsApiController@index']);
Route::get('products/{id}', ['as' => 'products.show', 'uses' => 'ProductsApiController@show']);
Route::get('test',['as' => 'test', 'uses' => 'SmsController@index']);
// Route::get('products/{id}/delete', ['as' => 'products.delete', 'uses' => 'ProductsApiController@destroy']);
@ -21,6 +22,8 @@ Route::group(['prefix' =>'api/v1','namespace' =>'AhmadFatoni\ApiGenerator\Contro
Route::resource('terms', 'TermsapiController', ['except' => ['destroy', 'create', 'edit']]);
// Route::get('terms/{id}/delete', ['as' => 'terms.delete', 'uses' => 'TermsapiController@destroy']);
Route::post('send-contact-form', 'ContactFormApiController@sendContactForm');
Route::middleware(['\Tymon\JWTAuth\Middleware\GetUserFromToken'])->group(function () {
Route::post('products', 'ProductsApiController@store');
@ -55,6 +58,13 @@ Route::group(['prefix' =>'api/v1','namespace' =>'AhmadFatoni\ApiGenerator\Contro
Route::get('transactions', 'TransactionsApiController@index');
Route::get('my-balance', 'TransactionsApiController@myBalance');
Route::post('withdraw-from-balance', 'ExchangeRequestsController@withdrawFromBalance');
Route::post('send-sms-code', 'SmsController@sendSmsCode');
Route::post('check-sms-code', 'SmsController@checkSmsCode');
Route::post('send-email-verification-link', 'EmailVerificationController@sendEmailVerificationLink');
});
});

View File

@ -21,7 +21,7 @@ Route::group(['prefix' =>'api/v1','namespace' =>'AhmadFatoni\ApiGenerator\Contro
Route::resource('terms', 'TermsapiController', ['except' => ['destroy', 'create', 'edit']]);
// Route::get('terms/{id}/delete', ['as' => 'terms.delete', 'uses' => 'TermsapiController@destroy']);
Route::post('send-contact-form', 'ContactFormApiController@sendContactForm');
Route::middleware(['\Tymon\JWTAuth\Middleware\GetUserFromToken'])->group(function () {
Route::post('products', 'ProductsApiController@store');
@ -56,6 +56,13 @@ Route::group(['prefix' =>'api/v1','namespace' =>'AhmadFatoni\ApiGenerator\Contro
Route::get('transactions', 'TransactionsApiController@index');
Route::get('my-balance', 'TransactionsApiController@myBalance');
Route::post('withdraw-from-balance', 'ExchangeRequestsController@withdrawFromBalance');
Route::post('send-sms-code', 'SmsController@sendSmsCode');
Route::post('check-sms-code', 'SmsController@checkSmsCode');
Route::post('send-email-verification-link', 'EmailVerificationController@sendEmailVerificationLink');
});
});
{{route}}

1
plugins/offline/cors/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/vendor/

View File

@ -0,0 +1,7 @@
# How to contribute
Contributions to this project are highly welcome.
1. Submit your pull requests to the `develop` branch
1. Adhere to the [PSR-2 coding](http://www.php-fig.org/psr/psr-2/) standard
1. If you are not sure if your ideas are fit for this project, create an issue and ask

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 OFFLINE GmbH
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,49 @@
<?php namespace OFFLINE\CORS;
use OFFLINE\CORS\Classes\HandleCors;
use OFFLINE\CORS\Classes\HandlePreflight;
use OFFLINE\CORS\Classes\ServiceProvider;
use OFFLINE\CORS\Models\Settings;
use System\Classes\PluginBase;
class Plugin extends PluginBase
{
public function boot()
{
\App::register(ServiceProvider::class);
$this->app['Illuminate\Contracts\Http\Kernel']
->prependMiddleware(HandleCors::class);
if (request()->isMethod('OPTIONS')) {
$this->app['Illuminate\Contracts\Http\Kernel']
->prependMiddleware(HandlePreflight::class);
}
}
public function registerPermissions()
{
return [
'offline.cors.manage' => [
'label' => 'Can manage cors settings',
'tab' => 'CORS',
],
];
}
public function registerSettings()
{
return [
'cors' => [
'label' => 'CORS-Settings',
'description' => 'Manage CORS headers',
'category' => 'system::lang.system.categories.cms',
'icon' => 'icon-code',
'class' => Settings::class,
'order' => 500,
'keywords' => 'cors',
'permissions' => ['offline.cors.manage'],
],
];
}
}

View File

@ -0,0 +1,45 @@
# CORS plugin for October CMS
This plugin is based on [https://github.com/barryvdh/laravel-cors](https://github.com/barryvdh/laravel-cors/blob/master/config/cors.php).
All configuration for the plugin can be done via the backend settings.
The following cors headers are supported:
* Access-Control-Allow-Origin
* Access-Control-Allow-Headers
* Access-Control-Allow-Methods
* Access-Control-Allow-Credentials
* Access-Control-Expose-Headers
* Access-Control-Max-Age
Currently these headers are sent for every request. There is no per-route configuration possible at this time.
## Setup
After installing the plugin visit the CORS settings page in your October CMS backend settings.
You can add `*` as an entry to `Allowed origins`, `Allowed headers` and `Allowed methods` to allow any kind of CORS request from everywhere.
It is advised to be more explicit about these settings. You can add values for each header via the repeater fields.
> It is important to set these intial settings once for the plugin to work as excpected!
### Filesystem configuration
As an alternative to the backend settings you can create a `config/config.php` file in the plugins root directory to configure it.
The filesystem configuration will overwrite any defined backend setting.
```php
<?php
// plugins/offline/cors/config/config.php
return [
'supportsCredentials' => true,
'maxAge' => 3600,
'allowedOrigins' => ['*'],
'allowedHeaders' => ['*'],
'allowedMethods' => ['GET', 'POST'],
'exposedHeaders' => [''],
];
```

View File

@ -0,0 +1,71 @@
<?php
namespace OFFLINE\CORS\Classes;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Based on asm89/stack-cors
*/
class Cors implements HttpKernelInterface
{
/**
* @var \Symfony\Component\HttpKernel\HttpKernelInterface
*/
private $app;
/**
* @var CorsService
*/
private $cors;
private $defaultOptions = [
'allowedHeaders' => [],
'allowedMethods' => [],
'allowedOrigins' => [],
'exposedHeaders' => false,
'maxAge' => false,
'supportsCredentials' => false,
];
/**
* Cors constructor.
*
* @param HttpKernelInterface $app
* @param array $options
*/
public function __construct(HttpKernelInterface $app, array $options = [])
{
$this->app = $app;
$this->cors = new CorsService(array_merge($this->defaultOptions, $options));
}
/**
* @param Request $request
* @param int $type
* @param bool $catch
*
* @return bool|Response
*/
public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
{
if ( ! $this->cors->isCorsRequest($request)) {
return $this->app->handle($request, $type, $catch);
}
if ($this->cors->isPreflightRequest($request)) {
return $this->cors->handlePreflightRequest($request);
}
if ( ! $this->cors->isActualRequestAllowed($request)) {
return new Response('Not allowed.', 403);
}
$response = $this->app->handle($request, $type, $catch);
return $this->cors->addActualRequestHeaders($response, $request);
}
}

View File

@ -0,0 +1,199 @@
<?php
namespace OFFLINE\CORS\Classes;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Based on asm89/stack-cors
*/
class CorsService
{
private $options;
public function __construct(array $options = [])
{
$this->options = $this->normalizeOptions($options);
}
private function normalizeOptions(array $options = [])
{
$options += [
'supportsCredentials' => false,
'maxAge' => 0,
];
// Make sure these values are arrays, if not specified in the backend settings.
$arrayKeys = [
'allowedOrigins',
'allowedHeaders',
'exposedHeaders',
'allowedMethods',
];
foreach ($arrayKeys as $key) {
if (!$options[$key]) {
$options[$key] = [];
}
}
// normalize array('*') to true
if (in_array('*', $options['allowedOrigins'])) {
$options['allowedOrigins'] = true;
}
if (in_array('*', $options['allowedHeaders'])) {
$options['allowedHeaders'] = true;
} else {
$options['allowedHeaders'] = array_map('strtolower', $options['allowedHeaders']);
}
if (in_array('*', $options['allowedMethods'])) {
$options['allowedMethods'] = true;
} else {
$options['allowedMethods'] = array_map('strtoupper', $options['allowedMethods']);
}
return $options;
}
public function isActualRequestAllowed(Request $request)
{
return $this->checkOrigin($request);
}
public function isCorsRequest(Request $request)
{
return $request->headers->has('Origin') && $request->headers->get('Origin') !== $request->getSchemeAndHttpHost();
}
public function isPreflightRequest(Request $request)
{
return $this->isCorsRequest($request)
&& $request->getMethod() === 'OPTIONS'
&& $request->headers->has('Access-Control-Request-Method');
}
public function addActualRequestHeaders(Response $response, Request $request)
{
if ( ! $this->checkOrigin($request)) {
return $response;
}
$response->headers->set('Access-Control-Allow-Origin', $request->headers->get('Origin'));
if ( ! $response->headers->has('Vary')) {
$response->headers->set('Vary', 'Origin');
} else {
$response->headers->set('Vary', $response->headers->get('Vary') . ', Origin');
}
if ($this->options['supportsCredentials']) {
$response->headers->set('Access-Control-Allow-Credentials', 'true');
}
if ($this->options['exposedHeaders']) {
$response->headers->set('Access-Control-Expose-Headers', implode(', ', $this->options['exposedHeaders']));
}
return $response;
}
public function handlePreflightRequest(Request $request)
{
if (true !== $check = $this->checkPreflightRequestConditions($request)) {
return $check;
}
return $this->buildPreflightCheckResponse($request);
}
private function buildPreflightCheckResponse(Request $request)
{
$response = new Response();
if ($this->options['supportsCredentials']) {
$response->headers->set('Access-Control-Allow-Credentials', 'true');
}
$response->headers->set('Access-Control-Allow-Origin', $request->headers->get('Origin'));
if ($this->options['maxAge']) {
$response->headers->set('Access-Control-Max-Age', $this->options['maxAge']);
}
$allowMethods = $this->options['allowedMethods'] === true
? strtoupper($request->headers->get('Access-Control-Request-Method'))
: implode(', ', $this->options['allowedMethods']);
$response->headers->set('Access-Control-Allow-Methods', $allowMethods);
$allowHeaders = $this->options['allowedHeaders'] === true
? strtoupper($request->headers->get('Access-Control-Request-Headers'))
: implode(', ', $this->options['allowedHeaders']);
$response->headers->set('Access-Control-Allow-Headers', $allowHeaders);
return $response;
}
private function checkPreflightRequestConditions(Request $request)
{
if ( ! $this->checkOrigin($request)) {
return $this->createBadRequestResponse(403, 'Origin not allowed');
}
if ( ! $this->checkMethod($request)) {
return $this->createBadRequestResponse(405, 'Method not allowed');
}
$requestHeaders = [];
// if allowedHeaders has been set to true ('*' allow all flag) just skip this check
if ($this->options['allowedHeaders'] !== true && $request->headers->has('Access-Control-Request-Headers')) {
$headers = strtolower($request->headers->get('Access-Control-Request-Headers'));
$requestHeaders = explode(',', $headers);
foreach ($requestHeaders as $header) {
if ( ! in_array(trim($header), $this->options['allowedHeaders'])) {
return $this->createBadRequestResponse(403, 'Header not allowed');
}
}
}
return true;
}
private function createBadRequestResponse($code, $reason = '')
{
return new Response($reason, $code);
}
private function checkOrigin(Request $request)
{
if ($this->options['allowedOrigins'] === true) {
// allow all '*' flag
return true;
}
$origin = $request->headers->get('Origin');
foreach ($this->options['allowedOrigins'] as $allowedOrigin) {
if (OriginMatcher::matches($allowedOrigin, $origin)) {
return true;
}
}
return false;
}
private function checkMethod(Request $request)
{
if ($this->options['allowedMethods'] === true) {
// allow all '*' flag
return true;
}
$requestMethod = strtoupper($request->headers->get('Access-Control-Request-Method'));
return in_array($requestMethod, $this->options['allowedMethods']);
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace OFFLINE\CORS\Classes;
use Closure;
class HandleCors
{
/**
* The CORS service
*
* @var CorsService
*/
protected $cors;
/**
* @param CorsService $cors
*/
public function __construct(CorsService $cors)
{
$this->cors = $cors;
}
/**
* Handle an incoming request. Based on Asm89\Stack\Cors by asm89
* @see https://github.com/asm89/stack-cors/blob/master/src/Asm89/Stack/Cors.php
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
*
* @return mixed
*/
public function handle($request, Closure $next)
{
if ( ! $this->cors->isCorsRequest($request)) {
return $next($request);
}
if ( ! $this->cors->isActualRequestAllowed($request)) {
abort(403);
}
/** @var \Illuminate\Http\Response $response */
$response = $next($request);
return $this->cors->addActualRequestHeaders($response, $request);
}
}

View File

@ -0,0 +1,74 @@
<?php
namespace OFFLINE\CORS\Classes;
use Closure;
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Routing\Router;
class HandlePreflight
{
/**
* @param CorsService $cors
*/
public function __construct(CorsService $cors, Router $router, Kernel $kernel)
{
$this->cors = $cors;
$this->router = $router;
$this->kernel = $kernel;
}
/**
* Handle an incoming OPTIONS request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
*
* @return mixed
*/
public function handle($request, Closure $next)
{
$response = $next($request);
if ($this->cors->isPreflightRequest($request) && $this->hasMatchingCorsRoute($request)) {
$preflight = $this->cors->handlePreflightRequest($request);
$response->headers->add($preflight->headers->all());
}
$response->setStatusCode(204);
return $response;
}
/**
* Verify the current OPTIONS request matches a CORS-enabled route
*
* @param \Illuminate\Http\Request $request
*
* @return boolean
*/
private function hasMatchingCorsRoute($request)
{
// Check if CORS is added in a global middleware
if ($this->kernel->hasMiddleware(HandleCors::class)) {
return true;
}
// Check if CORS is added as a route middleware
$request = clone $request;
$request->setMethod($request->header('Access-Control-Request-Method'));
try {
$route = $this->router->getRoutes()->match($request);
// change of method name in laravel 5.3
if (method_exists($this->router, 'gatherRouteMiddleware')) {
$middleware = $this->router->gatherRouteMiddleware($route);
} else {
$middleware = $this->router->gatherRouteMiddlewares($route);
}
return in_array(HandleCors::class, $middleware);
} catch (\Exception $e) {
app('log')->error($e);
return false;
}
}
}

View File

@ -0,0 +1,100 @@
<?php
namespace OFFLINE\CORS\Classes;
class OriginMatcher
{
public static function matches($pattern, $origin)
{
if ($pattern === $origin) {
return true;
}
$scheme = parse_url($origin, PHP_URL_SCHEME);
$host = parse_url($origin, PHP_URL_HOST);
$port = parse_url($origin, PHP_URL_PORT);
$schemePattern = static::parseOriginPattern($pattern, PHP_URL_SCHEME);
$hostPattern = static::parseOriginPattern($pattern, PHP_URL_HOST);
$portPattern = static::parseOriginPattern($pattern, PHP_URL_PORT);
$schemeMatches = static::schemeMatches($schemePattern, $scheme);
$hostMatches = static::hostMatches($hostPattern, $host);
$portMatches = static::portMatches($portPattern, $port);
return $schemeMatches && $hostMatches && $portMatches;
}
public static function schemeMatches($pattern, $scheme)
{
return is_null($pattern) || $pattern === $scheme;
}
public static function hostMatches($pattern, $host)
{
$patternComponents = array_reverse(explode('.', $pattern));
$hostComponents = array_reverse(explode('.', $host));
foreach ($patternComponents as $index => $patternComponent) {
if ($patternComponent === '*') {
return true;
}
if ( ! isset($hostComponents[$index])) {
return false;
}
if ($hostComponents[$index] !== $patternComponent) {
return false;
}
}
return count($patternComponents) === count($hostComponents);
}
public static function portMatches($pattern, $port)
{
if ($pattern === "*") {
return true;
}
if ((string)$pattern === "") {
return (string)$port === "";
}
if (preg_match('/\A\d+\z/', $pattern)) {
return (string)$pattern === (string)$port;
}
if (preg_match('/\A(?P<from>\d+)-(?P<to>\d+)\z/', $pattern, $captured)) {
return $captured['from'] <= $port && $port <= $captured['to'];
}
throw new \InvalidArgumentException("Invalid port pattern: ${pattern}");
}
public static function parseOriginPattern($originPattern, $component = -1)
{
$matched = preg_match(
'!\A
(?: (?P<scheme> ([a-z][a-z0-9+\-.]*) ):// )?
(?P<host> (?:\*|[\w-]+)(?:\.[\w-]+)* )
(?: :(?P<port> (?: \*|\d+(?:-\d+)? ) ) )?
\z!x',
$originPattern,
$captured
);
if ( ! $matched) {
throw new \InvalidArgumentException("Invalid origin pattern ${originPattern}");
}
$components = [
'scheme' => $captured['scheme'] ?: null,
'host' => $captured['host'],
'port' => array_key_exists('port', $captured) ? $captured['port'] : null,
];
switch ($component) {
case -1:
return $components;
case PHP_URL_SCHEME:
return $components['scheme'];
case PHP_URL_HOST:
return $components['host'];
case PHP_URL_PORT:
return $components['port'];
}
throw new \InvalidArgumentException("Invalid component: ${component}");
}
}

View File

@ -0,0 +1,90 @@
<?php
namespace OFFLINE\CORS\Classes;
use October\Rain\Support\ServiceProvider as BaseServiceProvider;
use OFFLINE\CORS\Models\Settings;
class ServiceProvider extends BaseServiceProvider
{
/**
* Indicates if loading of the provider is deferred.
*
* @var bool
*/
protected $defer = false;
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->singleton(CorsService::class, function ($app) {
return new CorsService($this->getSettings());
});
}
/**
* Return default Settings
*/
protected function getSettings()
{
$supportsCredentials = (bool)$this->getConfigValue('supportsCredentials', false);
$maxAge = (int)$this->getConfigValue('maxAge', 0);
$allowedOrigins = $this->getConfigValue('allowedOrigins', []);
$allowedHeaders = $this->getConfigValue('allowedHeaders', []);
$allowedMethods = $this->getConfigValue('allowedMethods', []);
$exposedHeaders = $this->getConfigValue('exposedHeaders', []);
return compact(
'supportsCredentials',
'allowedOrigins',
'allowedHeaders',
'allowedMethods',
'exposedHeaders',
'maxAge'
);
}
/**
* Returns an effective config value.
*
* If a filesystem config is available it takes precedence
* over the backend settings values.
*
* @param $key
* @param null $default
*
* @return mixed
*/
public function getConfigValue($key, $default = null)
{
return $this->filesystemConfig($key) ?: $this->getValues(Settings::get($key, $default));
}
/**
* Return the filesystem config value if available.
*
* @param string $key
*
* @return mixed
*/
public function filesystemConfig($key)
{
return config('offline.cors::' . $key);
}
/**
* Extract the repeater field values.
*
* @param mixed $values
*
* @return array
*/
protected function getValues($values)
{
return \is_array($values) ? collect($values)->pluck('value')->toArray() : $values;
}
}

View File

@ -0,0 +1,13 @@
{
"name": "offline/oc-cors-plugin",
"description": "Setup and manage Cross-Origin Resource Sharing headers in October CMS",
"type": "october-plugin",
"license": "MIT",
"authors": [
{
"name": "Tobias Kündig",
"email": "tobias@offline.swiss"
}
],
"require": {}
}

View File

@ -0,0 +1,6 @@
<?php return [
'plugin' => [
'name' => 'CORS',
'description' => 'Verwalte Cross-Origin Resource Sharing Header in October CMS',
],
];

View File

@ -0,0 +1,6 @@
<?php return [
'plugin' => [
'name' => 'CORS',
'description' => 'Setup and manage Cross-Origin Resource Sharing headers',
],
];

View File

@ -0,0 +1,12 @@
<?php
namespace OFFLINE\CORS\Models;
use Model;
class Settings extends Model
{
public $implement = ['System.Behaviors.SettingsModel'];
public $settingsCode = 'offline_cors_settings';
public $settingsFields = 'fields.yaml';
}

View File

@ -0,0 +1,55 @@
fields:
supportsCredentials:
label: Supports credentials
type: switch
comment: 'Set Access-Control-Allow-Credentials header to true'
default: false
span: left
maxAge:
label: Max age
type: number
comment: 'Set Access-Control-Max-Age to this value'
default: 0
span: right
tabs:
fields:
allowedOrigins:
label: Allowed origins
tab: Allowed origins
type: repeater
span: left
form:
fields:
value:
type: text
label: Origin
allowedHeaders:
label: Allowed headers
tab: Allowed headers
type: repeater
span: left
form:
fields:
value:
type: text
label: Header
allowedMethods:
label: Allowed methods
tab: Allowed methods
type: repeater
span: left
form:
fields:
value:
type: text
label: Method
exposedHeaders:
label: Exposed headers
tab: Exposed headers
type: repeater
span: left
form:
fields:
value:
type: text
label: Header

View File

@ -0,0 +1,6 @@
plugin:
name: 'offline.cors::lang.plugin.name'
description: 'offline.cors::lang.plugin.description'
author: 'OFFLINE GmbH'
icon: oc-icon-code
homepage: ''

View File

@ -0,0 +1,14 @@
1.0.1:
- Initial release.
1.0.2:
- Fixed backend settings label (thanks to LukeTowers)
1.0.3:
- Added support for filesystem configuration file / Added plugin to Packagist (https://packagist.org/packages/offline/oc-cors-plugin)
1.0.4:
- Fixed minor bug when running the plugin without custom settings
1.0.5:
- "Return proper 204 response code for preflight requests (thanks to @adrian-marinescu-ch on GitHub)"
1.0.6:
- "Dummy release to sync with Packagist version"
1.0.7:
- "Optimized compatibility with October v2"

View File

@ -1,5 +1,6 @@
<?php namespace RainLab\Notify\NotifyRules;
use Illuminate\Support\Facades\Log;
use Mail;
use Lang;
use Config;
@ -48,6 +49,7 @@ class SendMailTemplateAction extends ActionBase
throw new ApplicationException('Missing valid recipient or mail template');
}
Log::info($template);
Mail::sendTo($recipient, $template, $params, function($message) use ($replyTo) {
if ($replyTo) {
$message->replyTo($replyTo);

View File

@ -17,6 +17,8 @@ use Cms\Classes\ComponentBase;
use RainLab\User\Models\User as UserModel;
use RainLab\User\Models\Settings as UserSettings;
use Exception;
use Throwable;
use TPS\Birzha\Classes\SMS;
/**
* Account component
@ -109,9 +111,9 @@ class Account extends ComponentBase
/*
* Activation code supplied
*/
if ($code = $this->activationCode()) {
$this->onActivate($code);
}
// if ($code = $this->activationCode()) {
// $this->onActivate($code);
// }
$this->prepareVars();
}
@ -228,7 +230,7 @@ class Account extends ComponentBase
* Authenticate user
*/
$credentials = [
'login' => array_get($data, 'login'),
'login' => array_get($data, 'dial_code') . array_get($data, 'login'),
'password' => array_get($data, 'password')
];
@ -303,7 +305,15 @@ class Account extends ComponentBase
$data['password_confirmation'] = post('password');
}
$rules = (new UserModel)->rules;
$rules = array_merge((new UserModel)->rules, [
'username' => [
'required',
'unique' => function($attribute, $value, $fail) use($data) {
$u = UserModel::find($data['dial_code'] . $value);
if(!is_null($u)) $fail();
}
],
]);
if ($this->loginAttribute() !== UserSettings::LOGIN_USERNAME) {
unset($rules['username']);
@ -337,7 +347,9 @@ class Account extends ComponentBase
$automaticActivation = UserSettings::get('activate_mode') == UserSettings::ACTIVATE_AUTO;
$userActivation = UserSettings::get('activate_mode') == UserSettings::ACTIVATE_USER;
$adminActivation = UserSettings::get('activate_mode') == UserSettings::ACTIVATE_ADMIN;
$user = Auth::register($data, $automaticActivation);
$user = Auth::register(array_merge($data, [
'username' => $data['dial_code'] . $data['username']
]), $automaticActivation);
Event::fire('rainlab.user.register', [$user, $data]);
@ -345,9 +357,10 @@ class Account extends ComponentBase
* Activation is by the user, send the email
*/
if ($userActivation) {
$this->sendActivationEmail($user);
Flash::success(Lang::get(/*An activation email has been sent to your email address.*/'rainlab.user::lang.account.activation_email_sent'));
// $this->sendActivationEmail($user);
//
// Flash::success(Lang::get(/*An activation email has been sent to your email address.*/'rainlab.user::lang.account.activation_email_sent'));
//todo open activation view
}
/*
@ -380,6 +393,101 @@ class Account extends ComponentBase
}
}
/**
* Send sms with 6 digits code to user
*/
public function onSendSmsCode()
{
try {
if($this->user()->dial_code == '+993' && !$this->user()->phone_verified) {
$code = random_int(1000, 9999);
$result = SMS::send(str_replace(array('+', ' ', '(' , ')', '-'), '', $this->user()->username), $code);
// $result = 0;
switch ($result) {
case 0:
$this->user()->phone_activation_code = $code;
$this->user()->save();
break;
case 1:
return \Redirect::to('/error');
break;
default:
return \Redirect::to('/error');
break;
}
}
} catch(Throwable $th) {
\Log::info($th);
return \Redirect::to('/error');
}
}
/**
* Check SMS code sent by user
*/
public function onCheckSmsCode()
{
$data = post();
$validator = \Validator::make($data, [
'sms_code' => 'required|digits:4',
]);
if($validator->fails()) {
throw new ValidationException($validator);
}
if($this->user()->phone_activation_code == $data['sms_code']) {
$this->user()->phone_verified = true;
$this->user()->phone_activation_code = null;
$this->user()->save();
Flash::success('Your phone number has been succesfully verified');
return \Redirect::to('profile');
} else {
Flash::error('Invalid sms code');
return \Redirect::to('profile');
}
}
/**
* Send email with the verification link to the user
*/
public function onSendEmailVerificationLink()
{
if(!$this->user()->email_verified) {
$code = sha1(time());
$vars = [
'verification_link' => $this->controller->pageUrl('kabinet/verify_email.htm', ['code' => $code])
];
try {
\Mail::send('tps.birzha::mail.' . app()->getLocale() . '.email_verify', $vars, function($message) {
$message->to($this->user()->email, 'Birzha User');
$message->subject('Подтверждение Email');
});
} catch(Throwable $th) {
\Log::info($th);
Flash::error('tps.birzha::mail.' . app()->getLocale() . '.email_verify');
return \Redirect::to('profile');
}
$this->user()->email_activation_code = $code;
$this->user()->save();
Flash::success('Please check your email');
return \Redirect::to('profile');
}
}
/**
* Activate the user
* @param string $code Activation code

View File

@ -29,6 +29,7 @@ class User extends UserBase
'username' => 'required|unique:users',
'password' => 'required:create|between:8,255|confirmed',
'password_confirmation' => 'required_with:password|between:8,255',
'dial_code' => 'required', // +993, +375...
];
public $messages = [
@ -55,7 +56,7 @@ class User extends UserBase
public $hasMany = [
'products' => ['TPS\Birzha\Models\Product', 'key' => 'vendor_id'],
'transactions' => ['TPS\Birzha\Models\Transaction'],
'exchangerequests' => ['TPS\Birzha\Models\Exchangerequest'],
];
@ -71,7 +72,8 @@ class User extends UserBase
'password',
'password_confirmation',
'created_ip_address',
'last_ip_address'
'last_ip_address',
'dial_code'
];
/**
@ -174,6 +176,16 @@ class User extends UserBase
public function getUserBalanceAttribute(){
return $this->getBalance();
}
/**
* Mutators
*/
// public function setUsernameAttribute($value)
// {
// dd($this->attributes);
// return $this->attributes['username'] = $this->attributes['dial_code'] . $value;
// }
/**
* Gets a code for when the user is persisted to a cookie or session which identifies the user.
* @return string
@ -546,4 +558,17 @@ class User extends UserBase
{
$this->password = $this->password_confirmation = Str::random(static::getMinPasswordLength());
}
/**
* Get an activation code for the given user.
* @return string
*/
public function getActivationCode()
{
$this->activation_code = $activationCode = random_int(1000, 9999);;
$this->forceSave();
return $activationCode;
}
}

View File

@ -15,4 +15,14 @@ class UserRegisteredEvent extends UserEventBase
'group' => 'user'
];
}
public static function makeParamsFromEvent(array $args, $eventName = null)
{
$user = array_get($args, 0);
$params = $user->getNotificationVars();
$params['activation_code'] = $user->getActivationCode();
$params['user'] = $user;
return $params;
}
}

View File

@ -15,3 +15,6 @@ attributes:
email:
label: Email address
phone_verified:
label: Phone verified

View File

@ -0,0 +1,6 @@
layout = "birzha_email"
==
<h1>Confirm your account</h1>
<a href="{{verification_link}}">Click here</a> <span>to verify your email</span>

View File

@ -6,6 +6,7 @@ use RainLab\Notify\NotifyRules\SaveDatabaseAction;
use System\Classes\PluginBase;
use TPS\Birzha\Actions\MailToAdminsAction;
use TPS\Birzha\Actions\SendSMSAction;
use TPS\Birzha\Actions\VerifyAction;
use TPS\Birzha\Models\Category;
use TPS\Birzha\Models\Message;
use TPS\Birzha\Models\Product;
@ -213,7 +214,34 @@ class Plugin extends PluginBase
// ]);
// });
}
public function registerMailLayouts()
{
return [
'birzha_default' => 'tps.birzha::mail.layout-default',
'birzha_system' => 'tps.birzha::mail.layout-system',
];
}
public function registerMailTemplates()
{
return [
'tps.birzha::mail.ru.activate',
'tps.birzha::mail.en.activate',
'tps.birzha::mail.tm.activate',
'tps.birzha::mail.message',
'tps.birzha::mail.request',
// email verification
'tps.birzha::mail.ru.email_verify',
'tps.birzha::mail.en.email_verify',
'tps.birzha::mail.tm.email_verify',
// product reviewed
// 'tps.birzha::mail.ru.product_reviewed',
// 'tps.birzha::mail.en.product_reviewed',
// 'tps.birzha::mail.tm.product_reviewed',
];
}
public function registerNotificationRules()
{
@ -228,8 +256,10 @@ class Plugin extends PluginBase
'actions' => [
SendSMSAction::class,
MailToAdminsAction::class,
VerifyAction::class
],
'conditions' => [
\RainLab\User\NotifyRules\UserAttributeCondition::class
],
'groups' => [
'payment' => [
@ -285,6 +315,7 @@ class Plugin extends PluginBase
'TPS\Birzha\Components\MyOffers' => 'myOffers',
'TPS\Birzha\Components\Balance' => 'balance',
'TPS\Birzha\Components\ContactForm' => 'contactForm',
'TPS\Birzha\Components\EmailVerify' => 'emailverify',
];
}

View File

@ -2,10 +2,19 @@
namespace TPS\Birzha\Actions;
use Backend\Models\UserGroup as AdminGroupModel;
use Illuminate\Support\Facades\Log;
use RainLab\Notify\Classes\ActionBase;
use TPS\Birzha\Classes\SMS;
class SendSMSAction extends ActionBase
{
public $recipientModes = [
'user' => 'User phone number (if applicable)',
// 'admin' => 'Back-end administrators phones',
'custom' => 'Specific phone number',
];
/**
* Returns information about this event, including name and description.
*/
@ -17,17 +26,48 @@ class SendSMSAction extends ActionBase
'icon' => 'icon-envelope'
];
}
/**
* Defines validation rules for the custom fields.
* @return array
*/
public function defineValidationRules()
{
return [
'send_to_mode' => 'required',
'send_to_custom' => 'required_if:send_to_mode,custom',
// 'send_to_admin' => 'required_if:send_to_mode,admin',
'message' => 'required|string|max:160'
];
}
/**
* Field configuration for the action.
*/
// public function defineFormFields()
public function defineFormFields()
{
return 'fields.yaml';
}
public function getSendToModeOptions()
{
$modes = $this->recipientModes;
return $modes;
}
// public function getSendToAdminOptions()
// {
// return 'fields.yaml';
// $options = ['' => '- All administrators -'];
//
// $groups = AdminGroupModel::lists('name', 'id');
//
// return $options + $groups;
// }
public function getTitle()
{
// if ($this->isAdminMode()) {
// return 'Send sms to administrators';
// }
return 'Send sms to customers';
}
@ -44,5 +84,21 @@ class SendSMSAction extends ActionBase
public function triggerAction($params)
{
if($this->host->send_to_mode == 'user' && $params['user']){
if(!$params['user']['phone_verified'])
return;
$to = $params['user']['username'];
}else{
$to = $this->host->send_to_custom;
}
SMS::send($to,$this->host->message);
// Log::info(json_encode($params));
}
// protected function isAdminMode()
// {
// return $this->host->send_to_mode == 'admin';
// }
}

View File

@ -0,0 +1,106 @@
<?php
namespace TPS\Birzha\Actions;
use Illuminate\Support\Facades\Log;
use Mail;
use October\Rain\Exception\ApplicationException;
use RainLab\Notify\Classes\ActionBase;
use System\Models\MailTemplate;
use TPS\Birzha\Classes\SMS;
class VerifyAction extends ActionBase
{
public $recipientModes = [
'email' => 'Send verification email',
'sms' => 'Send verification sms',
'email&sms' => 'Send verification email and sms',
'email_sms' => 'Send verification sms to local numbers (+993), emails to foreign numbers',
];
/**
* Returns information about this event, including name and description.
*/
public function actionDetails()
{
return [
'name' => 'Verify user',
'description' => 'Verify user email/phone',
'icon' => 'icon-envelope'
];
}
/**
* Defines validation rules for the custom fields.
* @return array
*/
public function defineValidationRules()
{
return [
'send_to_mode' => 'required',
'mail_template' => 'required_unless:send_to_mode,sms',
];
}
public function getMailTemplateOptions()
{
$codes = array_keys(MailTemplate::listAllTemplates());
$result = array_combine($codes, $codes);
return $result;
}
public function getTitle()
{
return 'Send vefification code tu user';
}
public function getActionIcon()
{
return 'icon-envelope-square';
}
public function triggerAction($params)
{
// Log::info(json_encode($params));
$code = $params['activation_code'];
$phone = $params['username'];
$dial_code = $params['user']['dial_code'];
switch ($this->host->send_to_mode) {
case 'sms':
$this->sendSMS($phone,$code);
break;
case 'email':
$this->sendEmail($params['email'],$code);
break;
case 'email&sms':
$this->sendSMS($phone,$code);
$this->sendEmail($params['email'],$code);
break;
case 'email_sms':
$dial_code === '+993' ? $this->sendSMS($phone,$code) : $this->sendEmail($params['email'],$code);
break;
}
}
private function sendMail($email,$code){
$template = $this->host->mail_template;
if (!$email || !$template) {
throw new ApplicationException('Missing valid recipient or mail template');
}
Mail::sendTo($email, 'tps.birzha::mail.activate', ['code'=>$code], function($message){
});
}
private function sendSMS($phone,$code){
$message = "tmex.gov.tm%20verification%20code:%20{$code}";
SMS::send($phone,$message);
}
public function getSendToModeOptions()
{
$modes = $this->recipientModes;
return $modes;
}
}

View File

@ -3,10 +3,10 @@
# ===================================
fields:
mail_template:
label: Mail template
type: dropdown
placeholder: Select template
# mail_template:
# label: Mail template
# type: dropdown
# placeholder: Select template
send_to_mode:
label: Send to
@ -19,24 +19,16 @@ fields:
action: show
field: send_to_mode
condition: value[custom]
#
# send_to_admin:
# label: Send to admin group
# span: right
# type: dropdown
# trigger:
# action: show
# field: send_to_mode
# condition: value[admin]
send_to_admin:
label: Send to admin group
span: right
type: dropdown
trigger:
action: show
field: send_to_mode
condition: value[admin]
reply_to_mode:
label: Reply-to address
type: radio
span: left
reply_to_custom:
message:
cssClass: radio-align
trigger:
action: show
field: reply_to_mode
condition: value[custom]
label: Message

View File

@ -0,0 +1,6 @@
fields:
send_to_mode:
label: Send to
type: radio
span: left

View File

@ -20,7 +20,7 @@ namespace TPS\Birzha\Classes;
*/
/**
*This class is also a merge from smpp lib of onlinecity
*This class is also a merge from smpp lib of onlinecity
* @see https://github.com/onlinecity/php-smpp
*/
@ -49,7 +49,7 @@ class SMPP{
const OUTBIND = 0x0000000B;
const ENQUIRE_LINK = 0x00000015;
const ENQUIRE_LINK_RESP = 0x80000015;
// Command status - SMPP v3.4 - 5.1.3 page 112-114
const ESME_ROK = 0x00000000; // No Error
const ESME_RINVMSGLEN = 0x00000001; // Message Length is invalid
@ -123,7 +123,7 @@ class SMPP{
var $sms_replace_if_present_flag=0;
var $sms_data_coding=0;
var $sms_sm_default_msg_id=0;
/**
* Constructs the smpp class
* @param $host - SMSC host name or host IP
@ -156,9 +156,9 @@ class SMPP{
$hours = (int)($duration/60/60);
$minutes = (int)($duration/60)-$hours*60;
$seconds = (int)$duration-$hours*60*60-$minutes*60;
if($this->debug){
echo 'Seconds from last enquire link = ' . $seconds . PHP_EOL;
echo "<br>" . 'Seconds from last enquire link = ' . $seconds . PHP_EOL;
}
if ($seconds >= $this->enquirelink_timeout){
@ -176,11 +176,11 @@ class SMPP{
if($this->state!="open")return false;
if($this->debug){
echo "Binding receiver...\n\n";
echo "<br>" . "Binding receiver...\n\n";
}
$status=$this->_bind($login, $pass, SMPP::BIND_RECEIVER);
if($this->debug){
echo "Binding status : $status\n\n";
echo "<br>" . "Binding status : $status\n\n";
}
if($status===0)$this->state="bind_rx";
return ($status===0);
@ -196,11 +196,11 @@ class SMPP{
if($this->state!="open")return false;
if($this->debug){
echo "Binding transmitter...\n\n";
echo "<br>" . "Binding transmitter...\n\n";
}
$status=$this->_bind($login, $pass, SMPP::BIND_TRANSMITTER);
if($this->debug){
echo "Binding status : $status\n\n";
echo "<br>" . "Binding status : $status\n\n";
}
if($status===0)$this->state="bind_tx";
@ -213,11 +213,11 @@ class SMPP{
function close(){
if($this->state=="closed")return;
if($this->debug){
echo "Unbinding...\n\n";
echo "<br>" . "Unbinding...\n\n";
}
$status=$this->sendCommand(SMPP::UNBIND,"");
if($this->debug){
echo "Unbind status : $status\n\n";
echo "<br>" . "Unbind status : $status\n\n";
}
fclose($this->socket);
$this->state="closed";
@ -225,7 +225,7 @@ class SMPP{
/**
* Read one SMS from SMSC. Can be executed only after bindReceiver() call.
* Receiver not send enquirelink
* Receiver not send enquirelink
* This method bloks. Method returns on socket timeout or enquire_link signal from SMSC.
* @return sms associative array or false when reading failed or no more sms.
*/
@ -246,7 +246,7 @@ class SMPP{
//read pdu
do{
if($this->debug){
echo "read sms...\n\n";
echo "<br>" . "read sms...\n\n";
}
$pdu=$this->readPDU();
//check for enquire link command
@ -270,16 +270,16 @@ class SMPP{
function sendSMS($from, $to, $message){
if (strlen($from)>20 || strlen($to)>20 || strlen($message)>160)return false;
if($this->state!="bind_tx")return false;
//TON
$this->sms_source_addr_ton = $this->setTon($from);
$this->sms_dest_addr_ton = $this->setTon($to);
$this->sms_source_addr_ton = 2;//$this->setTon($from);
$this->sms_dest_addr_ton = 2;$this->setTon($to);
//NPI
$this->sms_source_addr_npi = $this->setNPI($from);
$this->sms_dest_addr_npi = $this->setNPI($to);
$pdu = pack('a1cca'.(strlen($from)+1).'cca'.(strlen($to)+1).'ccca1a1ccccca'.(strlen($message)+1),
$pdu = pack('a1cca'.(strlen($from)+1).'cca'.(strlen($to)+1).'ccca1a1ccccca'.(strlen($message)),
$this->sms_service_type,
$this->sms_source_addr_ton,
$this->sms_source_addr_npi,
@ -310,7 +310,7 @@ class SMPP{
function getStatusMessage($statuscode)
{
if (is_bool($statuscode)) return 'Connection Error';
switch ($statuscode) {
case SMPP::ESME_ROK: return 'OK';
case SMPP::ESME_RINVMSGLEN: return 'Message Length is invalid';
@ -391,7 +391,7 @@ class SMPP{
//If empty and length is > 8 then TON is International (1)
if(empty($address) || strlen($address) > $NationalNumberLenght) return 1; //International
}
//If address is alphanumeric then TON is Alphanumeric (5)
if (!ctype_digit($address)) return 5; //Alphanumeric
@ -422,7 +422,7 @@ public function enquireLink()
if ($response == 0) {
$this->lastenquire = microtime(true);
if ($this->debug){
echo "ENQUIRE!!" . PHP_EOL;
echo "<br>" . "ENQUIRE!!" . PHP_EOL;
}
}
return $response;
@ -480,9 +480,9 @@ public function enquireLink()
'short_message'=>$this->getString($ar,255)
);
if($this->debug){
echo "Delivered sms:\n";
echo "<br>" . "Delivered sms:\n";
print_r($sms);
echo "\n";
echo "<br>" . "\n";
}
//send response of recieving sms
$this->sendPDU(SMPP::DELIVER_SM_RESP, "\0", $pdu['sn']);
@ -513,21 +513,21 @@ public function enquireLink()
$length=strlen($pdu) + 16;
$header=pack("NNNN", $length, $command_id, 0, $seq_number);
if($this->debug){
echo "Send PDU : $length bytes\n";
echo "<br>" . "Send PDU : $length bytes\n";
$this->printHex($header.$pdu);
echo "command_id : ".$command_id."\n";
echo "sequence number : $seq_number\n\n";
echo "<br>" . "command_id : ".$command_id."\n";
echo "<br>" . "sequence number : $seq_number\n\n";
}
$writed = @fwrite($this->socket, $header.$pdu, $length);
//Close conection if not bind
if ($writed == FALSE) {
if ($this->debug) echo "Lost connection to SMSC" . PHP_EOL;
if ($this->debug) echo "<br>" . "Lost connection to SMSC" . PHP_EOL;
exit();
};
}
/**
@ -584,12 +584,12 @@ public function enquireLink()
$body="";
}
if($this->debug){
echo "Read PDU : $length bytes\n";
echo "<br>" . "Read PDU : $length bytes\n";
$this->printHex($tmp.$tmp2.$body);
echo "body len : " . strlen($body) . "\n";
echo "Command id : $command_id\n";
echo "Command status : $command_status\n";
echo "sequence number : $sequence_number\n\n";
echo "<br>" . "body len : " . strlen($body) . "\n";
echo "<br>" . "Command id : $command_id\n";
echo "<br>" . "Command status : $command_status\n";
echo "<br>" . "sequence number : $sequence_number\n\n";
}
$pdu=array(
'id'=>$command_id,
@ -631,4 +631,4 @@ public function enquireLink()
}
print "\n";
}
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace TPS\Birzha\Classes;
use Exception;
use Illuminate\Support\Facades\Log;
use October\Rain\Support\Facades\Http;
use TPS\Birzha\Classes\SMPP;
class SMS
{
public static function send($to, $content){
try {
$tx = new SMPP(env('SMPP_IP','217.174.228.218'), env('SMPP_PORT',5019)); // make sure the port is integer
$tx->bindTransmitter(env('SMPP_USER',"birja"),env('SMPP_PASS',"Birj@1"));
$result = $tx->sendSMS(env('SMPP_SENDER',"0773"),$to,$content);
$tx->close();
unset($tx);
return $result;
}
catch (\Throwable $th){
Log::info($th);
return 1;
}
}
}

View File

@ -2,6 +2,7 @@
use Cms\Classes\ComponentBase;
use TPS\Birzha\Models\Contactmessage;
use TPS\Birzha\Models\Settings;
class ContactForm extends ComponentBase
{
@ -40,13 +41,17 @@ class ContactForm extends ComponentBase
'lastname' => \Input::get('surname'),
'mobile' => \Input::get('mobile'),
'email' => \Input::get('email'),
'content' => \Input::get('message')
'content' => \Input::get('content')
];
\Mail::send('tps.birzha::mail.message', $vars, function($message) {
$message->to(env('TPS_EMAIL_GETTER'), 'Birzha Admin');
$message->subject('Birzha web site contact form');
});
$admin_email = Settings::getValue('admin_email');
if($admin_email) {
\Mail::send('tps.birzha::mail.message', $vars, function($message) use($admin_email){
$message->to($admin_email, 'Birzha Admin');
$message->subject('Контактная форма');
});
}
return [
'#form-steps' => $this->renderPartial('@message_sent')

View File

@ -0,0 +1,47 @@
<?php namespace Tps\Birzha\Components;
use Cms\Classes\ComponentBase;
class EmailVerify extends ComponentBase
{
public function componentDetails() {
return [
'name' => 'Email Verification',
'description' => 'Email Verification'
];
}
public function defineProperties()
{
return [
'code' => [
'title' => 'Verificaiton code',
'description' => 'Verificaiton code',
'default' => '{{ :code }}',
'type' => 'string',
]
];
}
public function onRun() {
$user = \Auth::user();
if(!$user->email_verified) {
if($user->email_activation_code == $this->property('code')) {
$user->email_verified = true;
$user->email_activation_code = null;
$user->save();
$this->page['message'] = 'Your email address has been succesfully verified';
} else {
$this->page['message'] = 'Invalid verification link';
}
} else {
$this->page['message'] = 'You have already verified your email address';
}
}
}

View File

@ -0,0 +1,17 @@
<section class="thanks">
<div class="auto_container">
<div class="thanks_wrap">
<div class="thanks_text">
{{message}}
</div>
<div class="link">
<a href="{{'index'|page}}" class="home_link">
<span>OK</span>
</a>
</div>
</div>
</div>
</section>

View File

@ -0,0 +1,17 @@
<?php namespace TPS\Birzha\Controllers;
use Backend\Classes\Controller;
use BackendMenu;
class Exchangerequests extends Controller
{
public $implement = [ 'Backend\Behaviors\ListController' ];
public $listConfig = 'config_list.yaml';
public function __construct()
{
parent::__construct();
BackendMenu::setContext('TPS.Birzha', 'birzha-menu', 'exchange-requests');
}
}

View File

@ -0,0 +1,17 @@
<div data-control="toolbar">
<button
class="btn btn-default oc-icon-trash-o"
disabled="disabled"
onclick="$(this).data('request-data', {
checked: $('.control-list').listWidget('getChecked')
})"
data-request="onDelete"
data-request-confirm="<?= e(trans('backend::lang.list.delete_selected_confirm')) ?>"
data-trigger-action="enable"
data-trigger=".control-list input[type=checkbox]"
data-trigger-condition="checked"
data-request-success="$(this).prop('disabled', true)"
data-stripe-load-indicator>
<?= e(trans('backend::lang.list.delete_selected')) ?>
</button>
</div>

View File

@ -0,0 +1,11 @@
list: $/tps/birzha/models/exchangerequest/columns.yaml
modelClass: TPS\Birzha\Models\Exchangerequest
title: Exchangerequests
noRecordsMessage: 'backend::lang.list.no_records'
showSetup: true
showCheckboxes: true
recordsPerPage: 20
toolbar:
buttons: list_toolbar
search:
prompt: 'backend::lang.list.search_prompt'

View File

@ -0,0 +1 @@
<?= $this->listRender() ?>

View File

@ -6,17 +6,22 @@ scopes:
status:
label: Status
type: group
conditions: status in (:filtered)
options:
new: new
payed: payed
approved: approved
declined: declined
failed: failed
payment_type:
label: 'Payment type'
type: group
conditions: payment_type in (:filtered)
options:
bank: Bank
online: Online
gift: Gift
user:
label: Seller
modelClass: RainLab\User\Models\User
nameFrom: name
nameFrom: email
conditions: user_id in (:filtered)

View File

@ -1,6 +1,6 @@
<div data-control="toolbar">
<a href="<?= Backend::url('tps/birzha/sliders/create') ?>" class="btn btn-primary oc-icon-plus"><?= e(trans('backend::lang.form.create')) ?></a>
<a href="<?= Backend::url('tps/birzha/sliders/reorder') ?>" class="btn btn-default oc-icon-list"><?= e(trans('backend::lang.reorder.default_title')) ?></a>
<!-- <a href="<?= Backend::url('tps/birzha/sliders/reorder') ?>" class="btn btn-default oc-icon-list"><?= e(trans('backend::lang.reorder.default_title')) ?></a> -->
<button
class="btn btn-default oc-icon-trash-o"
disabled="disabled"

View File

@ -6,5 +6,5 @@ scopes:
user:
label: Seller
modelClass: RainLab\User\Models\User
nameFrom: name
nameFrom: email
conditions: user_id in (:filtered)

View File

@ -21,23 +21,23 @@ class MessageReceivedEvent extends \RainLab\Notify\Classes\EventBase
/**
* Defines the usable parameters provided by this class.
*/
public function defineParams()
{
return [
'name' => [
'title' => 'Name',
'label' => 'Name of the user',
],
// ...
];
}
// public function defineParams()
// {
// return [
// 'name' => [
// 'title' => 'Name',
// 'label' => 'Name of the user',
// ],
// // ...
// ];
// }
public static function makeParamsFromEvent(array $args, $eventName = null)
{
return [
'user' => array_get($args, 0),
'message' => array_get($args, 1)
'message' => array_get($args, 1),
];
}
}

View File

@ -22,7 +22,8 @@ class PaymentReviewedEvent extends EventBase
{
return [
'payment' => array_get($args, 0),
'user' => array_get($args, 1)
'user' => array_get($args, 1),
];
}
}

View File

@ -0,0 +1,57 @@
<?php namespace TPS\Birzha\Models;
use Model;
use RainLab\User\Models\User;
/**
* Model
*/
class Exchangerequest extends Model
{
use \October\Rain\Database\Traits\Validation;
use \October\Rain\Database\Traits\SoftDelete;
protected $dates = ['deleted_at'];
/**
* @var string The database table used by the model.
*/
public $table = 'tps_birzha_exchange_requests';
public $fillable = ['payed_for_request', 'content', 'status', 'currency', 'total_price', 'converted_to_tmt'];
/**
* @var array Validation rules
*/
public $rules = [
];
/**
* Relations
*/
public $belongsTo = [
'user' => User::class,
];
public $morphOne = [
'transaction' => [Transaction::class, 'name' => 'transactable']
];
private function createTransaction(){
$transaction = new Transaction([
'user_id' => $this->user_id,
'amount' => 0 - $this->payed_for_request,
'description' => "Kotirowkalar (import bahalar) #{$this->id} ucin arzaň tutumy."
]);
$this->transaction()->save($transaction);
}
public function afterCreate()
{
parent::afterCreate();
$this->createTransaction();
}
}

View File

@ -1,6 +1,7 @@
<?php namespace TPS\Birzha\Models;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Model;
use October\Rain\Support\Facades\Event;
@ -58,6 +59,7 @@ class Payment extends Model
public function afterUpdate()
{
if($this->status == 'approved' && $this->payment_type == 'bank' ){
// Log::info($this);
Event::fire('tps.payment.reviewed',[$this,$this->user]);
}
}

View File

@ -0,0 +1,25 @@
columns:
id:
label: ID
type: number
sortable: true
payed_for_request:
label: Fee
type: number
sortable: true
user_id:
label: User
type: text
relation: user
valueFrom: email
content:
label: Content
type: text
invisible: true
created_at:
label: 'Created At'
type: datetime
status:
label: Status
type: text
sortable: true

View File

@ -0,0 +1,32 @@
fields:
content:
label: Content
span: auto
type: text
description:
label: Description
size: ''
span: auto
type: textarea
payed_for_request:
label: Fee
span: auto
type: number
user_id:
label: User
nameFrom: email
descriptionFrom: description
span: auto
type: relation
created_at:
label: 'Created at'
mode: datetime
span: auto
type: datepicker
status:
label: Status
options:
success: success
failed: failed
span: auto
type: balloon-selector

View File

@ -10,6 +10,7 @@ columns:
label: User
type: user
relation: user
sortable: false
valueFrom: email
amount:
label: Amount

View File

@ -12,6 +12,7 @@ columns:
label: User
type: vendor
relation: vendor
sortable: false
valueFrom: email
status:
label: Status

View File

@ -89,4 +89,7 @@ tabs:
tab: Bank Info
label: Bank address
span: left
admin_email:
tab: General
label: Admin Email

View File

@ -34,6 +34,10 @@ navigation:
icon: icon-usd
permissions:
- payment
exchange-requests:
label: 'Exchange Req.'
url: tps/birzha/exchangerequests
icon: icon-sitemap
frontend:
label: Frontend
url: tps/birzha/sliders

View File

@ -15,15 +15,3 @@ Route::namespace('TPS\Birzha\Controllers')->group(function () {
});
});
// Route::get('bank_result/{payment_id}', ['as'=>'paymentReturn','uses'=>'...@checkPayment'] );
Route::get('tm/check-sms', function() {
$tx=new SMPP('217.174.228.218', 5019); // make sure the port is integer
$tx->debug=false;
$tx->bindTransmitter("birja","Birj@1");
$result = $tx->sendSMS("0773","99365611968","Hello world");
echo $tx->getStatusMessage($result);
$tx->close();
unset($tx);
});

View File

@ -0,0 +1,28 @@
<?php namespace TPS\Birzha\Updates;
use Schema;
use October\Rain\Database\Updates\Migration;
class BuilderTableCreateTpsBirzhaExchangeRequests extends Migration
{
public function up()
{
Schema::create('tps_birzha_exchange_requests', function($table)
{
$table->engine = 'InnoDB';
$table->increments('id');
$table->decimal('payed_for_request', 10, 2);
$table->integer('vendor_id');
$table->timestamp('created_at')->nullable();
$table->timestamp('updated_at')->nullable();
$table->timestamp('deleted_at')->nullable();
$table->string('content')->nullable();
$table->text('description')->nullable();
});
}
public function down()
{
Schema::dropIfExists('tps_birzha_exchange_requests');
}
}

View File

@ -0,0 +1,23 @@
<?php namespace TPS\Birzha\Updates;
use Schema;
use October\Rain\Database\Updates\Migration;
class BuilderTableUpdateTpsBirzhaExchangeRequests extends Migration
{
public function up()
{
Schema::table('tps_birzha_exchange_requests', function($table)
{
$table->renameColumn('vendor_id', 'user_id');
});
}
public function down()
{
Schema::table('tps_birzha_exchange_requests', function($table)
{
$table->renameColumn('user_id', 'vendor_id');
});
}
}

View File

@ -0,0 +1,23 @@
<?php namespace TPS\Birzha\Updates;
use Schema;
use October\Rain\Database\Updates\Migration;
class BuilderTableUpdateTpsBirzhaExchangeRequests2 extends Migration
{
public function up()
{
Schema::table('tps_birzha_exchange_requests', function($table)
{
$table->string('status')->nullable();
});
}
public function down()
{
Schema::table('tps_birzha_exchange_requests', function($table)
{
$table->dropColumn('status');
});
}
}

View File

@ -0,0 +1,27 @@
<?php namespace TPS\Birzha\Updates;
use Schema;
use October\Rain\Database\Updates\Migration;
class BuilderTableUpdateTpsBirzhaExchangeRequests3 extends Migration
{
public function up()
{
Schema::table('tps_birzha_exchange_requests', function($table)
{
$table->string('currency');
$table->decimal('total_price', 10, 2);
$table->decimal('converted_to_tmt', 10, 2);
});
}
public function down()
{
Schema::table('tps_birzha_exchange_requests', function($table)
{
$table->dropColumn('currency');
$table->dropColumn('total_price');
$table->dropColumn('converted_to_tmt');
});
}
}

View File

@ -0,0 +1,23 @@
<?php namespace TPS\Birzha\Updates;
use Schema;
use October\Rain\Database\Updates\Migration;
class BuilderTableUpdateUsers extends Migration
{
public function up()
{
Schema::table('users', function($table)
{
$table->boolean('verified')->default(false);
});
}
public function down()
{
Schema::table('users', function($table)
{
$table->dropColumn('verified');
});
}
}

View File

@ -0,0 +1,25 @@
<?php namespace TPS\Birzha\Updates;
use Schema;
use October\Rain\Database\Updates\Migration;
class BuilderTableUpdateUsers extends Migration
{
public function up()
{
Schema::table('users', function($table)
{
$table->renameColumn('verified', 'phone_verified');
$table->boolean('email_verified')->default(false);
});
}
public function down()
{
Schema::table('users', function($table)
{
$table->renameColumn('phone_verified', 'verified');
$table->dropColumn('email_verified');
});
}
}

View File

@ -0,0 +1,25 @@
<?php namespace TPS\Birzha\Updates;
use Schema;
use October\Rain\Database\Updates\Migration;
class BuilderTableUpdateUsers extends Migration
{
public function up()
{
Schema::table('users', function($table)
{
$table->string('email_activation_code', 191)->nullable();
$table->string('phone_activation_code', 191)->nullable();
});
}
public function down()
{
Schema::table('users', function($table)
{
$table->dropColumn('email_activation_code');
$table->dropColumn('phone_activation_code');
});
}
}

View File

@ -0,0 +1,23 @@
<?php namespace TPS\Birzha\Updates;
use Schema;
use October\Rain\Database\Updates\Migration;
class BuilderTableUpdateUsers extends Migration
{
public function up()
{
Schema::table('users', function($table)
{
$table->string('dial_code');
});
}
public function down()
{
Schema::table('users', function($table)
{
$table->dropColumn('dial_code');
});
}
}

View File

@ -20,9 +20,6 @@
1.0.8:
- 'Updated table tps_birzha_product'
- builder_table_update_tps_birzha_product.php
#1.0.9:
# - 'Created table tps_birzha_messages'
# - builder_table_create_tps_birzha_messages.php
1.0.11:
- 'Created table tps_birzha_terms'
- builder_table_create_tps_birzha_terms.php
@ -251,3 +248,27 @@
1.0.87:
- 'Updated table tps_birzha_contactform_messages'
- builder_table_update_tps_birzha_contactform_messages_2.php
1.0.88:
- 'Created table tps_birzha_exchange_requests'
- builder_table_create_tps_birzha_exchange_requests.php
1.0.89:
- 'Updated table tps_birzha_exchange_requests'
- builder_table_update_tps_birzha_exchange_requests.php
1.0.90:
- 'Updated table tps_birzha_exchange_requests'
- builder_table_update_tps_birzha_exchange_requests_2.php
1.0.91:
- 'Updated table tps_birzha_exchange_requests'
- builder_table_update_tps_birzha_exchange_requests_3.php
1.0.94:
- 'Updated table users'
- builder_table_update_users_table_18_01_2022_14_26.php
1.0.95:
- 'Updated table users'
- builder_table_update_users_table_02_02_2022_16_55.php
1.0.96:
- 'Updated table users'
- builder_table_update_users_table_03_02_2022_17_12.php
1.0.97:
- 'Updated table users'
- builder_table_update_users_table_04_02_2022_15_20.php

View File

@ -0,0 +1,8 @@
subject = "Confirm your account"
description = "Activate a new user"
==
Hello {{ user.name }}
We need to verify that this is your email address.
Please enter this code: {{ message.text }}

View File

@ -0,0 +1,8 @@
subject = "Confirm your account"
description = "Activate a new user"
==
Hello {{ name }}
We need to verify that this is your email address.
Please enter this code: {{ code }}

View File

@ -0,0 +1,8 @@
layout="birzha_system"
==
<p>
Hello. You successfully registered on the website of the State Stock
Exchange of Turkmenistan. In order to verify your email address and completely activate
your account, go through <a href="{{ verification_link }}">the link.</a>
</p>

View File

@ -0,0 +1,23 @@
name = "Default layout birzha"
==
{{ content|raw }}
==
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
background-color: aqua;
}
</style>
</head>
<body>
<header>header - default layout - birzha</header>
<main>{{ content|raw }}</main>
<footer>footer - default layout - birzha</footer>
</body>
</html>

View File

@ -0,0 +1,23 @@
name = "Sys layout birzha"
==
{{ content|raw }}
==
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
background-color: coral;
}
</style>
</head>
<body>
<header>header - system layout - birzha</header>
<main>{{ content|raw }}</main>
<footer>footer - system layout - birzha</footer>
</body>
</html>

View File

@ -3,4 +3,4 @@
<p><b>Тел.: </b>{{mobile}}</p>
<p><b>Email: </b>{{email}}</p>
<p><b>Текст сообщения:</b></p>
<p>{{content}} fgftgrtgt</p>
<p>{{content}} </p>

View File

@ -0,0 +1,24 @@
<p><b>ФИО: </b>{{ first_name }} {{ last_name }}</p>
<p><b>Тел.: </b>{{ phone }}</p>
<p><b>Email: </b>{{ email }}</p>
<p><b>Тип организации: </b>{{ org_type }}</p>
<p><b>Статус: </b>{{ status }}</p>
<p><b>Снято за отправку запроса: </b>{{ withdraw_from_balance }} TMT</p>
<table class="requests-table">
<thead>
<th>Наименование</th>
<th>Страна</th>
<th>Единица</th>
<th>Валюта</th>
<th>Цена</th>
</thead>
{% for item in items %}
<tr>
<td>{{ item.title }}</td>
<td>{{ item.country }}</td>
<td>{{ item.unit }}</td>
<td>{{ item.currency }}</td>
<td>{{ item.price }}</td>
</tr>
{% endfor %}
</table>

View File

@ -0,0 +1,8 @@
subject = "Confirm your account"
description = "Activate a new user"
==
Hello {{ name }}
We need to verify that this is your email address.
Please enter this code: {{ code }}

View File

@ -0,0 +1,8 @@
layout="birzha_default"
==
<p>
Здравствуйте. Вы успешно зарегистрировались на сайте Государственной
товарно-сырьевой биржи Туркменистана. Чтобы подтвердить адрес электронной почты
и полностью активировать учетную запись, перейдите по <a href="{{ verification_link }}">ссылке.</a>
</p>

View File

@ -0,0 +1,8 @@
subject = "Confirm your account"
description = "Activate a new user"
==
Hello {{ name }}
We need to verify that this is your email address.
Please enter this code: {{ code }}

View File

@ -0,0 +1,8 @@
layout="birzha_default"
==
<p>
Salam. Siz Türkmenistanyň Döwlet
haryt-çig mal biržasynyň web-sahypasynda üstünlikli hasaba alyndyňyz.
Email salgyňyzy tassyklamak üçin, <a href="{{ verification_link }}">şu düwmäni basyň.</a>
</p>

View File

@ -3,6 +3,7 @@
use RainLab\User\Models\User as UserModel;
use RainLab\User\Models\Settings as UserSettings;
use Vdomah\JWTAuth\Models\Settings;
use Illuminate\Http\Request;
Route::group(['prefix' => 'api'], function() {
@ -13,10 +14,11 @@ Route::group(['prefix' => 'api'], function() {
$login_fields = Settings::get('login_fields', ['email', 'password']);
$credentials = Input::only($login_fields);
$username = $credentials['username'];
try {
// verify the credentials and create a token for the user
if (! $token = JWTAuth::attempt($credentials)) {
if (! $token = JWTAuth::attempt(array_merge($credentials, ['username' => $credentials['dial_code'] . $username]))) {
return response()->json(['error' => [
'ru' => trans('validation.no_user', [], 'ru'),
'en' => trans('validation.no_user', [], 'en'),
@ -45,6 +47,7 @@ Route::group(['prefix' => 'api'], function() {
'username' => $userModel->username,
'email' => $userModel->email,
'is_activated' => $userModel->is_activated,
'user_balance' => $userModel->user_balance
];
}
// if no errors are encountered we can return a JWT
@ -55,7 +58,14 @@ Route::group(['prefix' => 'api'], function() {
if (Settings::get('is_refresh_disabled'))
App::abort(404, 'Page not found');
$token = Request::get('token');
$validation = \Validator::make($request->all(), [
'token' => 'required'
]);
if ($validation->fails()) {
return response()->json(['error' => $validation->errors()], 400);
}
$token = $request->get('token');
try {
// attempt to refresh the JWT
@ -96,6 +106,17 @@ Route::group(['prefix' => 'api'], function() {
$login_fields = Settings::get('signup_fields', ['email', 'password', 'password_confirmation']);
$credentials = Input::only($login_fields);
$rules = [
'email' => 'required|between:6,191|email',
'username' => 'required|digits_between:8,20|numeric',
'dial_code' => 'required',
];
$validation = \Validator::make($credentials, $rules,(new UserModel)->messages);
if ($validation->fails()) {
return Response::json(['error' => $validation->errors()], 400);
}
/**
* activation is set to be automatic
*/
@ -107,7 +128,9 @@ Route::group(['prefix' => 'api'], function() {
if (!array_key_exists('password_confirmation', $credentials) && array_key_exists('password', $credentials)) {
$credentials['password_confirmation'] = $credentials['password'];
}
$userModel = Auth::register($credentials, $automaticActivation);
$userModel = Auth::register(array_merge($credentials,[
'username' => $credentials['dial_code'] . $credentials['username']
]), $automaticActivation);
if ($userModel->methodExists('getAuthApiSignupAttributes')) {
$user = $userModel->getAuthApiSignupAttributes();
@ -117,6 +140,7 @@ Route::group(['prefix' => 'api'], function() {
'name' => $userModel->name,
'surname' => $userModel->surname,
'username' => $userModel->username,
'dial_code' => $userModel->dial_code,
'email' => $userModel->email,
'is_activated' => $userModel->is_activated,
];

View File

@ -1,4 +1,11 @@
<?php return array (
'franzose/laravel-smpp' =>
array (
'providers' =>
array (
0 => 'LaravelSmpp\\LaravelSmppServiceProvider',
),
),
'laravel/tinker' =>
array (
'providers' =>

View File

@ -28,7 +28,8 @@
24 => 'October\\Rain\\Argon\\ArgonServiceProvider',
25 => 'October\\Rain\\Redis\\RedisServiceProvider',
26 => 'October\\Rain\\Validation\\ValidationServiceProvider',
27 => 'System\\ServiceProvider',
27 => 'LaravelSmpp\\LaravelSmppServiceProvider',
28 => 'System\\ServiceProvider',
),
'eager' =>
array (
@ -43,7 +44,8 @@
8 => 'October\\Rain\\Filesystem\\FilesystemServiceProvider',
9 => 'October\\Rain\\Html\\UrlServiceProvider',
10 => 'October\\Rain\\Argon\\ArgonServiceProvider',
11 => 'System\\ServiceProvider',
11 => 'LaravelSmpp\\LaravelSmppServiceProvider',
12 => 'System\\ServiceProvider',
),
'deferred' =>
array (

View File

@ -4,4 +4,21 @@ url = "/2"
layout = "static"
is_hidden = 0
navigation_hidden = 0
==
==
<p>Web sahypasynda akkaunty hasaba almak Öz hasabyňyzyň bolmagy, haryt satmak üçin (satyjylar üçin) mahabat ibermäge ýa-da belliklerden we araçy tutumlardan (alyjylar üçin) geçip, satyjylardan özbaşdak satyn almaga mümkinçilik berer. Söwda gatnaşyklaryna gatnaşyjylar aragatnaşyk gurup we ähli zerur soraglary derrew aýdyňlaşdyryp bilerler.</p>
<p><strong>Nädip hasaba alynmaly:</strong></p>
<p>1. Kompýuteriňiziň brauzerinde Türkmenistanyň Döwlet haryt-çig mal biržasynyň web sahypasyny açyň (https://tmex.gov.tm/) we sahypanyň ýokarky sag böleginde “Hasaba alyş” elementini saýlaň ýa-da göni baglanyşygyň üsti bilen hasaba alyşa geçiň. Hasaba alyş amalyny geçmezden ozal sahypanyň ýokarky sag böleginde zerur interfeýs dilini saýlaň.</p>
<p>2. Ondan soňra zerur meýdanlary dolduryň: e-poçta salgysyny (e-mail), el telefonyňyzyň belgisini we özboluşly paroly giriziň. “Hasaby almagy dowam etdirmek” basyň.</p>
<p>3. Indiki tapgyrda görkezilen telefon belgisine işjeňleşdirme tassyklaýyş kody bolan SMS habary iberilmelidir. Ony sahypada girizmeli we “Dowam et” düwmesine basmaly. Eger-de SMS habary gelmedik ýagdaýynda, “Kod ibermek” düwmesini ulanyň.</p>
<p>4. Açylan sahypada zerur meýdanlary (Ady we familiýasy), islegiňize görä galanlaryny dolduryp maglumaty giriziň. Soňra “Saklamak” düwmesine basyň.</p>
<p>Paroly üýtgetmek isleseňiz, şu ýerde edip bilersiňiz: paroly düzüň we täzeden gaýtalaň. Hasap üçin parol girizilende giriş setiriniň gapdalyndaky görkezijä üns beriň, onuň güýç derejesini görkeziň.</p>
<p>5. Şeýlelikde web sahypadaky profiliňiz hasaba almak we işjeňleşdirmek üstünlikli tamamlandy we Siz hasap açmagyň üstünlikli tamamlanandygyny barada habary görmeli.</p>
<p>6. Web saýtda doly hukukly işe başlamak üçin hasabyňyzy doldurmak galýar. Muny iki ýol bilen edip bolýar. Hasabyňyzy nädip maliýeleşdirmelidigini okaň.</p>

View File

@ -62,6 +62,20 @@ function onStart(){
</head>
<body>
{% flash success %}
<p data-control="flash-message" data-interval="5" class="success">
{{ message }}
</p>
{% endflash %}
{% flash error %}
<p data-control="flash-message" data-interval="5" class="error">
{{ message }}
</p>
{% endflash %}
{% partial 'header' %}
{% page %}
{% partial 'footer' %}
@ -69,6 +83,32 @@ function onStart(){
{% framework extras %}
{% scripts %}
<!-- Try to find a hash when parsing a url -->
<script>
$(document).ready(function() {
switch (location.hash.substr(1)) {
case 'register':
$('.register').addClass('active');
$('#btn-2').addClass('active');
$('body').addClass('active');
$('.register_content').addClass('active');
break;
case 'login':
$('.register').addClass('active');
$('#btn-1').addClass('active');
$('body').addClass('active');
$('.register_content_2').addClass('active');
break;
default:
break;
}
})
</script>
<!-- End hash script -->
<script src="{{'assets/js/main.js'|theme}}"></script>
<script>
$('.iti__country').click(function (e) {
@ -87,6 +127,8 @@ function onStart(){
let code = $(this).find('.iti__dial-code').text()
$('.phone_box-code').html(code)
$('input[name="dial_code"]').val(code)
})
</script>

View File

@ -21,9 +21,13 @@ items:
isExternal: '0'
-
title: Sorag-jogap
nesting: null
type: cms-page
url: null
code: ''
reference: terms
cmsPage: null
replace: null
viewBag:
locale:
en:
@ -55,4 +59,20 @@ items:
isHidden: '0'
cssClass: ''
isExternal: '0'
-
title: Kotirowkalar
type: url
url: /app
code: ''
viewBag:
locale:
en:
title: Quotations
url: /app
ru:
title: Котировки
url: /app
isHidden: '0'
cssClass: ''
isExternal: '0'
name: main-top

View File

@ -11,6 +11,7 @@ localeTitle[ru] = "Профиль"
[session]
security = "user"
redirect = "index"
==
<!-- Add post ============================================================= -->
<section class="post">

View File

@ -0,0 +1,15 @@
title = "Verify Email"
url = "/verify-email/:code"
layout = "default"
is_hidden = 0
[session]
security = "user"
redirect = "index"
[emailverify]
code = "{{ :code }}"
==
{% component 'session' %}
{% component 'emailverify' %}

View File

@ -0,0 +1,23 @@
title = "Gizlinlik syýasaty"
url = "/gizlinlik-syyasaty"
layout = "default"
is_hidden = 0
[viewBag]
localeTitle[en] = "Privacy policy"
localeTitle[ru] = "Политика конфиденциальности"
localeUrl[en] = "/privacy-policy"
localeUrl[ru] = "/politika-konfidencialnosti"
==
<section class="thanks">
<div class="auto_container">
<div class="thanks_wrap">
<h3 class="thanks_text">
{{this.page.title}}
</h3>
</div>
</div>
</section>

View File

@ -1,3 +1,7 @@
<!-- This messages are shawn when a user attempts tp verify the phone number. -->
<!-- Flash messages end. -->
{{ form_ajax('onUpdate',{class:'post_form','data-request-validate':''}) }}
<div class="post_input">
@ -16,6 +20,21 @@
<label for="phone_num">{{'auth.phone'|_}}</label>
<input type="text" id="phone_num" name="username" value="{{user.username}}">
<div data-validate-for="username"></div>
<!-- Phone verification -->
{% if user.dial_code == '+993' and not user.phone_verified %}
<a href="#" onclick="$('.sms-code').addClass('active'); $(this).request('onSendSmsCode', {
error: function(e) {
console.error(e);
$('.sms-code').removeClass(active);
}
}); return false;"
>
Verify phone
</a>
{% endif %}
<!-- End Phone verification -->
</div>
@ -23,6 +42,19 @@
<label for="blue_mail">{{'auth.email'|_}}</label>
<input type="email" id="blue_mail" name="email" value="{{user.email}}">
<div data-validate-for="email"></div>
<!-- Email verification -->
{% if not user.email_verified %}
<a href="#" onclick="$(this).request('onSendEmailVerificationLink', {
error: function(e) {
console.error(e);
}
}); return false;"
>
Verify email
</a>
{% endif %}
<!-- End Email verification -->
</div>
<div class="post_input">

View File

@ -8,6 +8,9 @@
<div>
<span class="error_span t-c" data-validate-for="no_user" style="color: red;"></span>
</div>
<input type="hidden" name="dial_code" value="+993">
<div class="register_input pl-100">
<div class="phone_box ">
<div class="phone_box-flag iti__flag iti__tm">

View File

@ -6,6 +6,9 @@
<input type="email" name="email" placeholder="{{'auth.email'|_}}">
<div data-validate-for="email"></div>
</div>
<input type="hidden" name="dial_code" value="+993">
<div class="register_input pl-100">
<div class="phone_box">
<div class="phone_box-flag iti__flag iti__tm">

View File

@ -68,6 +68,76 @@ code = "main-top"
</section>
<!-- Header Top end ========================================================= -->
{% if not account.user %}
<!-- Password ==================================================== -->
<section class="password">
<div class="auto_container">
<div class="pass_wrap">
<div class="pass_mail">
<div class="pass_title">
Забыли пароль
</div>
<form action="#" class="password_form">
<div class="pass_input">
<input type="text" placeholder="Ваш электронный адрес">
</div>
<button type="submit" class="pass_btn">
Отправить
</button>
</form>
</div>
<div class="pass_change">
<div class="pass_title">
Забыли пароль
</div>
<form action="#" class="password_form">
<div class="pass_input">
<input type="text" placeholder="Введите новый пароль">
</div>
<div class="pass_input">
<input type="text" placeholder="Введите еще раз новый пароль">
</div>
<button type="submit" class="pass_btn">
Отправить
</button>
</form>
</div>
</div>
</div>
</section>
<!-- Password end ==================================================== -->
{% else %}
{% if user.dial_code == '+993' and not user.phone_verified %}
<!-- Sms code input ==================================================== -->
<section class="password sms-code">
<div class="auto_container">
<div class="pass_wrap">
<div class="pass_mail">
<div class="pass_title">
Enter 4 digits SMS code
</div>
<form action="#" data-request="onCheckSmsCode" data-request-validate data-request-flash class="password_form">
<div class="pass_input">
<input type="text" name="sms_code" placeholder="SMS code" required>
<span data-validate-for="sms_code" style="color: red;"></span>
</div>
<button type="submit" class="pass_btn" data-attach-loading>
Send
</button>
</form>
</div>
</div>
</div>
</section>
<!-- Sms code input end ==================================================== -->
{% endif %}
{% endif %}
<!--Header ============================================================= -->
<header class="header" id="header">
<div class="auto_container">
@ -135,6 +205,8 @@ code = "main-top"
</div>
<div class="profile_drop">
<a href="{{'kabinet/profile'|page}}" class="profile_item">
<div class="profile_icon">
<img src="{{'assets/images/svg/user-plus.svg'|theme}}" alt="">