diff --git a/composer.json b/composer.json index f9ca9ad03..99f6c53fd 100644 --- a/composer.json +++ b/composer.json @@ -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", diff --git a/config/app.php b/config/app.php index 95d9d98a1..6418a2744 100644 --- a/config/app.php +++ b/config/app.php @@ -155,7 +155,7 @@ return [ 'providers' => array_merge(include(base_path('modules/system/providers.php')), [ // 'Illuminate\Html\HtmlServiceProvider', // Example - + 'LaravelSmpp\LaravelSmppServiceProvider', 'System\ServiceProvider', ]), diff --git a/config/laravel-smpp.php b/config/laravel-smpp.php new file mode 100644 index 000000000..033fbb650 --- /dev/null +++ b/config/laravel-smpp.php @@ -0,0 +1,81 @@ + [ + '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 + ] +]; diff --git a/modules/system/lang/en/validation.php b/modules/system/lang/en/validation.php index d38e2eef6..89c97c5bc 100644 --- a/modules/system/lang/en/validation.php +++ b/modules/system/lang/en/validation.php @@ -195,6 +195,7 @@ return [ 'name' => 'Name', 'surname' => 'Surname', 'mobile' => 'Phone number', + 'username' => 'Phone number', ], ]; diff --git a/modules/system/lang/ru/validation.php b/modules/system/lang/ru/validation.php index 59316188e..637764d6b 100644 --- a/modules/system/lang/ru/validation.php +++ b/modules/system/lang/ru/validation.php @@ -195,6 +195,7 @@ return [ 'name' => 'Имя', 'surname' => 'Фаимилия', 'mobile' => 'Номер телефона', + 'username' => 'Номер телефона', ], ]; diff --git a/modules/system/lang/tm/validation.php b/modules/system/lang/tm/validation.php index f311dbe19..ea9042d15 100644 --- a/modules/system/lang/tm/validation.php +++ b/modules/system/lang/tm/validation.php @@ -197,6 +197,7 @@ return [ 'name' => 'At', 'surname' => 'Familiýa', 'mobile' => 'Telefon belgiňiz', + 'username' => 'Telefon belgi', ], ]; diff --git a/plugins/ahmadfatoni/apigenerator/controllers/api/ContactFormApiController.php b/plugins/ahmadfatoni/apigenerator/controllers/api/ContactFormApiController.php new file mode 100644 index 000000000..0a469b183 --- /dev/null +++ b/plugins/ahmadfatoni/apigenerator/controllers/api/ContactFormApiController.php @@ -0,0 +1,60 @@ +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); + } +} \ No newline at end of file diff --git a/plugins/ahmadfatoni/apigenerator/controllers/api/EmailVerificationController.php b/plugins/ahmadfatoni/apigenerator/controllers/api/EmailVerificationController.php new file mode 100644 index 000000000..167ec1079 --- /dev/null +++ b/plugins/ahmadfatoni/apigenerator/controllers/api/EmailVerificationController.php @@ -0,0 +1,38 @@ +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); + } + } +} diff --git a/plugins/ahmadfatoni/apigenerator/controllers/api/ExchangeRequestsController.php b/plugins/ahmadfatoni/apigenerator/controllers/api/ExchangeRequestsController.php new file mode 100644 index 000000000..b145b7f51 --- /dev/null +++ b/plugins/ahmadfatoni/apigenerator/controllers/api/ExchangeRequestsController.php @@ -0,0 +1,51 @@ +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); + } +} \ No newline at end of file diff --git a/plugins/ahmadfatoni/apigenerator/controllers/api/MessagesapiController.php b/plugins/ahmadfatoni/apigenerator/controllers/api/MessagesapiController.php index 73838df7c..a6df91889 100644 --- a/plugins/ahmadfatoni/apigenerator/controllers/api/MessagesapiController.php +++ b/plugins/ahmadfatoni/apigenerator/controllers/api/MessagesapiController.php @@ -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) + ]); } diff --git a/plugins/ahmadfatoni/apigenerator/controllers/api/NotificationsApiController.php b/plugins/ahmadfatoni/apigenerator/controllers/api/NotificationsApiController.php index 0f336456b..b868d134d 100644 --- a/plugins/ahmadfatoni/apigenerator/controllers/api/NotificationsApiController.php +++ b/plugins/ahmadfatoni/apigenerator/controllers/api/NotificationsApiController.php @@ -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); } -} \ No newline at end of file +} diff --git a/plugins/ahmadfatoni/apigenerator/controllers/api/SmsController.php b/plugins/ahmadfatoni/apigenerator/controllers/api/SmsController.php new file mode 100644 index 000000000..4ff5ddf54 --- /dev/null +++ b/plugins/ahmadfatoni/apigenerator/controllers/api/SmsController.php @@ -0,0 +1,101 @@ +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); + } + } +} diff --git a/plugins/ahmadfatoni/apigenerator/routes.php b/plugins/ahmadfatoni/apigenerator/routes.php index 58f433f29..57c105efc 100644 --- a/plugins/ahmadfatoni/apigenerator/routes.php +++ b/plugins/ahmadfatoni/apigenerator/routes.php @@ -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'); + }); }); diff --git a/plugins/ahmadfatoni/apigenerator/template/routes.dot b/plugins/ahmadfatoni/apigenerator/template/routes.dot index 51ffc7346..b11c97e06 100644 --- a/plugins/ahmadfatoni/apigenerator/template/routes.dot +++ b/plugins/ahmadfatoni/apigenerator/template/routes.dot @@ -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}} diff --git a/plugins/offline/cors/.gitignore b/plugins/offline/cors/.gitignore new file mode 100644 index 000000000..57872d0f1 --- /dev/null +++ b/plugins/offline/cors/.gitignore @@ -0,0 +1 @@ +/vendor/ diff --git a/plugins/offline/cors/CONTRIBUTING.md b/plugins/offline/cors/CONTRIBUTING.md new file mode 100644 index 000000000..f74dc2f88 --- /dev/null +++ b/plugins/offline/cors/CONTRIBUTING.md @@ -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 \ No newline at end of file diff --git a/plugins/offline/cors/LICENSE b/plugins/offline/cors/LICENSE new file mode 100644 index 000000000..6aece3014 --- /dev/null +++ b/plugins/offline/cors/LICENSE @@ -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. diff --git a/plugins/offline/cors/Plugin.php b/plugins/offline/cors/Plugin.php new file mode 100644 index 000000000..0dcb0e267 --- /dev/null +++ b/plugins/offline/cors/Plugin.php @@ -0,0 +1,49 @@ +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'], + ], + ]; + } +} diff --git a/plugins/offline/cors/README.md b/plugins/offline/cors/README.md new file mode 100644 index 000000000..ecbb035cd --- /dev/null +++ b/plugins/offline/cors/README.md @@ -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 + true, + 'maxAge' => 3600, + 'allowedOrigins' => ['*'], + 'allowedHeaders' => ['*'], + 'allowedMethods' => ['GET', 'POST'], + 'exposedHeaders' => [''], +]; +``` \ No newline at end of file diff --git a/plugins/offline/cors/classes/Cors.php b/plugins/offline/cors/classes/Cors.php new file mode 100644 index 000000000..2ceeadef4 --- /dev/null +++ b/plugins/offline/cors/classes/Cors.php @@ -0,0 +1,71 @@ + [], + '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); + } +} \ No newline at end of file diff --git a/plugins/offline/cors/classes/CorsService.php b/plugins/offline/cors/classes/CorsService.php new file mode 100644 index 000000000..41758e729 --- /dev/null +++ b/plugins/offline/cors/classes/CorsService.php @@ -0,0 +1,199 @@ +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']); + } + +} \ No newline at end of file diff --git a/plugins/offline/cors/classes/HandleCors.php b/plugins/offline/cors/classes/HandleCors.php new file mode 100644 index 000000000..9f2a88dae --- /dev/null +++ b/plugins/offline/cors/classes/HandleCors.php @@ -0,0 +1,49 @@ +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); + } + +} \ No newline at end of file diff --git a/plugins/offline/cors/classes/HandlePreflight.php b/plugins/offline/cors/classes/HandlePreflight.php new file mode 100644 index 000000000..392b5a32b --- /dev/null +++ b/plugins/offline/cors/classes/HandlePreflight.php @@ -0,0 +1,74 @@ +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; + } + } +} diff --git a/plugins/offline/cors/classes/OriginMatcher.php b/plugins/offline/cors/classes/OriginMatcher.php new file mode 100644 index 000000000..bf529d229 --- /dev/null +++ b/plugins/offline/cors/classes/OriginMatcher.php @@ -0,0 +1,100 @@ + $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\d+)-(?P\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 ([a-z][a-z0-9+\-.]*) ):// )? + (?P (?:\*|[\w-]+)(?:\.[\w-]+)* ) + (?: :(?P (?: \*|\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}"); + } +} \ No newline at end of file diff --git a/plugins/offline/cors/classes/ServiceProvider.php b/plugins/offline/cors/classes/ServiceProvider.php new file mode 100644 index 000000000..7138f9ce4 --- /dev/null +++ b/plugins/offline/cors/classes/ServiceProvider.php @@ -0,0 +1,90 @@ +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; + } +} \ No newline at end of file diff --git a/plugins/offline/cors/composer.json b/plugins/offline/cors/composer.json new file mode 100644 index 000000000..4ce370db2 --- /dev/null +++ b/plugins/offline/cors/composer.json @@ -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": {} +} diff --git a/plugins/offline/cors/lang/de/lang.php b/plugins/offline/cors/lang/de/lang.php new file mode 100644 index 000000000..0df3bb16f --- /dev/null +++ b/plugins/offline/cors/lang/de/lang.php @@ -0,0 +1,6 @@ + [ + 'name' => 'CORS', + 'description' => 'Verwalte Cross-Origin Resource Sharing Header in October CMS', + ], +]; \ No newline at end of file diff --git a/plugins/offline/cors/lang/en/lang.php b/plugins/offline/cors/lang/en/lang.php new file mode 100644 index 000000000..beaa2e5b0 --- /dev/null +++ b/plugins/offline/cors/lang/en/lang.php @@ -0,0 +1,6 @@ + [ + 'name' => 'CORS', + 'description' => 'Setup and manage Cross-Origin Resource Sharing headers', + ], +]; \ No newline at end of file diff --git a/plugins/offline/cors/models/Settings.php b/plugins/offline/cors/models/Settings.php new file mode 100644 index 000000000..381d13f1c --- /dev/null +++ b/plugins/offline/cors/models/Settings.php @@ -0,0 +1,12 @@ +replyTo($replyTo); diff --git a/plugins/rainlab/user/components/Account.php b/plugins/rainlab/user/components/Account.php index dbf6096b1..27b4bb5e1 100644 --- a/plugins/rainlab/user/components/Account.php +++ b/plugins/rainlab/user/components/Account.php @@ -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 diff --git a/plugins/rainlab/user/models/User.php b/plugins/rainlab/user/models/User.php index cf64fb7df..53e73cbd4 100644 --- a/plugins/rainlab/user/models/User.php +++ b/plugins/rainlab/user/models/User.php @@ -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; + } } diff --git a/plugins/rainlab/user/notifyrules/UserRegisteredEvent.php b/plugins/rainlab/user/notifyrules/UserRegisteredEvent.php index 1aeeab254..a31aaebfd 100644 --- a/plugins/rainlab/user/notifyrules/UserRegisteredEvent.php +++ b/plugins/rainlab/user/notifyrules/UserRegisteredEvent.php @@ -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; + } } diff --git a/plugins/rainlab/user/notifyrules/userattributecondition/attributes.yaml b/plugins/rainlab/user/notifyrules/userattributecondition/attributes.yaml index 2f4c8948a..858f239ba 100644 --- a/plugins/rainlab/user/notifyrules/userattributecondition/attributes.yaml +++ b/plugins/rainlab/user/notifyrules/userattributecondition/attributes.yaml @@ -15,3 +15,6 @@ attributes: email: label: Email address + + phone_verified: + label: Phone verified diff --git a/plugins/rainlab/user/views/mail/email_verification.htm b/plugins/rainlab/user/views/mail/email_verification.htm new file mode 100644 index 000000000..0357a5e28 --- /dev/null +++ b/plugins/rainlab/user/views/mail/email_verification.htm @@ -0,0 +1,6 @@ +layout = "birzha_email" +== + +

Confirm your account

+ +Click here to verify your email \ No newline at end of file diff --git a/plugins/tps/birzha/Plugin.php b/plugins/tps/birzha/Plugin.php index e9a1dfbe9..e659c77fb 100644 --- a/plugins/tps/birzha/Plugin.php +++ b/plugins/tps/birzha/Plugin.php @@ -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', ]; } diff --git a/plugins/tps/birzha/actions/SendSMSAction.php b/plugins/tps/birzha/actions/SendSMSAction.php index d2caf23b1..25adaa72c 100644 --- a/plugins/tps/birzha/actions/SendSMSAction.php +++ b/plugins/tps/birzha/actions/SendSMSAction.php @@ -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'; +// } } diff --git a/plugins/tps/birzha/actions/VerifyAction.php b/plugins/tps/birzha/actions/VerifyAction.php new file mode 100644 index 000000000..b63684960 --- /dev/null +++ b/plugins/tps/birzha/actions/VerifyAction.php @@ -0,0 +1,106 @@ + '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; + } +} diff --git a/plugins/tps/birzha/actions/sendsmsaction/fields.yaml b/plugins/tps/birzha/actions/sendsmsaction/fields.yaml index 13aa54807..6c4551dce 100644 --- a/plugins/tps/birzha/actions/sendsmsaction/fields.yaml +++ b/plugins/tps/birzha/actions/sendsmsaction/fields.yaml @@ -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 diff --git a/plugins/tps/birzha/actions/verifyaction/fields.yaml b/plugins/tps/birzha/actions/verifyaction/fields.yaml new file mode 100644 index 000000000..7be1ed36d --- /dev/null +++ b/plugins/tps/birzha/actions/verifyaction/fields.yaml @@ -0,0 +1,6 @@ +fields: + send_to_mode: + label: Send to + type: radio + span: left + diff --git a/plugins/tps/birzha/classes/SMPP.php b/plugins/tps/birzha/classes/SMPP.php index 6fe166158..a486ae3a2 100644 --- a/plugins/tps/birzha/classes/SMPP.php +++ b/plugins/tps/birzha/classes/SMPP.php @@ -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 "
" . '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 "
" . "Binding receiver...\n\n"; } $status=$this->_bind($login, $pass, SMPP::BIND_RECEIVER); if($this->debug){ - echo "Binding status : $status\n\n"; + echo "
" . "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 "
" . "Binding transmitter...\n\n"; } $status=$this->_bind($login, $pass, SMPP::BIND_TRANSMITTER); if($this->debug){ - echo "Binding status : $status\n\n"; + echo "
" . "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 "
" . "Unbinding...\n\n"; } $status=$this->sendCommand(SMPP::UNBIND,""); if($this->debug){ - echo "Unbind status : $status\n\n"; + echo "
" . "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 "
" . "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 "
" . "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 "
" . "Delivered sms:\n"; print_r($sms); - echo "\n"; + echo "
" . "\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 "
" . "Send PDU : $length bytes\n"; $this->printHex($header.$pdu); - echo "command_id : ".$command_id."\n"; - echo "sequence number : $seq_number\n\n"; + echo "
" . "command_id : ".$command_id."\n"; + echo "
" . "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 "
" . "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 "
" . "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 "
" . "body len : " . strlen($body) . "\n"; + echo "
" . "Command id : $command_id\n"; + echo "
" . "Command status : $command_status\n"; + echo "
" . "sequence number : $sequence_number\n\n"; } $pdu=array( 'id'=>$command_id, @@ -631,4 +631,4 @@ public function enquireLink() } print "\n"; } -} \ No newline at end of file +} diff --git a/plugins/tps/birzha/classes/SMS.php b/plugins/tps/birzha/classes/SMS.php new file mode 100644 index 000000000..ad61be241 --- /dev/null +++ b/plugins/tps/birzha/classes/SMS.php @@ -0,0 +1,31 @@ +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; + } + + } +} diff --git a/plugins/tps/birzha/components/ContactForm.php b/plugins/tps/birzha/components/ContactForm.php index 13f88ef72..9cdf43822 100644 --- a/plugins/tps/birzha/components/ContactForm.php +++ b/plugins/tps/birzha/components/ContactForm.php @@ -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') diff --git a/plugins/tps/birzha/components/EmailVerify.php b/plugins/tps/birzha/components/EmailVerify.php new file mode 100644 index 000000000..4e326b70b --- /dev/null +++ b/plugins/tps/birzha/components/EmailVerify.php @@ -0,0 +1,47 @@ + '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'; + } + + } +} \ No newline at end of file diff --git a/plugins/tps/birzha/components/emailverify/default.htm b/plugins/tps/birzha/components/emailverify/default.htm new file mode 100644 index 000000000..5465996a2 --- /dev/null +++ b/plugins/tps/birzha/components/emailverify/default.htm @@ -0,0 +1,17 @@ +
+
+
+ +
+ {{message}} +
+ + + +
+
+
\ No newline at end of file diff --git a/plugins/tps/birzha/controllers/Exchangerequests.php b/plugins/tps/birzha/controllers/Exchangerequests.php new file mode 100644 index 000000000..610b7f533 --- /dev/null +++ b/plugins/tps/birzha/controllers/Exchangerequests.php @@ -0,0 +1,17 @@ + + + diff --git a/plugins/tps/birzha/controllers/exchangerequests/config_list.yaml b/plugins/tps/birzha/controllers/exchangerequests/config_list.yaml new file mode 100644 index 000000000..2aa763a02 --- /dev/null +++ b/plugins/tps/birzha/controllers/exchangerequests/config_list.yaml @@ -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' diff --git a/plugins/tps/birzha/controllers/exchangerequests/index.htm b/plugins/tps/birzha/controllers/exchangerequests/index.htm new file mode 100644 index 000000000..ea43a3636 --- /dev/null +++ b/plugins/tps/birzha/controllers/exchangerequests/index.htm @@ -0,0 +1 @@ +listRender() ?> diff --git a/plugins/tps/birzha/controllers/payments/config_filter.yaml b/plugins/tps/birzha/controllers/payments/config_filter.yaml index e2665344f..993894338 100644 --- a/plugins/tps/birzha/controllers/payments/config_filter.yaml +++ b/plugins/tps/birzha/controllers/payments/config_filter.yaml @@ -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) diff --git a/plugins/tps/birzha/controllers/sliders/_list_toolbar.htm b/plugins/tps/birzha/controllers/sliders/_list_toolbar.htm index b55dc3966..fcecdd100 100644 --- a/plugins/tps/birzha/controllers/sliders/_list_toolbar.htm +++ b/plugins/tps/birzha/controllers/sliders/_list_toolbar.htm @@ -1,6 +1,6 @@
- +
diff --git a/themes/birzha/partials/account/signin.htm b/themes/birzha/partials/account/signin.htm index 2f78e1817..fde31e051 100644 --- a/themes/birzha/partials/account/signin.htm +++ b/themes/birzha/partials/account/signin.htm @@ -8,6 +8,9 @@
+ + +
diff --git a/themes/birzha/partials/account/signup.htm b/themes/birzha/partials/account/signup.htm index 5b3853eb5..e9b526286 100644 --- a/themes/birzha/partials/account/signup.htm +++ b/themes/birzha/partials/account/signup.htm @@ -6,6 +6,9 @@
+ + +
diff --git a/themes/birzha/partials/header.htm b/themes/birzha/partials/header.htm index 235422cd1..b2895c88c 100644 --- a/themes/birzha/partials/header.htm +++ b/themes/birzha/partials/header.htm @@ -68,6 +68,76 @@ code = "main-top" +{% if not account.user %} + +
+
+
+
+
+ Забыли пароль +
+
+
+ +
+ +
+
+
+
+ Забыли пароль +
+
+
+ +
+
+ +
+ +
+
+
+
+
+ + +{% else %} + + {% if user.dial_code == '+993' and not user.phone_verified %} + + +
+
+
+
+
+ Enter 4 digits SMS code +
+
+
+ + +
+ +
+
+
+
+
+ + + {% endif %} + +{% endif %} +