from server

This commit is contained in:
root 2023-08-01 17:27:33 +05:00
parent f196747335
commit 570ecad551
293 changed files with 15970 additions and 88 deletions

View File

@ -11,6 +11,11 @@ use Illuminate\Support\Facades\Validator;
use RainLab\Blog\Models\Post; use RainLab\Blog\Models\Post;
use TPS\Birzha\Classes\BlogPostResource; use TPS\Birzha\Classes\BlogPostResource;
use TPS\Birzha\Models\SliderApp; use TPS\Birzha\Models\SliderApp;
use TPS\Birzha\Models\UserSliders;
use TPS\Birzha\Models\Product;
use TPS\Birzha\Models\City;
use TPS\Birzha\Models\Favourites;
use TPS\Birzha\Models\Comment;
use RainLab\User\Models\User; use RainLab\User\Models\User;
class BlogPostsApiController extends Controller class BlogPostsApiController extends Controller
@ -19,10 +24,12 @@ class BlogPostsApiController extends Controller
protected $helpers; protected $helpers;
public function __construct(Post $Post, Helpers $helpers) public function __construct(Post $Post, Favourites $Favourites, Comment $Comment, Helpers $helpers)
{ {
parent::__construct(); parent::__construct();
$this->Post = $Post; $this->Post = $Post;
$this->Favourites = $Favourites;
$this->Comment = $Comment;
$this->helpers = $helpers; $this->helpers = $helpers;
} }
@ -45,6 +52,184 @@ class BlogPostsApiController extends Controller
->paginate($data['per_page'] ?? 7))->response()->getData(), 200); ->paginate($data['per_page'] ?? 7))->response()->getData(), 200);
} }
public function getAccountSliders($id)
{
$data = UserSliders::where('user_id', $id)->paginate(9);
if($data) {
$data->each(function ($item, $key) {
$item->img = 'http://78.111.88.8:9086' . \Config::get('cms.storage.media.path') . $item->img;
});
}
return response()->json($data, 200);
}
public function createComment(Request $request)
{
$currentUser = \JWTAuth::parseToken()->authenticate();
$data = $request->all();
$validator = Validator::make($data, [
'product_id' => 'required',
'comment' => 'required',
'rating' => 'required|numeric'
]);
if($validator->fails()) {
return $this->helpers->apiArrayResponseBuilder(400, 'fail', $validator->errors() );
}
$comment = new $this->Comment;
//dd($favourite);
$comment->user_id = $currentUser->id;
$comment->product_id = (int)$data['product_id'];
$comment->comment = $data['comment'];
$comment->rating = $data['rating'];
$comment->save();
return $this->helpers->apiArrayResponseBuilder(201, 'ok', ['comment' => $comment]);
}
public function getComment(Request $request)
{
//$data = $request->all();
// $validator = Validator::make($data, [
// 'product_id' => 'required',
// ]);
// if($validator->fails()) {
// return $this->helpers->apiArrayResponseBuilder(400, 'fail', $validator->errors() );
// }
$currentUser = \JWTAuth::parseToken()->authenticate();
$comment = $this->Comment::where('user_id', $currentUser->id)->paginate(9);
//dd($favourite);
return response()->json($comment, 200);
}
public function getProductComment(Request $request)
{
$data = $request->all();
$validator = Validator::make($data, [
'product_id' => 'required',
]);
if($validator->fails()) {
return $this->helpers->apiArrayResponseBuilder(400, 'fail', $validator->errors() );
}
$comment = $this->Comment::where('product_id', $data["product_id"])->where('is_approve', 1)->paginate(15);
//dd($favourite);
return response()->json($comment, 200);
}
public function createFav(Request $request)
{
$currentUser = \JWTAuth::parseToken()->authenticate();
$data = $request->all();
$validator = Validator::make($data, [
'product_id' => 'required'
]);
if($validator->fails()) {
return $this->helpers->apiArrayResponseBuilder(400, 'fail', $validator->errors() );
}
$favourite = new $this->Favourites;
//dd($favourite);
$favourite->user_id = $currentUser->id;
$favourite->product_id = (int)$data['product_id'];
$favourite->save();
return $this->helpers->apiArrayResponseBuilder(201, 'ok', ['favourite' => $favourite]);
}
public function getFav(Request $request)
{
$currentUser = \JWTAuth::parseToken()->authenticate();
$favourite = $this->Favourites::where('user_id', $currentUser->id)
->with(['product' => function($q){
$q->approved()->with(['images', 'place', 'files', 'translations:locale,model_id,attribute_data']);
}])->paginate(9);
//dd($favourite);
return response()->json($favourite, 200);
}
public function deleteFav($id){
$favourite = $this->Favourites->find($id);
$favourite->delete();
return $this->helpers->apiArrayResponseBuilder(200, 'success', ['message' => 'Data has been deleted successfully']);
}
public function getAccountProducts($id)
{
$data = Product::where('vendor_id', $id)
->with('categories:id,name')
->with([
'translations:locale,model_id,attribute_data',
'images:attachment_id,attachment_type,disk_name,file_name',
])
->approved()
->paginate(9);
return response()->json($data, 200);
}
public function getPlaces()
{
$data = City::with([
'translations:locale,model_id,attribute_data',
])
->paginate(100);
return response()->json($data, 200);
}
public function getAccountDatas($id)
{
//$data = User::find($id)->first;
$data = User::where('id', $id)->select('id', 'name', 'email', 'username', 'type', 'logo', 'shop_title', 'slogan', 'work_time', 'description', 'map', 'banner', 'is_instagram')
->with(['categories' => function($q){
$q->with('translations:locale,model_id,attribute_data');
}])
->with('sliders')
->first();
$data->pathUrl = 'http://78.111.88.8:9086' . \Config::get('cms.storage.media.path');
return response()->json($data, 200);
}
public function getAccounts(Request $request) public function getAccounts(Request $request)
{ {
@ -52,7 +237,7 @@ class BlogPostsApiController extends Controller
$dataAccounts = User::select('id', 'name', 'email', 'username', 'type', 'logo', 'shop_title', 'slogan', 'work_time', 'description', 'map', 'banner', 'is_instagram') $dataAccounts = User::select('id', 'name', 'email', 'username', 'type', 'logo', 'shop_title', 'slogan', 'work_time', 'description', 'map', 'banner', 'is_instagram')
->with('categories:id,name,slug,icon') ->with('categories:id,name,slug,icon')
->get(); ->paginate(9);
if($data && $data["type"] == "home"){ if($data && $data["type"] == "home"){
@ -94,7 +279,7 @@ class BlogPostsApiController extends Controller
if($validator->fails()) { if($validator->fails()) {
return $this->helpers->apiArrayResponseBuilder(400, 'fail', $validator->errors() ); return $this->helpers->apiArrayResponseBuilder(400, 'fail', $validator->errors() );
} }
$dataSlider = SliderApp::where('type', $data['type'])->orderBy('order', 'desc')->select('id', 'img', 'type', 'order')->get(); $dataSlider = SliderApp::where('type', $data['type'])->orderBy('order', 'desc')->select('id', 'img', 'type', 'order')->paginate(9);
//$dataSlider["img"] = $path.$dataSlider["img"]; //$dataSlider["img"] = $path.$dataSlider["img"];

View File

@ -33,31 +33,30 @@ class CategoriesAPIController extends Controller
if($data["featured"] == 3){ if($data["featured"] == 3){
$dataCat = $this->Category $dataCat = $this->Category
->select('id', 'name', 'icon', 'status', 'slug', 'sort_order', 'is_featured') ->select('id', 'name', 'icon', 'status', 'slug', 'sort_order', 'is_featured', 'is_file_category')
->with('translations:locale,model_id,attribute_data') ->with('translations:locale,model_id,attribute_data')
->orderBy('sort_order', 'asc') ->orderBy('sort_order', 'asc')
->active() ->active()
->get(); ->paginate(9);
}else{ }else{
$dataCat = $this->Category $dataCat = $this->Category
->select('id', 'name', 'icon', 'status', 'slug', 'sort_order', 'is_featured') ->select('id', 'name', 'icon', 'status', 'slug', 'sort_order', 'is_featured', 'is_file_category')
->with('translations:locale,model_id,attribute_data') ->with('translations:locale,model_id,attribute_data')
->where('is_featured', $data["featured"]) ->where('is_featured', $data["featured"])
->orderBy('sort_order', 'asc') ->orderBy('sort_order', 'asc')
->active() ->active()
->get(); ->paginate(9);
} }
if($dataCat) { if($dataCat) {
$dataCat->each(function ($item, $key) { $dataCat->each(function ($item, $key) {
$item->icon = $this->pageUrl('index') . \Config::get('cms.storage.media.path') . $item->icon; $item->icon = 'http://78.111.88.8:9086' . \Config::get('cms.storage.media.path') . $item->icon;
}); });
} }
return $this->helpers->apiArrayResponseBuilder(200, 'success', $dataCat); return response()->json($dataCat, 200);
} }
public function show($id){ public function show($id){

View File

@ -6,6 +6,7 @@ use AhmadFatoni\ApiGenerator\Helpers\Helpers;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use TPS\Birzha\Models\Product; use TPS\Birzha\Models\Product;
use TPS\Birzha\Models\Category; use TPS\Birzha\Models\Category;
use TPS\Birzha\Models\City;
use TPS\Birzha\Models\Term; use TPS\Birzha\Models\Term;
use TPS\Birzha\Models\Measure; use TPS\Birzha\Models\Measure;
use TPS\Birzha\Models\Settings; use TPS\Birzha\Models\Settings;
@ -28,6 +29,7 @@ class ProductsAPIController extends Controller
} }
public function index(){ public function index(){
//dd("sdf");
$sortOrderParam = strtolower(input('sort_order')); $sortOrderParam = strtolower(input('sort_order'));
// protect from sql injection // protect from sql injection
@ -40,13 +42,16 @@ class ProductsAPIController extends Controller
$categoryId = intval(input('category_id')); // intval protects from injection $categoryId = intval(input('category_id')); // intval protects from injection
$perPage = intval(input('custom_per_page')); // intval protects from injection $perPage = intval(input('custom_per_page')); // intval protects from injection
$productId = intval(input('product_id')); // intval protects from injection $productId = intval(input('product_id')); // intval protects from injection
$is_home = intval(input('is_home'));
$queryString = input('q'); $queryString = input('q');
$locale = input('locale'); $locale = input('locale');
// $query = $this->Product::with('categories:id,name') // $query = $this->Product::with('categories:id,name')
$query = $this->Product::with([ $query = Product::with([
'translations:locale,model_id,attribute_data', 'translations:locale,model_id,attribute_data',
'images:attachment_id,attachment_type,disk_name,file_name', 'images:attachment_id,attachment_type,disk_name,file_name',
'vendor:id,name,email,type,logo,banner,shop_title,slogan,is_instagram',
'place',
]) ])
->approved() ->approved()
->orderBy('ends_at', $sortOrder); ->orderBy('ends_at', $sortOrder);
@ -66,6 +71,19 @@ class ProductsAPIController extends Controller
$query = null; $query = null;
} }
} }
if($is_home) { // fetch offers by the category of the product
$query = $this->Product::with([
'translations:locale,model_id,attribute_data',
'images:attachment_id,attachment_type,disk_name,file_name',
'place',
'vendor:id,name,email,type,logo,banner,shop_title,slogan,is_instagram'
])
->where('is_home', 1)
->approved()
->orderBy('ends_at', $sortOrder);
}
if($productId) { // fetch offers with products in the same category if($productId) { // fetch offers with products in the same category
@ -97,7 +115,8 @@ class ProductsAPIController extends Controller
]) ])
->orderBy('ends_at', $sortOrder); ->orderBy('ends_at', $sortOrder);
} }
$data = $query ? $query->paginate($perPage) : null; $data = $query ? $query->paginate($perPage) : null;
@ -109,8 +128,11 @@ class ProductsAPIController extends Controller
$data = $this->Product::with([ $data = $this->Product::with([
'translations:locale,model_id,attribute_data', 'translations:locale,model_id,attribute_data',
'images:attachment_id,attachment_type,disk_name,file_name', 'images:attachment_id,attachment_type,disk_name,file_name',
'vendor:id,name,surname,email,username' 'vendor:id,name,surname,email,username,logo,banner',
'files', 'place'
])->find($id); ])->find($id);
$data->main_path = 'http://78.111.88.8:9086' . \Config::get('cms.storage.media.path');
if ($data && $data->status == 'approved' /*&& $data->ends_at >= \Carbon\Carbon::now()*/){ if ($data && $data->status == 'approved' /*&& $data->ends_at >= \Carbon\Carbon::now()*/){
return response()->json($data, 200); return response()->json($data, 200);
@ -123,23 +145,20 @@ class ProductsAPIController extends Controller
// step1 // step1
public function store(Request $request){ public function store(Request $request){
$data = $request->all(); $data = $request->all();
// dd($data);
$rules = [ $rules = [
'name_ru' => 'required', 'name_ru' => 'required',
// 'name_en' => 'required',
'name_tm' => 'required', 'name_tm' => 'required',
'price' => 'required|numeric', 'price' => 'required|numeric',
'place' => 'required', 'place_id' => 'required',
'description_tm' => 'required', 'description_tm' => 'required',
'description_ru' => 'required', 'description_ru' => 'required',
'old_img' => 'array',
'new_img' => 'array', 'new_img' => 'array',
'is_file' => 'required',m
// allowed only jpg & png files with size <= 1024 // allowed only jpg & png files with size <= 1024
'new_img.*' => 'mimes:jpg,png|max:1024', 'new_img.*' => 'mimes:jpg,png|max:1024',
//'new_file.*' => 'max:9024',
'category_id' => [ 'category_id' => [
'required', 'required',
@ -152,6 +171,9 @@ class ProductsAPIController extends Controller
} }
} }
], ],
//'place_id' => 'required',
]; ];
@ -159,44 +181,105 @@ class ProductsAPIController extends Controller
if($validator->fails()) { if($validator->fails()) {
return $this->helpers->apiArrayResponseBuilder(400, 'fail', $validator->errors() ); return $this->helpers->apiArrayResponseBuilder(400, 'fail', $validator->errors() );
} }
$category = Category::find($data['category_id']); // validate if no old images and no new images
if(!$request->has('new_img')) {
if(isset($data['productForEditing'])) { // if product is being edited - not added return response()->json(ErrorResponseApi::translateErrorMessage('atleast_1_image'), 400);
if(!$product = $this->checkUserHasProduct($data['productForEditing'])) {
return $this->helpers->apiArrayResponseBuilder(404, 'not found', ['error' => "Resource id = {$data['productForEditing']} could not be found"]);
}
//approved edilen produkty prodlenie uchin drafta tayinlamak, bagly transacsiasyny goparmak,
if($product && $product->status == 'approved' && $product->transaction)
{
$product->transaction()->update(['description'=>"Lot #{$product->id} {$product->name} harydyn onki publikasia tolegidir.(bu haryt prodlenia uchin taze transaksia doredilyar)"]);
$product->transaction = null;
}
} else {
$product = new $this->Product;
} }
// if new_img field is passed but empty
if($request->has('new_img')) {
$rules = [
'new_img' => 'required'
];
$validator = $this->validateForm($data, $rules);
if($validator->fails()) {
return $this->helpers->apiArrayResponseBuilder(400, 'fail', $validator->errors() );
}
}
$category = Category::find($data['category_id']);
$product = new $this->Product;
$product->translateContext('tm');
$product->name = $data['name_tm'];
$product->setAttributeTranslated('name', $data['name_ru'], 'ru');
$product->translateContext('tm'); $product->translateContext('tm');
$product->name = $data['name_tm']; $product->description = $data['description_tm'];
// Sets a single translated attribute for a language
$product->setAttributeTranslated('name', $data['name_ru'], 'ru');
// $product->setAttributeTranslated('name', $data['name_en'], 'en');
// Sets a single translated attribute for a language // Sets a single translated attribute for a language
$product->setAttributeTranslated('description', $data['description_ru'], 'ru');
$product->setAttributeTranslated('slug', \Str::slug($data['name_ru'],'-'), 'ru'); $product->setAttributeTranslated('slug', \Str::slug($data['name_ru'],'-'), 'ru');
// $product->setAttributeTranslated('slug', \Str::slug($data['name_en'],'-'), 'en');
$product->slug = \Str::slug($data['name_tm'],'-'); $product->slug = \Str::slug($data['name_tm'],'-');
$product->status = 'draft'; $product->status = 'new';
$product->place_id = $data['place_id'];
$product->price = $data['price'];
$product->is_file_product = $data['is_file'];
if($data['is_file'] == 1){
// validate if no old images and no new images
if(!$request->has('new_file')) {
return response()->json(ErrorResponseApi::translateErrorMessage('atleast_1_file'), 400);
}
// if new_img field is passed but empty
if($request->has('new_file')) {
$rules = [
'new_file' => 'required'
];
$validator = $this->validateForm($data, $rules);
if($validator->fails()) {
return $this->helpers->apiArrayResponseBuilder(400, 'fail', $validator->errors() );
}
}
try {
foreach($data['new_file'] ?? [] as $key => $fileq) {
$product->files = $fileq;
$product->save();
}
} catch(\Throwable $e) {
return $this->helpers->apiArrayResponseBuilder(500, 'server error', ['message' => 'something went wrong file']);
}
}
$product->vendor_id = \JWTAuth::parseToken()->authenticate()->id; $product->vendor_id = \JWTAuth::parseToken()->authenticate()->id;
$product->ends_at = null; // if approved but date was expired $product->ends_at = null; // if approved but date was expired
try {
foreach($data['new_img'] ?? [] as $key => $img) {
$product->images = $img;
$product->save();
}
} catch(\Throwable $e) {
return $this->helpers->apiArrayResponseBuilder(500, 'server error', ['message' => 'qqqsomething went wrong']);
}
// product found, validation passed -> can fill product &save
if(!isset($data['productForEditing'])) { if(!isset($data['productForEditing'])) {
// $product->created_at = Carbon::now('Asia/Ashgabat'); // $product->created_at = Carbon::now('Asia/Ashgabat');
$category->products()->save($product); $category->products()->save($product);
@ -206,6 +289,10 @@ class ProductsAPIController extends Controller
// attach to a new category // attach to a new category
$category->products()->save($product); $category->products()->save($product);
} }
//$produt->save();
return $this->helpers->apiArrayResponseBuilder(200, 'ok', ['product' => $product]); return $this->helpers->apiArrayResponseBuilder(200, 'ok', ['product' => $product]);
} }
@ -413,16 +500,11 @@ class ProductsAPIController extends Controller
$attachedProduct->description = $data['description_tm']; $attachedProduct->description = $data['description_tm'];
// Sets a single translated attribute for a language // Sets a single translated attribute for a language
$attachedProduct->setAttributeTranslated('description', $data['description_ru'], 'ru'); $attachedProduct->setAttributeTranslated('description', $data['description_ru'], 'ru');
$attachedProduct->setAttributeTranslated('description', $data['description_en'], 'en');
$attachedProduct->quantity = $data['quantity'];
$attachedProduct->price = $data['price']; $attachedProduct->price = $data['price'];
$attachedProduct->measure_id = $data['measure_id'];
$attachedProduct->payment_term_id = $data['payment_term_id'];
$attachedProduct->delivery_term_id = $data['delivery_term_id'];
$attachedProduct->packaging = $data['packaging'];
$attachedProduct->place = $data['place']; $attachedProduct->place = $data['place'];
$attachedProduct->currency_id = $data['currency_id'];
$attachedProduct->save(); $attachedProduct->save();
return $attachedProduct; return $attachedProduct;
@ -430,21 +512,18 @@ class ProductsAPIController extends Controller
public function myProducts() public function myProducts()
{ {
dd("sdf");
$perPage = intval(input('custom_per_page')); // intval protects from injection $perPage = intval(input('custom_per_page')); // intval protects from injection
try { try {
$products = \JWTAuth::parseToken()->authenticate() $products = \JWTAuth::parseToken()->authenticate()
->products() ->products()
->with('translations:locale,model_id,attribute_data', ->with(['translations:locale,model_id,attribute_data',
'images:attachment_id,attachment_type,disk_name,file_name') 'images:attachment_id,attachment_type,disk_name,file_name', 'place', 'files'])
->orderBy('updated_at', 'desc') ->orderBy('updated_at', 'desc')
->paginate($perPage); ->paginate($perPage);
if($products) {
foreach ($products as $product) {
$product->unit = new MeasureResource($product->measure);
}
}
} catch (\Throwable $th) { } catch (\Throwable $th) {
return $this->helpers->apiArrayResponseBuilder(500, 'server error', ['message' => 'Something went wrong']); return $this->helpers->apiArrayResponseBuilder(500, 'server error', ['message' => 'Something went wrong']);
} }

View File

@ -0,0 +1,72 @@
<?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\Term;
use RainLab\User\Models\User as Vendor;
class VendorApiController extends Controller
{
protected $Vendor;
protected $helpers;
public function __construct(Vendor $Vendor, Helpers $helpers)
{
parent::__construct();
$this->Vendor = $Vendor;
$this->helpers = $helpers;
}
public function updateVendor(Request $request){
$data = $request->all();
$validator = Validator::make($data, [
'shop_title' => 'required',
'slogan' => 'required',
'work_time' => 'required',
'description' => 'required',
]);
// $validator = $this->validateForm($data, $rules);
if($validator->fails()) {
return $this->helpers->apiArrayResponseBuilder(400, 'fail', $validator->errors() );
}
//dd($data);
$currentUser = \JWTAuth::parseToken()->authenticate();
$vendor = Vendor::find($currentUser->id)->first();
//dd($vendor->type);
if ($vendor->type != "simple"){
$vendor->shop_title = $data["shop_title"];
$vendor->slogan = $data["slogan"];
$vendor->work_time = $data["work_time"];
$vendor->description = $data["description"];
$vendor->save();
return $this->helpers->apiArrayResponseBuilder(200, 'ok', [$vendor]);
}else{
return $this->helpers->apiArrayResponseBuilder(400, 'fail');
}
}
public static function getAfterFilters() {return [];}
public static function getBeforeFilters() {return [];}
public static function getMiddleware() {return [];}
public function callAction($method, $parameters=false) {
return call_user_func_array(array($this, $method), $parameters);
}
}

View File

@ -9,17 +9,12 @@ Route::group(['prefix' =>'api/v1','namespace' =>'AhmadFatoni\ApiGenerator\Contro
// Route::get('categories/{id}/delete', ['as' => 'categories.delete', 'uses' => 'CategoriesAPIController@destroy']); // Route::get('categories/{id}/delete', ['as' => 'categories.delete', 'uses' => 'CategoriesAPIController@destroy']);
Route::get('products', ['as' => 'products.index', 'uses' => 'ProductsApiController@index']); Route::get('products', ['as' => 'products.index', 'uses' => 'ProductsApiController@index']);
// Route::get('products', 'ProductsApiController@index');
Route::get('products/{id}', ['as' => 'products.show', 'uses' => 'ProductsApiController@show']); Route::get('products/{id}', ['as' => 'products.show', 'uses' => 'ProductsApiController@show']);
Route::get('test',['as' => 'test', 'uses' => 'SmsController@index']); Route::get('test',['as' => 'test', 'uses' => 'SmsController@index']);
// Route::get('products/{id}/delete', ['as' => 'products.delete', 'uses' => 'ProductsApiController@destroy']); // Route::get('products/{id}/delete', ['as' => 'products.delete', 'uses' => 'ProductsApiController@destroy']);
// Route::resource('countries', 'CountriesapiController', ['except' => ['destroy', 'create', 'edit']]);
// Route::resource('currencies', 'CurrenciesapiController', ['except' => ['destroy', 'create', 'edit']]);
// Route::resource('measures', 'MeasuresapiController', ['except' => ['destroy', 'create', 'edit']]);
// Route::get('measures/{id}/delete', ['as' => 'measures.delete', 'uses' => 'MeasuresapiController@destroy']);
Route::resource('terms', 'TermsapiController', ['except' => ['destroy', 'create', 'edit']]); Route::resource('terms', 'TermsapiController', ['except' => ['destroy', 'create', 'edit']]);
// Route::get('terms/{id}/delete', ['as' => 'terms.delete', 'uses' => 'TermsapiController@destroy']); // Route::get('terms/{id}/delete', ['as' => 'terms.delete', 'uses' => 'TermsapiController@destroy']);
@ -30,19 +25,37 @@ Route::group(['prefix' =>'api/v1','namespace' =>'AhmadFatoni\ApiGenerator\Contro
Route::get('news/{id}', 'BlogPostsApiController@show')->where(['id' => '[0-9]+']); Route::get('news/{id}', 'BlogPostsApiController@show')->where(['id' => '[0-9]+']);
Route::get('sliders', 'BlogPostsApiController@getSliders'); Route::get('sliders', 'BlogPostsApiController@getSliders');
Route::get('places', 'BlogPostsApiController@getPlaces');
Route::get('accounts', 'BlogPostsApiController@getAccounts'); Route::get('accounts', 'BlogPostsApiController@getAccounts');
Route::get('get/product/comments', 'BlogPostsApiController@getProductComment');
// Route::get('account/{id}/sliders', 'BlogPostsApiController@getAccountSliders')->where(['id' => '[0-9]+']);
Route::get('account/{id}/products', 'BlogPostsApiController@getAccountProducts')->where(['id' => '[0-9]+']);
Route::get('account/{id}/datas', 'BlogPostsApiController@getAccountDatas');
Route::middleware(['\Tymon\JWTAuth\Middleware\GetUserFromToken'])->group(function () { Route::middleware(['\Tymon\JWTAuth\Middleware\GetUserFromToken'])->group(function () {
Route::post('update/vendor/data', 'VendorApiController@updateVendor');
Route::post('create/fav', 'BlogPostsApiController@createFav');
Route::get('get/fav', 'BlogPostsApiController@getFav');
Route::delete('fav/{id}', 'BlogPostsApiController@deleteFav');
Route::post('create/comment', 'BlogPostsApiController@createComment');
Route::get('get/customer/comments', 'BlogPostsApiController@getComment');
Route::post('products', 'ProductsApiController@store'); Route::post('products', 'ProductsApiController@store');
Route::post('products/{id}', 'ProductsApiController@update') Route::post('products/{id}', 'ProductsApiController@update')->where('id', '[0-9]+');
->where('id', '[0-9]+');
Route::delete('products/{id}/image-delete/{image_id}', 'ProductsApiController@imageDelete') Route::delete('products/{id}/image-delete/{image_id}', 'ProductsApiController@imageDelete')
->where(['id' => '[0-9]+', 'image_id' => '[0-9]+']); ->where(['id' => '[0-9]+', 'image_id' => '[0-9]+']);
Route::post('products/{id}/publish', 'ProductsApiController@publish') Route::post('products/{id}/publish', 'ProductsApiController@publish')
->where('id', '[0-9]+'); ->where('id', '[0-9]+');
Route::get('my-products/','ProductsApiController@myProducts');
Route::get('my-products','ProductsApiController@customerProducts');
Route::get('my-products/{id}','ProductsApiController@showMyProductById'); Route::get('my-products/{id}','ProductsApiController@showMyProductById');
Route::delete('my-products/{id}', 'ProductsApiController@delete') Route::delete('my-products/{id}', 'ProductsApiController@delete')
->where('id', '[0-9]+'); ->where('id', '[0-9]+');

View File

@ -0,0 +1,21 @@
# MIT license
Copyright (c) 2014-2022 Responsiv Pty Ltd, October CMS
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,74 @@
<?php namespace RainLab\Notify;
use Backend;
use System\Classes\PluginBase;
use System\Classes\SettingsManager;
/**
* Plugin registration file
*/
class Plugin extends PluginBase
{
/**
* pluginDetails
*/
public function pluginDetails()
{
return [
'name' => 'Notify',
'description' => 'Notification Services',
'author' => 'Alexey Bobkov, Samuel Georges',
'icon' => 'icon-bullhorn'
];
}
/**
* registerSettings
*/
public function registerSettings()
{
return [
'notifications' => [
'label' => 'Notification Rules',
'description' => 'Manage the events and actions that trigger notifications.',
'category' => SettingsManager::CATEGORY_NOTIFICATIONS,
'icon' => 'icon-bullhorn',
'url' => Backend::url('rainlab/notify/notifications'),
'permissions' => ['rainlab.notify.manage_notifications'],
'order' => 600,
'keywords' => 'notify'
],
];
}
/**
* registerNotificationRules
*/
public function registerNotificationRules()
{
return [
'groups' => [],
'events' => [],
'actions' => [
\RainLab\Notify\NotifyRules\SaveDatabaseAction::class,
\RainLab\Notify\NotifyRules\SendMailTemplateAction::class,
],
'conditions' => [
\RainLab\Notify\NotifyRules\ExecutionContextCondition::class,
],
];
}
/**
* registerPermissions
*/
public function registerPermissions()
{
return [
'rainlab.notify.manage_notifications' => [
'tab' => SettingsManager::CATEGORY_NOTIFICATIONS,
'label' => 'Notifications management'
],
];
}
}

View File

@ -0,0 +1,400 @@
# Notification engine
**Plugin is currently in Beta status. Proceed with caution.**
Adds support for sending notifications across a variety of different channels, including mail, SMS and Slack.
Notifications are managed in the back-end area by navigating to *Settings > Notification rules*.
## Notification workflow
When a notification fires, it uses the following workflow:
1. Plugin registers associated actions, conditions and events using `registerNotificationRules`
1. A notification class is bound to a system event using `Notifier::bindEvent`
1. A system event is fired `Event::fire`
1. The parameters of the event are captured, along with any global context parameters
1. A command is pushed on the queue to process the notification `Queue::push`
1. The command finds all notification rules using the notification class and triggers them
1. The notification conditions are checked and only proceed if met
1. The notification actions are triggered
Here is an example of a plugin registering notification rules. The `groups` definition will create containers that are used to better organise events. The `presets` definition specifies notification rules defined by the system.
```php
public function registerNotificationRules()
{
return [
'events' => [
\RainLab\User\NotifyRules\UserActivatedEvent::class,
],
'actions' => [
\RainLab\User\NotifyRules\SaveToDatabaseAction::class,
],
'conditions' => [
\RainLab\User\NotifyRules\UserAttributeCondition::class
],
'groups' => [
'user' => [
'label' => 'User',
'icon' => 'icon-user'
],
],
'presets' => '$/rainlab/user/config/notify_presets.yaml',
];
}
```
Here is an example of triggering a notification. The system event `rainlab.user.activate` is bound to the `UserActivatedEvent` class.
```php
// Bind to a system event
\RainLab\Notify\Classes\Notifier::bindEvents([
'rainlab.user.activate' => \RainLab\User\NotifyRules\UserActivatedEvent::class
]);
// Fire the system event
Event::fire('rainlab.user.activate', [$this]);
```
Here is an example of registering context parameters, which are available globally to all notifications.
```php
\RainLab\Notify\Classes\Notifier::instance()->registerCallback(function($manager) {
$manager->registerGlobalParams([
'user' => Auth::getUser()
]);
});
```
Here is an example of an event preset:
```yaml
# ===================================
# Event Presets
# ===================================
welcome_email:
name: Send welcome email to user
event: RainLab\User\NotifyRules\UserRegisteredEvent
items:
- action: RainLab\Notify\NotifyRules\SendMailTemplateAction
mail_template: rainlab.user::mail.welcome
send_to_mode: user
conditions:
- condition: RainLab\Notify\NotifyRules\ExecutionContextCondition
subcondition: environment
operator: is
value: dev
condition_text: Application environment <span class="operator">is</span> dev
```
## Creating Event classes
An event class is responsible for preparing the parameters passed to the conditions and actions. The static method `makeParamsFromEvent` will take the arguments provided by the system event and convert them in to parameters.
```php
class UserActivatedEvent extends \RainLab\Notify\Classes\EventBase
{
/**
* @var array Local conditions supported by this event.
*/
public $conditions = [
\RainLab\User\NotifyRules\UserAttributeCondition::class
];
/**
* Returns information about this event, including name and description.
*/
public function eventDetails()
{
return [
'name' => 'Activated',
'description' => 'A user is activated',
'group' => 'user'
];
}
/**
* Defines the usable parameters provided by this class.
*/
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)
];
}
}
```
## Creating Action classes
Action classes define the final step in a notification and subsequently perform the notification itself. Some examples might be sending and email or writing to the database.
```php
class SendMailTemplateAction extends \RainLab\Notify\Classes\ActionBase
{
/**
* Returns information about this event, including name and description.
*/
public function actionDetails()
{
return [
'name' => 'Compose a mail message',
'description' => 'Send a message to a recipient',
'icon' => 'icon-envelope'
];
}
/**
* Field configuration for the action.
*/
public function defineFormFields()
{
return 'fields.yaml';
}
public function getText()
{
$template = $this->host->template_name;
return 'Send a message using '.$template;
}
/**
* Triggers this action.
* @param array $params
* @return void
*/
public function triggerAction($params)
{
$email = 'test@email.tld';
$template = $this->host->template_name;
Mail::sendTo($email, $template, $params);
}
}
```
A form fields definition file is used to provide form fields when the action is established. These values are accessed from condition using the host model via the `$this->host` property.
```yaml
# ===================================
# Field Definitions
# ===================================
fields:
template_name:
label: Template name
type: text
```
An action may choose to provide no form fields by simply returning false from the `defineFormFields` method.
```php
public function defineFormFields()
{
return false;
}
```
## Creating Condition classes
A condition class should specify how it should appear in the user interface, providing a name, title and summary text. It also must declare an `isTrue` method for evaluating whether the condition is true or not.
```php
class MyCondition extends \RainLab\Notify\Classes\ConditionBase
{
/**
* Return either ConditionBase::TYPE_ANY or ConditionBase::TYPE_LOCAL
*/
public function getConditionType()
{
// If the condition should appear for all events
return ConditionBase::TYPE_ANY;
// If the condition should appear only for some events
return ConditionBase::TYPE_LOCAL;
}
/**
* Field configuration for the condition.
*/
public function defineFormFields()
{
return 'fields.yaml';
}
public function getName()
{
return 'My condition is checked';
}
public function getTitle()
{
return 'My condition';
}
public function getText()
{
$value = $this->host->mycondition;
return 'My condition <span class="operator">is</span> '.$value;
}
/**
* Checks whether the condition is TRUE for specified parameters
* @param array $params
* @return bool
*/
public function isTrue(&$params)
{
return true;
}
}
```
A form fields definition file is used to provide form fields when the condition is established. These values are accessed from condition using the host model via the `$this->host` property.
```yaml
# ===================================
# Field Definitions
# ===================================
fields:
mycondition:
label: My condition
type: dropdown
options:
true: True
false: False
```
## Model attribute condition classes
Model attribute conditions are designed specially for applying conditions to sets of model attributes.
```php
class UserAttributeCondition extends \RainLab\Notify\Classes\ModelAttributesConditionBase
{
protected $modelClass = \RainLab\User\Models\User::class;
public function getGroupingTitle()
{
return 'User attribute';
}
public function getTitle()
{
return 'User attribute';
}
/**
* Checks whether the condition is TRUE for specified parameters
* @param array $params Specifies a list of parameters as an associative array.
* @return bool
*/
public function isTrue(&$params)
{
$hostObj = $this->host;
$attribute = $hostObj->subcondition;
if (!$user = array_get($params, 'user')) {
throw new ApplicationException('Error evaluating the user attribute condition: the user object is not found in the condition parameters.');
}
return parent::evalIsTrue($user);
}
}
```
An attributes definition file is used to specify which attributes should be included in the condition.
```yaml
# ===================================
# Condition Attribute Definitions
# ===================================
attributes:
name:
label: Name
email:
label: Email address
country:
label: Country
type: relation
relation:
model: RainLab\Location\Models\Country
label: Name
nameFrom: name
keyFrom: id
```
## Save to database action
There is a dedicated table in the database for storing events and their parameters. This table is accessed using the `RainLab\Notify\Models\Notification` model and can be referenced as a relation from your own models. In this example the `MyProject` model contains its own notification channel called `notifications`.
```php
class MyProject extends Model
{
// ...
public $morphMany = [
'my_notifications' => [
\RainLab\Notify\Models\Notification::class,
'name' => 'notifiable'
]
];
}
```
This channel should be registered with the `RainLab\Notify\NotifyRules\SaveDatabaseAction` so it appears as a related object when selecting the action.
```php
SaveDatabaseAction::extend(function ($action) {
$action->addTableDefinition([
'label' => 'Project activity',
'class' => MyProject::class,
'relation' => 'my_notifications',
'param' => 'project'
]);
});
```
The **label** is shown as the related object, the **class** references the model class, the **relation** refers to the relation name. The **param** defines the parameter name, passed to the triggering event.
So essentially if you pass a `project` to the event parameters, or if `project` is a global parameter, a notification model is created with the parameters stored in the `data` attribute. Equivalent to the following code:
```php
$myproject->my_notifications()->create([
// ...
'data' => $params
]);
```
## Dynamically adding conditions to events
Events can be extended to include new local conditions. Simply add the condition class to the event `$conditions` array property.
```php
UserActivatedEvent::extend(function($event) {
$event->conditions[] = \RainLab\UserPlus\NotifyRules\UserLocationAttributeCondition::class;
});
```

View File

@ -0,0 +1,168 @@
<?php namespace RainLab\Notify\Classes;
use System\Classes\PluginManager;
use October\Rain\Extension\ExtensionBase;
use RainLab\Notify\Interfaces\Action as ActionInterface;
/**
* Notification action base class
*
* @package rainlab\notify
* @author Alexey Bobkov, Samuel Georges
*/
class ActionBase extends ExtensionBase implements ActionInterface
{
use \System\Traits\ConfigMaker;
use \System\Traits\ViewMaker;
/**
* @var Model host object
*/
protected $host;
/**
* @var mixed Extra field configuration for the condition.
*/
protected $fieldConfig;
/**
* Returns information about this action, including name and description.
*/
public function actionDetails()
{
return [
'name' => 'Action',
'description' => 'Action Description',
'icon' => 'icon-dot-circle-o'
];
}
public function __construct($host = null)
{
/*
* Paths
*/
$this->viewPath = $this->configPath = $this->guessConfigPathFrom($this);
/*
* Parse the config, if available
*/
if ($formFields = $this->defineFormFields()) {
$this->fieldConfig = $this->makeConfig($formFields);
}
if (!$this->host = $host) {
return;
}
$this->boot($host);
}
/**
* Boot method called when the condition class is first loaded
* with an existing model.
* @return array
*/
public function boot($host)
{
// Set default data
if (!$host->exists) {
$this->initConfigData($host);
}
// Apply validation rules
$host->rules = array_merge($host->rules, $this->defineValidationRules());
}
public function triggerAction($params)
{
}
public function getTitle()
{
return $this->getActionName();
}
public function getText()
{
return $this->getActionDescription();
}
public function getActionName()
{
return array_get($this->actionDetails(), 'name');
}
public function getActionDescription()
{
return array_get($this->actionDetails(), 'description');
}
public function getActionIcon()
{
return array_get($this->actionDetails(), 'icon', 'icon-dot-circle-o');
}
/**
* Extra field configuration for the condition.
*/
public function defineFormFields()
{
return 'fields.yaml';
}
/**
* Determines if this action uses form fields.
* @return bool
*/
public function hasFieldConfig()
{
return !!$this->fieldConfig;
}
/**
* Returns the field configuration used by this model.
*/
public function getFieldConfig()
{
return $this->fieldConfig;
}
/**
* Initializes configuration data when the condition is first created.
* @param Model $host
*/
public function initConfigData($host) {}
/**
* Defines validation rules for the custom fields.
* @return array
*/
public function defineValidationRules()
{
return [];
}
/**
* Spins over types registered in plugin base class with `registerNotificationRules`.
* @return array
*/
public static function findActions()
{
$results = [];
$bundles = PluginManager::instance()->getRegistrationMethodValues('registerNotificationRules');
foreach ($bundles as $plugin => $bundle) {
foreach ((array) array_get($bundle, 'actions', []) as $conditionClass) {
if (!class_exists($conditionClass)) {
continue;
}
$obj = new $conditionClass;
$results[$conditionClass] = $obj;
}
}
return $results;
}
}

View File

@ -0,0 +1,178 @@
<?php namespace RainLab\Notify\Classes;
use RainLab\Notify\Interfaces\CompoundCondition as CompoundConditionInterface;
/**
* Compound Condition Class
*/
class CompoundCondition extends ConditionBase implements CompoundConditionInterface
{
/**
* getTitle returns a condition title for displaying in the condition settings form
* @return string
*/
public function getTitle()
{
return 'Compound Condition';
}
/**
* getText
*/
public function getText()
{
$result = $this->host->condition_type == 0
? __('ALL of subconditions should be') . ' '
: __('ANY of subconditions should be') . ' ';
$result .= $this->host->condition == 'false' ? 'FALSE' : 'TRUE';
return $result;
}
/**
* getJoinText returns the text to use when joining two rules within.
* @return string
*/
public function getJoinText()
{
return $this->host->condition_type == 0 ? 'AND' : 'OR';
}
/**
* Returns a list of condition types (`ConditionBase::TYPE_*` constants)
* that can be added to this compound condition
*/
public function getAllowedSubtypes()
{
return [];
}
/**
* defineFormFields
*/
public function defineFormFields()
{
return 'fields.yaml';
}
public function initConfigData($host)
{
$host->condition_type = 0;
$host->condition = 'true';
}
public function getConditionOptions()
{
$options = [
'true' => 'TRUE',
'false' => 'FALSE'
];
return $options;
}
public function getConditionTypeOptions()
{
$options = [
'0' => 'ALL subconditions should meet the requirement',
'1' => 'ANY subconditions should meet the requirement'
];
return $options;
}
/**
* getChildOptions
*/
public function getChildOptions(array $options)
{
extract(array_merge([
'extraRules' => [],
], $options));
$result = [
'Compound condition' => CompoundCondition::class
];
$classes = $extraRules + self::findConditionsByType(ConditionBase::TYPE_ANY);
$result = $this->addClassesSubconditions($classes, $result);
return $result;
}
protected function addClassesSubconditions($classes, $list)
{
foreach ($classes as $conditionClass => $obj) {
$subConditions = $obj->listSubconditions();
if ($subConditions) {
$groupName = $obj->getGroupingTitle();
foreach ($subConditions as $name => $subcondition) {
if (!$groupName) {
$list[$name] = $conditionClass.':'.$subcondition;
}
else {
if (!array_key_exists($groupName, $list)) {
$list[$groupName] = [];
}
$list[$groupName][$name] = $conditionClass.':'.$subcondition;
}
}
}
else {
$list[$obj->getName()] = $conditionClass;
}
}
return $list;
}
/**
* Checks whether the condition is TRUE for specified parameters.
*
* @param array $params
* @return bool
*/
public function isTrue(&$params)
{
$hostObj = $this->host;
$requiredConditionValue = $hostObj->condition == 'true' ? true : false;
foreach ($hostObj->children as $subcondition) {
$subconditionResult = $subcondition->getConditionObject()->isTrue($params) ? true : false;
/*
* All
*/
if ($hostObj->condition_type == 0) {
if ($subconditionResult !== $requiredConditionValue) {
return false;
}
}
/*
* Any
*/
else {
if ($subconditionResult === $requiredConditionValue) {
return true;
}
}
}
/*
* All
*/
if ($hostObj->condition_type == 0) {
return true;
}
return false;
}
}

View File

@ -0,0 +1,211 @@
<?php namespace RainLab\Notify\Classes;
use System\Classes\PluginManager;
use October\Rain\Extension\ExtensionBase;
use RainLab\Notify\Interfaces\Condition as ConditionInterface;
/**
* Condition Base Class
*/
class ConditionBase extends ExtensionBase implements ConditionInterface
{
use \System\Traits\ConfigMaker;
use \System\Traits\ViewMaker;
const TYPE_ANY = 'any';
const TYPE_LOCAL = 'local';
const CAST_FLOAT = 'float';
const CAST_STRING = 'string';
const CAST_INTEGER = 'integer';
const CAST_BOOLEAN = 'boolean';
const CAST_RELATION = 'relation';
/**
* @var Model host object
*/
protected $host;
/**
* @var mixed Extra field configuration for the condition.
*/
protected $fieldConfig;
/**
* @var string The plugin class method used to look for conditions.
*/
protected static $registrationMethod = 'registerNotificationRules';
public function __construct($host = null)
{
/*
* Paths
*/
$this->viewPath = $this->configPath = $this->guessConfigPathFrom($this);
/*
* Parse the config
*/
$this->fieldConfig = $this->makeConfig($this->defineFormFields());
if (!$this->host = $host) {
return;
}
$this->boot($host);
}
/**
* Boot method called when the condition class is first loaded
* with an existing model.
* @return array
*/
public function boot($host)
{
// Set default data
if (!$host->exists) {
$this->initConfigData($host);
}
// Apply validation rules
$host->rules = array_merge($host->rules, $this->defineValidationRules());
// Inject view paths to the controller through the Form widget
$host->bindEvent('model.form.filterFields', function($form) {
$form->getController()->addViewPath($this->getViewPaths());
});
}
/**
* Extra field configuration for the condition.
*/
public function defineFormFields()
{
return 'fields.yaml';
}
/**
* Initializes configuration data when the condition is first created.
* @param Model $host
*/
public function initConfigData($host) {}
/**
* Defines validation rules for the custom fields.
* @return array
*/
public function defineValidationRules()
{
return [];
}
public function getText()
{
return 'Condition Text';
}
/**
* Returns a condition name for displaying in the condition selection drop-down menu
*/
public function getName()
{
return 'Condition';
}
/**
* Returns a condition title for displaying in the condition settings form
*/
public function getTitle()
{
return 'Condition';
}
/**
* This function should return one of the `ConditionBase::TYPE_*` constants
* depending on a place where the condition is valid
*/
public function getConditionType()
{
return ConditionBase::TYPE_ANY;
}
public function listSubconditions()
{
return [];
}
/**
* Returns a title to use for grouping subconditions
* in the Create Condition drop-down menu
*/
public function getGroupingTitle()
{
return null;
}
/**
* Returns the field configuration used by this model.
*/
public function getFieldConfig()
{
return $this->fieldConfig;
}
/**
* Spins over types registered in plugin base class with `registerNotificationRules`,
* checks if the condition type matches and adds it to an array that is returned.
*
* @param string $type Use `self::TYPE_*` constants
* @return array
*/
public static function findConditionsByType($type)
{
$results = [];
$bundles = PluginManager::instance()->getRegistrationMethodValues(static::$registrationMethod);
foreach ($bundles as $plugin => $bundle) {
foreach ((array) array_get($bundle, 'conditions', []) as $conditionClass) {
if (!class_exists($conditionClass)) {
continue;
}
$obj = new $conditionClass;
if ($obj->getConditionType() != $type) {
continue;
}
$results[$conditionClass] = $obj;
}
}
return $results;
}
public function setFormFields($fields)
{
}
public function setCustomData()
{
}
public function onPreRender($controller, $widget)
{
}
/**
* Checks whether the condition is TRUE for specified parameters
* @param array $params
* @return bool
*/
public function isTrue(&$params)
{
return false;
}
public function getChildOptions(array $options)
{
return [];
}
}

View File

@ -0,0 +1,237 @@
<?php namespace RainLab\Notify\Classes;
use Str;
use Yaml;
use File;
use Lang;
use System\Classes\PluginManager;
use October\Rain\Extension\ExtensionBase;
use RainLab\Notify\Interfaces\Event as EventInterface;
/**
* Notification event base class
*
* @package rainlab\notify
* @author Alexey Bobkov, Samuel Georges
*/
class EventBase extends ExtensionBase implements EventInterface
{
/**
* @var Model host object
*/
protected $host;
/**
* @var array Contains the event parameter values.
*/
protected $params = [];
/**
* @var array Local conditions supported by this event.
*/
public $conditions = [];
/**
* Returns information about this event, including name and description.
*/
public function eventDetails()
{
return [
'name' => 'Event',
'description' => 'Event description',
'group' => 'groupcode'
];
}
public function __construct($host = null)
{
$this->host = $host;
}
/**
* Defines the parameters used by this class.
* This method should be used as an override in the extended class.
*/
public function defineParams()
{
return [];
}
/**
* Local conditions supported by this event.
*/
public function defineConditions()
{
return $this->conditions;
}
/**
* Sets multiple params.
* @param array $params
* @return void
*/
public function setParams($params)
{
$this->params = $params;
}
/**
* Returns all params.
* @return array
*/
public function getParams()
{
return $this->params;
}
/**
* Generates event parameters based on arguments from the triggering system event.
* @param array $args
* @param string $eventName
* @return void
*/
public static function makeParamsFromEvent(array $args, $eventName = null)
{
}
public function getEventName()
{
return Lang::get(array_get($this->eventDetails(), 'name'));
}
public function getEventDescription()
{
return Lang::get(array_get($this->eventDetails(), 'description'));
}
public function getEventGroup()
{
return array_get($this->eventDetails(), 'group');
}
/**
* Resolves an event or action identifier from a class name or object.
* @param mixed Class name or object
* @return string Identifier in format of vendor-plugin-class
*/
public function getEventIdentifier()
{
$namespace = Str::normalizeClassName(get_called_class());
if (strpos($namespace, '\\') === null) {
return $namespace;
}
$parts = explode('\\', $namespace);
$class = array_pop($parts);
$slice = array_slice($parts, 1, 2);
$code = strtolower(implode('-', $slice) . '-' . $class);
return $code;
}
/**
* Spins over types registered in plugin base class with `registerNotificationRules`.
* @return array
*/
public static function findEvents()
{
$results = [];
$bundles = PluginManager::instance()->getRegistrationMethodValues('registerNotificationRules');
foreach ($bundles as $plugin => $bundle) {
foreach ((array) array_get($bundle, 'events', []) as $conditionClass) {
if (!class_exists($conditionClass)) {
continue;
}
$obj = new $conditionClass;
$results[$conditionClass] = $obj;
}
}
return $results;
}
public static function findEventGroups()
{
$results = [];
$bundles = PluginManager::instance()->getRegistrationMethodValues('registerNotificationRules');
foreach ($bundles as $plugin => $bundle) {
if ($groups = array_get($bundle, 'groups')) {
$results += $groups;
}
}
return $results;
}
public static function findEventsByGroup($group)
{
$results = [];
foreach (self::findEvents() as $conditionClass => $obj) {
if ($obj->getEventGroup() != $group) {
continue;
}
$results[$conditionClass] = $obj;
}
return $results;
}
public static function findEventByIdentifier($identifier)
{
foreach (self::findEvents() as $class => $obj) {
if ($obj->getEventIdentifier() == $identifier) {
return $obj;
}
}
}
/**
* Spins over preset registered in plugin base class with `registerNotificationRules`.
* @return array
*/
public static function findEventPresets()
{
$results = [];
$bundles = PluginManager::instance()->getRegistrationMethodValues('registerNotificationRules');
foreach ($bundles as $plugin => $bundle) {
if (!$presets = array_get($bundle, 'presets')) {
continue;
}
if (!is_array($presets)) {
$presets = Yaml::parse(File::get(File::symbolizePath($presets)));
}
if ($presets && is_array($presets)) {
$results += $presets;
}
}
return $results;
}
public static function findEventPresetsByClass($className)
{
$results = [];
foreach (self::findEventPresets() as $code => $definition) {
if (!$eventClass = array_get($definition, 'event')) {
continue;
}
if ($eventClass != $className) {
continue;
}
$results[$code] = $definition;
}
return $results;
}
}

View File

@ -0,0 +1,59 @@
<?php namespace RainLab\Notify\Classes;
use Illuminate\Contracts\Queue\ShouldQueue;
class EventParams
{
use \Illuminate\Queue\InteractsWithQueue;
use \Illuminate\Queue\SerializesAndRestoresModelIdentifiers;
protected $eventClass;
protected $params;
/**
* Create a new job instance.
*
* @param array $params
* @return void
*/
public function __construct($eventClass, array $params)
{
$this->eventClass = $eventClass;
$this->params = $this->serializeParams($params);
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$this->delete();
Notifier::instance()->fireEvent($this->eventClass, $this->unserializeParams());
}
protected function serializeParams($params)
{
$result = [];
foreach ($params as $param => $value) {
$result[$param] = $this->getSerializedPropertyValue($value);
}
return $result;
}
protected function unserializeParams()
{
$result = [];
foreach ($this->params as $param => $value) {
$result[$param] = $this->getRestoredPropertyValue($value);
}
return $result;
}
}

View File

@ -0,0 +1,743 @@
<?php namespace RainLab\Notify\Classes;
use Db;
use App;
use SystemException;
use Illuminate\Database\Eloquent\Model as EloquentModel;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
class ModelAttributesConditionBase extends ConditionBase
{
/**
* @var string Special value to declare nothing provided.
*/
const NO_EVAL_VALUE = '__no_eval_value__';
protected $modelClass = null;
/**
* @var array operators
*/
protected $operators = [
'is' => 'is',
'is_not' => 'is not',
'equals_or_greater' => 'equals or greater than',
'equals_or_less' => 'equals or less than',
'contains' => 'contains',
'does_not_contain' => 'does not contain',
'greater' => 'greater than',
'less' => 'less than',
'one_of' => 'is one of',
'not_one_of' => 'is not one of'
];
protected $modelObj = null;
protected $referenceInfo = null;
protected $modelAttributes = null;
protected static $modelObjCache = [];
protected static $attributeControlTypeCache = [];
public function __construct($host = null)
{
parent::__construct($host);
/*
* This is used as a base class, so register view path from here too
*/
$this->addViewPath($this->guessViewPathFrom(__CLASS__));
}
public function initConfigData($host)
{
$host->operator = 'is';
}
public function setCustomData()
{
$this->host->condition_control_type = $this->evalControlType();
}
//
// Definitions
//
/**
* This function should return one of the `ConditionBase::TYPE_*` constants
* depending on a place where the condition is valid
*/
public function getConditionType()
{
return ConditionBase::TYPE_LOCAL;
}
public function defineModelAttributes($type = null)
{
return 'attributes.yaml';
}
public function defineValidationRules()
{
return [
'value' => 'required'
];
}
public function defineFormFields()
{
return plugins_path('rainlab/notify/classes/modelattributesconditionbase/fields.yaml');
}
//
// Text helpers
//
public function getText()
{
$host = $this->host;
$attributes = $this->listModelAttributes();
if (isset($attributes[$host->subcondition])) {
$result = $this->getConditionTextPrefix($host, $attributes);
}
else {
$result = __('Unknown Attribute');
}
$result .= ' <span class="operator">'.array_get($this->operators, $host->operator, $host->operator).'</span> ';
$controlType = $this->getValueControlType();
if ($controlType == 'text') {
$result .= $host->value;
}
else {
$textValue = $this->getCustomTextValue();
if ($textValue !== false) {
return $result.' '.$textValue;
}
$referenceInfo = $this->prepareReferenceListInfo();
$modelObj = $referenceInfo->referenceModel;
if (!count($referenceInfo->columns)) {
return $result;
}
if (!strlen($host->value)) {
return $result .= '?';
}
$visibleField = $referenceInfo->primaryColumn;
if ($controlType == 'dropdown') {
$obj = $modelObj->where('id', $host->value)->first();
if ($obj) {
$result .= e($obj->{$visibleField});
}
}
else {
$ids = explode(',', $host->value);
foreach ($ids as &$id) {
$id = trim(e($id));
}
$records = $modelObj
->whereIn('id', $ids)
->orderBy($visibleField)
->get();
$recordNames = [];
foreach ($records as $record) {
$recordNames[] = $record->{$visibleField};
}
$result .= '('.implode(', ', $recordNames).')';
}
}
return $result;
}
protected function getConditionTextPrefix($parametersHost, $attributes)
{
return $attributes[$parametersHost->subcondition];
}
public function getCustomTextValue()
{
return false;
}
//
// Options
//
/**
* getSubconditionOptions
*/
public function getSubconditionOptions()
{
return $this->listModelAttributes();
}
/**
* getOperatorOptions
*/
public function getOperatorOptions()
{
$hostObj = $this->host;
$options = [];
$attribute = $hostObj->subcondition;
$currentOperatorValue = $hostObj->operator;
$model = $this->getModelObj();
$definitions = $this->listModelAttributeInfo();
if (!isset($definitions[$attribute])) {
$options = ['none' => 'Unknown attribute selected'];
}
else {
$columnType = array_get($definitions[$attribute], 'type');
if ($columnType != ConditionBase::CAST_RELATION) {
if ($columnType == ConditionBase::CAST_STRING) {
$options = [
'is' => 'is',
'is_not' => 'is not',
'contains' => 'contains',
'does_not_contain' => 'does not contain'
];
}
else {
$options = [
'is' => 'is',
'is_not' => 'is not',
'equals_or_greater' => 'equals or greater than',
'equals_or_less' => 'equals or less than',
'greater' => 'greater than',
'less' => 'less than'
];
}
}
else {
$options = [
'is' => 'is',
'is_not' => 'is not',
'one_of' => 'is one of',
'not_one_of' => 'is not one of'
];
}
}
if (!array_key_exists($currentOperatorValue, $options)) {
$keys = array_keys($options);
if (count($keys)) {
$hostObj->operator = $options[$keys[0]];
}
else {
$hostObj->operator = null;
}
}
return $options;
}
public function getValueDropdownOptions()
{
$hostObj = $this->host;
$attribute = $hostObj->subcondition;
$definitions = $this->listModelAttributeInfo();
if (!isset($definitions[$attribute])) {
return [];
}
$columnType = array_get($definitions[$attribute], 'type');
if ($columnType != ConditionBase::CAST_RELATION) {
return [];
}
$referenceInfo = $this->prepareReferenceListInfo();
$referenceModel = $referenceInfo->referenceModel;
$nameFrom = $referenceInfo->primaryColumn;
$keyFrom = $referenceInfo->primaryKey;
// Determine if the model uses a tree trait
$treeTraits = [
\October\Rain\Database\Traits\NestedTree::class,
\October\Rain\Database\Traits\SimpleTree::class
];
$usesTree = count(array_intersect($treeTraits, class_uses($referenceModel))) > 0;
$results = $referenceModel->get();
return $usesTree
? $results->listsNested($nameFrom, $keyFrom)
: $results->lists($nameFrom, $keyFrom);
}
//
// Control type
//
protected function evalControlType()
{
$hostObj = $this->host;
$attribute = $hostObj->subcondition;
$operator = $hostObj->operator;
$definitions = $this->listModelAttributeInfo();
if (!isset($definitions[$attribute])) {
return 'text';
}
$columnType = array_get($definitions[$attribute], 'type');
if ($columnType != ConditionBase::CAST_RELATION) {
return 'text';
}
else {
if ($operator == 'is' || $operator == 'is_not') {
return 'dropdown';
}
return 'multi_value';
}
}
public function getValueControlType()
{
if (App::runningInBackend()) {
return $this->evalControlType();
}
$hostObj = $this->host;
if ($controlType = $hostObj->condition_control_type) {
return $controlType;
}
$controlType = $this->evalControlType();
$this->getModelObj()
->where('id', $hostObj->id)
->update(['condition_control_type' => $controlType]);
return $hostObj->condition_control_type = $controlType;
}
//
// Attributes
//
public function listSubconditions()
{
$attributes = $this->listModelAttributes();
$result = [];
foreach ($attributes as $name => $code) {
$result[$code] = $name;
}
return $result;
}
/**
* Returns the supported attributes by a condition as an array.
* The key is the attribute and the value is the label.
*
* @return array
*/
protected function listModelAttributes()
{
$attributeInfo = $this->listModelAttributeInfo();
foreach ($attributeInfo as $attribute => $info) {
$attributes[$attribute] = array_get($info, 'label');
}
asort($attributes);
return $attributes;
}
protected function listModelAttributeInfo()
{
if ($this->modelAttributes) {
return $this->modelAttributes;
}
$config = $this->makeConfig($this->defineModelAttributes($this->getConditionType()));
$attributes = $config->attributes ?? [];
/*
* Set defaults
*/
foreach ($attributes as $attribute => $info) {
if (!isset($info['type'])) {
$attributes[$attribute]['type'] = 'string';
}
}
return $this->modelAttributes = $attributes;
}
//
// Relation based attributes
//
public function getReferencePrimaryColumn($record)
{
$referenceInfo = $this->prepareReferenceListInfo();
return $record->{$referenceInfo->primaryColumn};
}
public function listSelectedReferenceRecords()
{
$referenceInfo = $this->prepareReferenceListInfo();
$model = $referenceInfo->referenceModel;
$value = $this->host->value;
$keys = strlen($value) ? explode(',', $value) : [];
if (count($keys)) {
$model = $model->whereIn('id', $keys);
}
else {
$model = $model->whereRaw('id <> id');
}
$orderField = $referenceInfo->primaryColumn;
return $model->orderBy($orderField)->get();
}
public function prepareReferenceListInfo()
{
if (!is_null($this->referenceInfo)) {
return $this->referenceInfo;
}
$model = $this->getModelObj();
$attribute = $this->host->subcondition;
$definitions = $this->listModelAttributeInfo();
$definition = array_get($definitions, $attribute);
$columns = array_get($definition, 'columns');
$primaryColumn = array_get($definition, 'relation.nameFrom', 'name');
if ($model->hasRelation($attribute)) {
$relationType = $model->getRelationType($attribute);
$relationModel = $model->makeRelation($attribute);
$relationObject = $model->{$attribute}();
// Some simpler relations can specify a custom local or foreign "other" key,
// which can be detected and implemented here automagically.
$primaryKey = in_array($relationType, ['hasMany', 'belongsTo', 'hasOne'])
? $relationObject->getOtherKey()
: $relationModel->getKeyName();
}
elseif ($relationClass = array_get($definition, 'relation.model')) {
$relationModel = new $relationClass;
$primaryKey = array_get($definition, 'relation.keyFrom', 'id');
}
else {
throw new SystemException(sprintf('Model %s does not contain a relation "%s"', get_class($model), $attribute));
}
if (!$columns) {
$columns = [
$primaryColumn => [
'label' => array_get($definition, 'relation.label', '?'),
'searchable' => true
]
];
}
$this->referenceInfo = [];
$this->referenceInfo['referenceModel'] = $relationModel;
$this->referenceInfo['primaryKey'] = $primaryKey;
$this->referenceInfo['primaryColumn'] = $primaryColumn;
$this->referenceInfo['columns'] = $columns;
return $this->referenceInfo = (object) $this->referenceInfo;
}
public function onPreRender($controller, $widget)
{
$controlType = $this->getValueControlType();
if ($controlType != 'multi_value') {
return;
}
$selectionColumn = [
'_select_record' => [
'label' => '',
'sortable' => false,
'type' => 'partial',
'width' => '10px',
'path' => plugins_path('rainlab/notify/classes/modelattributesconditionbase/_column_select_record.htm')
]
];
$referenceInfo = $this->prepareReferenceListInfo();
$filterModel = $referenceInfo->referenceModel;
$filterColumns = $selectionColumn + $referenceInfo->columns;
/*
* List widget
*/
$config = $this->makeConfig();
$config->columns = $filterColumns;
$config->model = $filterModel;
$config->alias = $widget->alias . 'List';
$config->showSetup = false;
$config->showCheckboxes = false;
$config->recordsPerPage = 6;
$listWidget = $controller->makeWidget('Backend\Widgets\Lists', $config);
/*
* Search widget
*/
$config = $this->makeConfig();
$config->alias = $widget->alias . 'Search';
$config->growable = false;
$config->prompt = 'backend::lang.list.search_prompt';
$searchWidget = $controller->makeWidget('Backend\Widgets\Search', $config);
$searchWidget->cssClasses[] = 'condition-filter-search';
$listWidget->bindToController();
$searchWidget->bindToController();
/*
* Extend list query
*/
$listWidget->bindEvent('list.extendQueryBefore', function ($query) use ($filterModel) {
$this->prepareFilterQuery($query, $filterModel);
});
/*
* Link the Search Widget to the List Widget
*/
$listWidget->setSearchTerm($searchWidget->getActiveTerm());
$searchWidget->bindEvent('search.submit', function () use (&$searchWidget, $listWidget) {
$listWidget->setSearchTerm($searchWidget->getActiveTerm());
return $listWidget->onRefresh();
});
$controller->vars['listWidget'] = $listWidget;
$controller->vars['searchWidget'] = $searchWidget;
$controller->vars['filterHostModel'] = $this->host;
}
public function prepareFilterQuery($query, $model)
{
}
//
// Condition check
//
/**
* Checks whether the condition is TRUE for a specified model
* @return bool
*/
public function evalIsTrue($model, $customValue = '__no_eval_value__')
{
$hostObj = $this->host;
$operator = $hostObj->operator;
$attribute = $hostObj->subcondition;
$conditionValue = $hostObj->value;
$conditionValue = trim(mb_strtolower($conditionValue));
$controlType = $this->getValueControlType();
if ($controlType == 'text') {
if ($customValue === self::NO_EVAL_VALUE) {
$modelValue = trim(mb_strtolower($model->{$attribute}));
}
else {
$modelValue = trim(mb_strtolower($customValue));
}
if ($operator == 'is') {
return $modelValue == $conditionValue;
}
if ($operator == 'is_not') {
return $modelValue != $conditionValue;
}
if ($operator == 'contains') {
return mb_strpos($modelValue, $conditionValue) !== false;
}
if ($operator == 'does_not_contain') {
return mb_strpos($modelValue, $conditionValue) === false;
}
if ($operator == 'equals_or_greater') {
return $modelValue >= $conditionValue;
}
if ($operator == 'equals_or_less') {
return $modelValue <= $conditionValue;
}
if ($operator == 'greater') {
return $modelValue > $conditionValue;
}
if ($operator == 'less') {
return $modelValue < $conditionValue;
}
}
if ($controlType == 'dropdown') {
if ($customValue === self::NO_EVAL_VALUE) {
$modelValue = $model->{$attribute};
}
else {
$modelValue = $customValue;
}
if ($operator == 'is') {
if ($modelValue == null) {
return false;
}
if ($modelValue instanceof EloquentModel) {
return $modelValue->getKey() == $conditionValue;
}
if (
is_array($modelValue) &&
count($modelValue) == 1 &&
array_key_exists(0, $modelValue)
) {
return $modelValue[0] == $conditionValue;
}
if ($modelValue instanceof EloquentCollection) {
if ($modelValue->count() != 1) {
return false;
}
return $modelValue[0]->getKey() == $conditionValue;
}
}
if ($operator == 'is_not') {
if ($modelValue == null) {
return true;
}
if ($modelValue instanceof EloquentModel) {
return $modelValue->getKey() != $conditionValue;
}
if (is_array($modelValue)) {
if (count($modelValue) != 1) {
return true;
}
if (!array_key_exists(0, $modelValue)) {
return true;
}
return $modelValue[0] != $conditionValue;
}
if ($modelValue instanceof EloquentCollection) {
if (!$modelValue->count() || $modelValue->count() > 1) {
return true;
}
return $modelValue->first()->getKey() != $conditionValue;
}
}
}
if ($controlType == 'multi_value') {
if ($customValue === self::NO_EVAL_VALUE) {
$modelValue = $model->{$attribute};
}
else {
$modelValue = $customValue;
}
if (
(!$modelValue instanceof EloquentCollection) &&
(!$modelValue instanceof EloquentModel) &&
!is_array($modelValue)
) {
return false;
}
if (strlen($conditionValue)) {
$conditionValues = explode(',', $conditionValue);
foreach ($conditionValues as &$value) {
$value = trim($value);
}
} else {
$conditionValues = [];
}
if ($modelValue instanceof EloquentCollection) {
$modelKeys = array_keys($modelValue->lists('id', 'id'));
}
elseif ($modelValue instanceof EloquentModel) {
$modelKeys = [$modelValue->getKey()];
}
else {
$modelKeys = $modelValue;
}
if ($operator == 'is') {
$operator = 'one_of';
}
elseif ($operator == 'is_not') {
$operator = 'not_one_of';
}
if ($operator == 'one_of') {
return count(array_intersect($conditionValues, $modelKeys)) ? true : false;
}
if ($operator == 'not_one_of') {
return count(array_intersect($conditionValues, $modelKeys)) ? false : true;
}
}
return false;
}
//
// Helpers
//
public function getModelObj()
{
if ($this->modelObj === null) {
if (array_key_exists($this->modelClass, self::$modelObjCache)) {
$this->modelObj = self::$modelObjCache[$this->modelClass];
}
else {
$this->modelObj = self::$modelObjCache[$this->modelClass] = new $this->modelClass;
}
}
return $this->modelObj;
}
}

View File

@ -0,0 +1,134 @@
<?php namespace RainLab\Notify\Classes;
use App;
use Queue;
use Event;
use BackendAuth;
use System\Classes\PluginManager;
use RainLab\Notify\Models\NotificationRule as NotificationRuleModel;
/**
* Notification manager
*
* @package rainlab\notify
* @author Alexey Bobkov, Samuel Georges
*/
class Notifier
{
use \October\Rain\Support\Traits\Singleton;
/**
* @var array Cache of registration callbacks.
*/
protected $callbacks = [];
/**
* @var bool Internal marker to see if callbacks are run.
*/
protected $registered = false;
/**
* @var array List of registered global params in the system
*/
protected $registeredGlobalParams;
/**
* Registers a callback function that defines context variables.
* The callback function should register context variables by calling the manager's
* `registerGlobalParams` method. The manager instance is passed to the callback
* function as an argument. Usage:
*
* Notifier::registerCallback(function($manager){
* $manager->registerGlobalParams([...]);
* });
*
* @param callable $callback A callable function.
*/
public function registerCallback(callable $callback)
{
$this->callbacks[] = $callback;
}
/**
* Helper to process callbacks once and once only.
* @return void
*/
protected function processCallbacks()
{
if ($this->registered) {
return;
}
foreach ($this->callbacks as $callback) {
$callback($this);
}
$this->registered = true;
}
//
// Event binding
//
public static function bindEvents(array $events)
{
foreach ($events as $event => $class) {
self::bindEvent($event, $class);
}
}
public static function bindEvent($systemEventName, $notifyEventClass)
{
Event::listen($systemEventName, function() use ($notifyEventClass, $systemEventName) {
$params = $notifyEventClass::makeParamsFromEvent(func_get_args(), $systemEventName);
self::instance()->queueEvent($notifyEventClass, $params);
});
}
public function queueEvent($eventClass, array $params)
{
$params += $this->getContextVars();
// Use queue
if (true) {
Queue::push(new EventParams($eventClass, $params));
}
else {
$this->fireEvent($eventClass, $params);
}
}
public function fireEvent($eventClass, array $params)
{
$models = NotificationRuleModel::listRulesForEvent($eventClass);
foreach ($models as $model) {
$model->setParams($params);
$model->triggerRule();
}
}
public function registerGlobalParams(array $params)
{
if (!$this->registeredGlobalParams) {
$this->registeredGlobalParams = [];
}
$this->registeredGlobalParams = $params + $this->registeredGlobalParams;
}
public function getContextVars()
{
$this->processCallbacks();
$globals = $this->registeredGlobalParams ?: [];
return [
'isBackend' => App::runningInBackend() ? 1 : 0,
'isConsole' => App::runningInConsole() ? 1 : 0,
'appLocale' => App::getLocale(),
'sender' => null // unsafe:BackendAuth::getUser()
] + $globals;
}
}

View File

@ -0,0 +1,63 @@
<?php namespace RainLab\Notify\Classes;
use Rainlab\Notify\Models\RuleAction;
class ScheduledAction
{
use \Illuminate\Queue\InteractsWithQueue;
use \Illuminate\Queue\SerializesAndRestoresModelIdentifiers;
protected $action;
protected $params;
/**
* Create a new job instance.
*
* @param array $params
* @return void
*/
public function __construct($action, array $params)
{
$this->action = $action->id;
$this->params = $this->serializeParams($params);
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$this->delete();
if (! $actionClass = RuleAction::find($this->action)) {
return traceLog('Error: Could not restore action #' . $this->action);
}
$params = $this->unserializeParams();
$actionClass->triggerAction($params, false);
}
protected function serializeParams($params)
{
$result = [];
foreach ($params as $param => $value) {
$result[$param] = $this->getSerializedPropertyValue($value);
}
return $result;
}
protected function unserializeParams()
{
$result = [];
foreach ($this->params as $param => $value) {
$result[$param] = $this->getRestoredPropertyValue($value);
}
return $result;
}
}

View File

@ -0,0 +1,14 @@
# ===================================
# Field Definitions
# ===================================
fields:
condition_type:
label: Condition Type
span: auto
type: dropdown
condition:
label: Required Value
span: auto
type: dropdown

View File

@ -0,0 +1,10 @@
<a
href="javascript:;"
class="nolink"
data-record-key="<?= e($record->getKey()) ?>"
data-record-value="<?= e($filterHostModel->getReferencePrimaryColumn($record)) ?>"
data-multivalue-add-record>
<span class="list-badge badge-success">
<i class="icon-plus"></i>
</span>
</a>

View File

@ -0,0 +1,107 @@
<?php
$attribute = $formModel->subcondition;
$controlType = $formModel->getValueControlType();
?>
<?php if ($controlType == 'text'): ?>
<input
type="text"
name="<?= $field->getName() ?>"
id="<?= $field->getId() ?>"
value="<?= e($field->value) ?>"
class="form-control"
autocomplete="off"
maxlength="255"
/>
<?php elseif ($controlType == 'dropdown'): ?>
<?php
$fieldOptions = $formModel->getValueDropdownOptions();
?>
<select
id="<?= $field->getId() ?>"
name="<?= $field->getName() ?>"
class="form-control custom-select">
<?php foreach ($fieldOptions as $value => $option): ?>
<option
<?= $field->isSelected($value) ? 'selected="selected"' : '' ?>
value="<?= $value ?>"><?= e(trans($option)) ?></option>
<?php endforeach ?>
</select>
<?php elseif ($controlType == 'multi_value'): ?>
<?php
$selectedRecords = $formModel->listSelectedReferenceRecords();
$hasData = $selectedRecords->count();
?>
<div
class="condition-multi-value"
data-control="condition-multivalue"
data-data-locker="#<?= $field->getId() ?>">
<div class="filter-list list-flush">
<?= $searchWidget->render() ?>
<?= $listWidget->render() ?>
</div>
<label class="form-label"><?= e(__('Selected Records')) ?></label>
<div class="added-filter-list list-preview">
<div class="control-list">
<table class="table data">
<tbody data-added-records>
<?php if ($hasData): ?>
<?php foreach ($selectedRecords as $record): ?>
<tr data-record-key="<?= e($record->getKey()) ?>">
<td style="width: 10px">
<a href="javascript:;" data-multivalue-remove-record>
<span class="list-badge badge-default">
<i class="icon-minus"></i>
</span>
</a>
</td>
<td>
<?= e($formModel->getReferencePrimaryColumn($record)) ?>
</td>
</tr>
<?php endforeach ?>
<?php endif ?>
</tbody>
</table>
</div>
</div>
<input
type="hidden"
name="<?= $field->getName() ?>"
id="<?= $field->getId() ?>"
value="<?= e($field->value) ?>"
/>
<script type="text/template" data-empty-record-template>
<tr class="no-data" data-no-record-data>
<td colspan="100" class="nolink">
<p class="no-data">
<?= e(__('No records added')) ?>
</p>
</td>
</tr>
</script>
<script type="text/template" data-record-template>
<tr data-record-key="{{ key }}">
<td style="width: 10px">
<a href="javascript:;" data-multivalue-remove-record>
<span class="list-badge badge-default">
<i class="icon-minus"></i>
</span>
</a>
</td>
<td>
{{value}}
</td>
</tr>
</script>
</div>
<?php endif ?>

View File

@ -0,0 +1,22 @@
# ===================================
# Field Definitions
# ===================================
fields:
subcondition:
label: Attribute
span: auto
type: dropdown
operator:
label: Operator
span: auto
type: dropdown
dependsOn: subcondition
value:
label: Value
dependsOn: [subcondition, operator]
type: partial
path: field_value

View File

@ -0,0 +1,25 @@
{
"name": "rainlab/notify-plugin",
"type": "october-plugin",
"description": "Notify plugin for October CMS",
"homepage": "https://octobercms.com/plugin/rainlab-pages",
"keywords": ["october", "octobercms", "pages"],
"license": "MIT",
"authors": [
{
"name": "Alexey Bobkov",
"email": "aleksey.bobkov@gmail.com",
"role": "Co-founder"
},
{
"name": "Samuel Georges",
"email": "daftspunky@gmail.com",
"role": "Co-founder"
}
],
"require": {
"php": ">=5.5.9",
"composer/installers": "~1.0"
},
"minimum-stability": "dev"
}

View File

@ -0,0 +1,130 @@
<?php namespace RainLab\Notify\Controllers;
use BackendMenu;
use Backend\Classes\Controller;
use RainLab\Notify\Models\NotificationRule;
use System\Classes\SettingsManager;
use RainLab\Notify\Classes\EventBase;
use ApplicationException;
use Exception;
/**
* Notification rules controller
*
* @package rainlab\notify
* @author Alexey Bobkov, Samuel Georges
*/
class Notifications extends Controller
{
public $implement = [
\Backend\Behaviors\FormController::class,
\Backend\Behaviors\ListController::class
];
public $requiredPermissions = ['rainlab.notify.manage_notifications'];
public $listConfig = 'config_list.yaml';
public $formConfig = 'config_form.yaml';
public $eventAlias;
protected $eventClass;
public function __construct()
{
parent::__construct();
BackendMenu::setContext('October.System', 'system', 'settings');
SettingsManager::setContext('RainLab.Notify', 'notifications');
}
public function index()
{
NotificationRule::syncAll();
$this->asExtension('ListController')->index();
}
public function create($eventAlias = null)
{
try {
if (!$eventAlias) {
throw new ApplicationException('Missing a rule code');
}
$this->eventAlias = $eventAlias;
$this->bodyClass = 'compact-container breadcrumb-fancy';
$this->asExtension('FormController')->create();
}
catch (Exception $ex) {
$this->handleError($ex);
}
}
public function update($recordId = null, $context = null)
{
$this->bodyClass = 'compact-container breadcrumb-fancy';
$this->asExtension('FormController')->update($recordId, $context);
}
public function formExtendModel($model)
{
if (!$model->exists) {
$model->applyEventClass($this->getEventClass());
$model->name = $model->getEventDescription();
}
return $model;
}
// public function formBeforeSave($model)
// {
// $model->is_custom = 1;
// }
public function index_onLoadRuleGroupForm()
{
try {
$groups = EventBase::findEventGroups();
$this->vars['eventGroups'] = $groups;
}
catch (Exception $ex) {
$this->handleError($ex);
}
return $this->makePartial('add_rule_group_form');
}
/**
* This handler requires the group code passed from `onLoadRuleGroupForm`
*/
public function index_onLoadRuleEventForm()
{
try {
if (!$code = post('code')) {
throw new ApplicationException('Missing event group code');
}
$events = EventBase::findEventsByGroup($code);
$this->vars['events'] = $events;
}
catch (Exception $ex) {
$this->handleError($ex);
}
return $this->makePartial('add_rule_event_form');
}
protected function getEventClass()
{
$alias = post('event_alias', $this->eventAlias);
if ($this->eventClass !== null) {
return $this->eventClass;
}
if (!$event = EventBase::findEventByIdentifier($alias)) {
throw new ApplicationException('Unable to find event with alias: '. $alias);
}
return $this->eventClass = get_class($event);
}
}

View File

@ -0,0 +1,39 @@
<?= Form::open(['id' => 'addRuleEventForm']) ?>
<div class="modal-header flex-row-reverse">
<button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title"><?= e(__('Add Notification Rule')) ?></h4>
</div>
<div class="modal-body">
<?php if ($this->fatalError): ?>
<p class="flash-message static error"><?= $fatalError ?></p>
<?php else: ?>
<div class="control-simplelist is-selectable is-scrollable size-large" data-control="simplelist">
<ul>
<?php foreach ($events as $event): ?>
<li>
<a href="<?= Backend::url('rainlab/notify/notifications/create/'.$event->getEventIdentifier()) ?>">
<h5 class="heading"><?= e(trans($event->getEventName())) ?></h5>
<p class="description"><?= e(trans($event->getEventDescription())) ?></p>
</a>
</li>
<?php endforeach ?>
</ul>
</div>
<?php endif ?>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
<?= e(trans('backend::lang.form.cancel')) ?>
</button>
</div>
<?= Form::close() ?>

View File

@ -0,0 +1,47 @@
<?= Form::open(['id' => 'addRuleGroupForm']) ?>
<div class="modal-header flex-row-reverse">
<button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title"><?= e(__('Add Notification Rule')) ?></h4>
</div>
<div class="modal-body">
<?php if ($this->fatalError): ?>
<p class="flash-message static error"><?= $fatalError ?></p>
<?php else: ?>
<div class="control-simplelist is-selectable-box is-scrollable size-large" data-control="simplelist">
<ul>
<?php foreach ($eventGroups as $code => $group): ?>
<li>
<a
href="javascript:;"
data-control="popup"
data-handler="onLoadRuleEventForm"
data-request-data="code: '<?= e($code) ?>'"
data-dismiss="popup"
>
<div class="box">
<div class="image"><i class="<?= e(array_get($group, 'icon')) ?>"></i></div>
</div>
<h5 class="heading"><?= e(trans(array_get($group, 'label'))) ?></h5>
</a>
</li>
<?php endforeach ?>
</ul>
</div>
<?php endif ?>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
<?= e(trans('backend::lang.form.cancel')) ?>
</button>
</div>
<?= Form::close() ?>

View File

@ -0,0 +1,39 @@
<?php
$isCreate = $this->formGetContext() == 'create';
$pageUrl = isset($pageUrl) ? $pageUrl : null;
?>
<div class="form-buttons loading-indicator-container">
<!-- Save -->
<a
href="javascript:;"
class="btn btn-primary oc-icon-check save"
data-request="onSave"
data-load-indicator="<?= e(trans('backend::lang.form.saving')) ?>"
data-request-before-update="$(this).trigger('unchange.oc.changeMonitor')"
<?php if (!$isCreate): ?>data-request-data="redirect:0"<?php endif ?>
data-hotkey="ctrl+s, cmd+s">
<?= e(trans('backend::lang.form.save')) ?>
</a>
<?php if (!$isCreate): ?>
<!-- Save and Close -->
<a
href="javascript:;"
class="btn btn-primary oc-icon-check save"
data-request-before-update="$(this).trigger('unchange.oc.changeMonitor')"
data-request="onSave"
data-load-indicator="<?= e(trans('backend::lang.form.saving')) ?>">
<?= e(trans('backend::lang.form.save_and_close')) ?>
</a>
<?php endif ?>
<?php if (!$isCreate): ?>
<!-- Delete -->
<button
type="button"
class="btn btn-default empty oc-icon-trash-o"
data-request="onDelete"
data-request-confirm="<?= e(__('Delete this notification rule?')) ?>"
data-control="delete-button"></button>
<?php endif ?>
</div>

View File

@ -0,0 +1,9 @@
<div data-control="toolbar">
<a
href="javascript:;"
data-control="popup"
data-handler="onLoadRuleGroupForm"
class="btn btn-primary oc-icon-plus">
<?= e(__('New Notification Rule')) ?>
</a>
</div>

View File

@ -0,0 +1,16 @@
# ===================================
# Form Behavior Config
# ===================================
name: Notification Rule
form: $/rainlab/notify/models/notificationrule/fields.yaml
modelClass: RainLab\Notify\Models\NotificationRule
defaultRedirect: rainlab/notify/notifications
create:
redirect: rainlab/notify/notifications/update/:id
redirectClose: rainlab/notify/notifications
update:
redirect: rainlab/notify/notifications
redirectClose: rainlab/notify/notifications

View File

@ -0,0 +1,20 @@
# ===================================
# List Behavior Config
# ===================================
title: Notification Rules
list: $/rainlab/notify/models/notificationrule/columns.yaml
modelClass: RainLab\Notify\Models\NotificationRule
recordUrl: rainlab/notify/notifications/update/:id
noRecordsMessage: backend::lang.list.no_records
recordsPerPage: 30
showSetup: true
showCheckboxes: true
defaultSort:
column: count
direction: desc
toolbar:
buttons: list_toolbar
search:
prompt: backend::lang.list.search_prompt

View File

@ -0,0 +1,31 @@
<?php Block::put('breadcrumb') ?>
<ul>
<li><a href="<?= Backend::url('rainlab/notify/notifications') ?>"><?= e(__('Notification Rules')) ?></a></li>
<li><?= e(trans($this->pageTitle)) ?></li>
</ul>
<?php Block::endPut() ?>
<?php if (!$this->fatalError): ?>
<div class="layout fancy-layout">
<?= Form::open([
'class' => 'layout',
'data-change-monitor' => 'true',
'data-window-close-confirm' => e(__('Are you sure?')),
'id' => 'post-form'
]) ?>
<input type="hidden" name="event_alias" value="<?= $this->eventAlias ?>" />
<?= $this->formRender() ?>
<?= Form::close() ?>
</div>
<?php else: ?>
<div class="padded-container">
<p class="flash-message static error"><?= e(trans($this->fatalError)) ?></p>
<p><a href="<?= Backend::url('rainlab/notify/notifications') ?>" class="btn btn-default"><?= e(__('Return to Notifications')) ?></a></p>
</div>
<?php endif ?>

View File

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

View File

@ -0,0 +1,30 @@
<?php Block::put('breadcrumb') ?>
<ul>
<li><a href="<?= Backend::url('rainlab/notify/notifications') ?>"><?= e(__('Notification Rules')) ?></a></li>
<li><?= e(trans($this->pageTitle)) ?></li>
</ul>
<?php Block::endPut() ?>
<?php if (!$this->fatalError): ?>
<div class="layout fancy-layout">
<?= Form::open([
'class' => 'layout',
'data-change-monitor' => 'true',
'data-window-close-confirm' => e(__('Are you sure?')),
'id' => 'post-form'
]) ?>
<?= $this->formRender() ?>
<?= Form::close() ?>
</div>
<?php else: ?>
<div class="padded-container">
<p class="flash-message static error"><?= e(trans($this->fatalError)) ?></p>
<p><a href="<?= Backend::url('rainlab/notify/notifications') ?>" class="btn btn-default"><?= e(__('Return to Notifications')) ?></a></p>
</div>
<?php endif ?>

View File

@ -0,0 +1,388 @@
<?php namespace RainLab\Notify\FormWidgets;
use Backend\Classes\FormField;
use Backend\Classes\FormWidgetBase;
use RainLab\Notify\Classes\ActionBase;
use ApplicationException;
use ValidationException;
use Exception;
use Request;
/**
* Action builder
*/
class ActionBuilder extends FormWidgetBase
{
use \Backend\Traits\FormModelWidget;
//
// Configurable properties
//
//
// Object properties
//
/**
* @var mixed Actions cache
*/
protected $actionsCache = false;
/**
* @var Backend\Widgets\Form
*/
protected $actionFormWidget;
/**
* @var Backend\Widgets\Form
*/
protected $actionScheduleFormWidget;
/**
* {@inheritDoc}
*/
public function init()
{
// $this->fillFromConfig([
// ]);
if ($widget = $this->makeActionFormWidget()) {
$widget->bindToController();
}
if ($widget = $this->makeActionScheduleFormWidget()) {
$widget->bindToController();
}
}
/**
* {@inheritDoc}
*/
protected function loadAssets()
{
$this->addJs('js/actions.js', 'RainLab.Notify');
$this->addCss('css/actions.css', 'RainLab.Notify');
}
/**
* {@inheritDoc}
*/
public function render()
{
$this->prepareVars();
return $this->makePartial('actions_container');
}
/**
* Prepares the list data
*/
public function prepareVars()
{
$this->vars['name'] = $this->getFieldName();
$this->vars['formModel'] = $this->model;
$this->vars['actions'] = $this->getActions();
$this->vars['actionFormWidget'] = $this->actionFormWidget;
$connection = config('queue.default');
$this->vars['queueDriver'] = config("queue.connections.{$connection}.driver");
$this->vars['actionScheduleFormWidget'] = $this->actionScheduleFormWidget;
$this->vars['availableTags'] = $this->getAvailableTags();
}
/**
* @inheritDoc
*/
public function getSaveValue($value)
{
$this->model->bindEvent('model.afterSave', function() {
$this->processSave();
});
return FormField::NO_SAVE_DATA;
}
protected function processSave()
{
$cache = $this->getCacheActionDataPayload();
foreach ($cache as $id => $data) {
$action = $this->findActionObj($id);
if ($attributes = $this->getCacheActionAttributes($action)) {
$action->fill($attributes);
}
$action->save(null, $this->sessionKey);
}
}
//
// AJAX
//
public function onLoadCreateActionForm()
{
try {
$actions = ActionBase::findActions();
$this->vars['actions'] = $actions;
}
catch (Exception $ex) {
$this->handleError($ex);
}
return $this->makePartial('create_action_form');
}
public function onSaveAction()
{
$this->restoreCacheActionDataPayload();
$action = $this->findActionObj();
$data = post('Action', []);
$schedule = post('ActionSchedule', []);
$action->fill($data);
$action->fill($schedule);
$action->validate();
$action->action_text = $action->getActionObject()->getText();
$action->applyCustomData();
$this->setCacheActionData($action);
return $this->renderActions($action);
}
public function onLoadActionSetup()
{
try {
$action = $this->findActionObj();
$data = $this->getCacheActionAttributes($action);
if (!is_null($this->actionFormWidget)) {
$this->actionFormWidget->setFormValues($data);
}
$this->actionScheduleFormWidget->setFormValues($data);
$this->prepareVars();
$this->vars['action'] = $action;
}
catch (Exception $ex) {
$this->handleError($ex);
}
return $this->makePartial('action_settings_form');
}
public function onCreateAction()
{
if (!$className = post('action_class')) {
throw new ApplicationException('Please specify an action');
}
$this->restoreCacheActionDataPayload();
$newAction = $this->getRelationModel();
$newAction->class_name = $className;
$newAction->save();
$this->model->rule_actions()->add($newAction, post('_session_key'));
$this->vars['newActionId'] = $newAction->id;
return $this->renderActions();
}
public function onDeleteAction()
{
$action = $this->findActionObj();
$this->model->rule_actions()->remove($action, post('_session_key'));
return $this->renderActions();
}
public function onCancelActionSettings()
{
$action = $this->findActionObj(post('new_action_id'));
$action->delete();
return $this->renderActions();
}
//
// Postback deferring
//
public function getCacheActionAttributes($action)
{
return array_get($this->getCacheActionData($action), 'attributes');
}
public function getCacheActionTitle($action)
{
return array_get($this->getCacheActionData($action), 'title');
}
public function getCacheActionText($action)
{
return array_get($this->getCacheActionData($action), 'text');
}
public function getCacheActionData($action, $default = null)
{
$cache = post('action_data', []);
if (is_array($cache) && array_key_exists($action->id, $cache)) {
return json_decode($cache[$action->id], true);
}
if ($default === false) {
return null;
}
return $this->makeCacheActionData($action);
}
public function makeCacheActionData($action)
{
$data = [
'attributes' => $action->config_data,
'title' => $action->getTitle(),
'text' => $action->getText(),
];
return $data;
}
public function setCacheActionData($action)
{
$cache = post('action_data', []);
$cache[$action->id] = json_encode($this->makeCacheActionData($action));
Request::merge([
'action_data' => $cache
]);
}
public function restoreCacheActionDataPayload()
{
Request::merge([
'action_data' => json_decode(post('current_action_data'), true)
]);
}
public function getCacheActionDataPayload()
{
return post('action_data', []);
}
//
// Helpers
//
protected function getAvailableTags()
{
$tags = [];
if ($this->model->methodExists('defineParams')) {
$params = $this->model->defineParams();
foreach ($params as $param => $definition) {
$tags[$param] = array_get($definition, 'label');
}
}
return $tags;
}
/**
* Updates the primary rule actions container
* @return array
*/
protected function renderActions()
{
$this->prepareVars();
return [
'#'.$this->getId() => $this->makePartial('actions')
];
}
protected function makeActionFormWidget()
{
if ($this->actionFormWidget !== null) {
return $this->actionFormWidget;
}
if (!$model = $this->findActionObj(null, false)) {
return null;
}
if (!$model->hasFieldConfig()) {
return null;
}
$config = $model->getFieldConfig();
$config->model = $model;
$config->alias = $this->alias . 'Form';
$config->arrayName = 'Action';
$widget = $this->makeWidget('Backend\Widgets\Form', $config);
return $this->actionFormWidget = $widget;
}
protected function makeActionScheduleFormWidget()
{
if ($this->actionScheduleFormWidget !== null) {
return $this->actionScheduleFormWidget;
}
if (!$model = $this->findActionObj(null, false)) {
return null;
}
$config = $this->makeConfig('$/rainlab/notify/models/ruleaction/fields_schedule.yaml');
$config->model = $model;
$config->alias = $this->alias . 'ScheduleForm';
$config->arrayName = 'ActionSchedule';
$widget = $this->makeWidget('Backend\Widgets\Form', $config);
return $this->actionScheduleFormWidget = $widget;
}
protected function getActions()
{
if ($this->actionsCache !== false) {
return $this->actionsCache;
}
$relationObject = $this->getRelationObject();
$actions = $relationObject->withDeferred($this->sessionKey)->get();
return $this->actionsCache = $actions ?: null;
}
protected function findActionObj($actionId = null, $throw = true)
{
$actionId = $actionId ? $actionId : post('current_action_id');
$action = null;
if (strlen($actionId)) {
$action = $this->getRelationModel()->find($actionId);
}
if ($throw && !$action) {
throw new ApplicationException('Action not found');
}
return $action;
}
}

View File

@ -0,0 +1,455 @@
<?php namespace RainLab\Notify\FormWidgets;
use Backend\Classes\FormField;
use Backend\Classes\FormWidgetBase;
use RainLab\Notify\Classes\ConditionBase;
use ApplicationException;
use ValidationException;
use Exception;
use Request;
/**
* Condition builder
*/
class ConditionBuilder extends FormWidgetBase
{
use \Backend\Traits\FormModelWidget;
use \Backend\Traits\CollapsableWidget;
//
// Configurable properties
//
/**
* @var string Rule type.
*/
public $conditionsRuleType = ConditionBase::TYPE_ANY;
//
// Object properties
//
/**
* {@inheritDoc}
*/
public $defaultAlias = 'ruleconditions';
/**
* @var mixed Root condition
*/
protected $conditionsRoot = false;
/**
* @var Backend\Widgets\Form
*/
protected $conditionFormWidget;
/**
* {@inheritDoc}
*/
public function init()
{
$this->fillFromConfig([
'conditionsRuleType',
]);
if ($widget = $this->makeConditionFormWidget()) {
$widget->bindToController();
}
$this->initRootCondition();
}
/**
* {@inheritDoc}
*/
protected function loadAssets()
{
$this->addJs('js/conditions.js', 'RainLab.Notify');
$this->addJs('js/conditions.multivalue.js', 'RainLab.Notify');
$this->addCss('css/conditions.css', 'RainLab.Notify');
}
/**
* {@inheritDoc}
*/
public function render()
{
$this->prepareVars();
return $this->makePartial('conditions_container');
}
/**
* Prepares the list data
*/
public function prepareVars()
{
$this->vars['name'] = $this->getFieldName();
$this->vars['rootCondition'] = $this->getConditionsRoot();
$this->vars['conditionFormWidget'] = $this->conditionFormWidget;
}
public function initRootCondition()
{
if ($this->getConditionsRoot()) {
return;
}
$relationObject = $this->getRelationObject();
$rootRule = $this->getRelationModel();
$rootRule->rule_host_type = $this->conditionsRuleType;
$rootRule->class_name = $rootRule->getRootConditionClass();
$rootRule->save();
$relationObject->add($rootRule, $this->sessionKey);
$this->conditionsRoot = $rootRule;
}
public function isRootCondition($condition)
{
if ($root = $this->getConditionsRoot()) {
return $condition->id === $root->id;
}
return false;
}
public function getConditionsRoot()
{
if ($this->conditionsRoot !== false) {
return $this->conditionsRoot;
}
$relationObject = $this->getRelationObject();
$rootCondition = $relationObject->withDeferred($this->sessionKey)->first();
return $this->conditionsRoot = $rootCondition ?: null;
}
/**
* @inheritDoc
*/
public function getSaveValue($value)
{
$this->model->bindEvent('model.afterSave', function() {
$this->processSave();
});
return FormField::NO_SAVE_DATA;
}
protected function processSave()
{
$cache = $this->getCacheConditionDataPayload();
foreach ($cache as $id => $data) {
$condition = $this->findConditionObj($id);
$attributes = $this->getCacheConditionAttributes($condition);
$condition->fill($attributes);
$condition->save(null, $this->sessionKey.'_'.$condition->id);
}
}
//
// AJAX
//
public function onLoadConditionSetup()
{
try {
$condition = $this->findConditionObj();
$this->prepareVars();
$this->vars['condition'] = $condition;
}
catch (Exception $ex) {
$this->handleError($ex);
}
return $this->makePartial('condition_settings_form');
}
public function onLoadCreateChildCondition()
{
try {
$condition = $this->findConditionObj();
/*
* Look up parents
*/
$parents = [$condition->id];
$parentsArray = post('condition_parent_id', []);
$currentId = $condition->id;
while (array_key_exists($currentId, $parentsArray) && $parentsArray[$currentId]) {
$parents[] = $currentId = $parentsArray[$currentId];
}
/*
* Custom rules provided by model
*/
$extraRules = [];
if ($this->model->methodExists('getExtraConditionRules')) {
$extraRules = $this->model->getExtraConditionRules();
}
/*
* Look up conditions
*/
$options = $condition->getChildOptions([
'ruleType' => $this->conditionsRuleType,
'parentIds' => $parents,
'extraRules' => $extraRules
]);
$this->prepareVars();
$this->vars['condition'] = $condition;
$this->vars['options'] = $options;
}
catch (Exception $ex) {
$this->handleError($ex);
}
return $this->makePartial('create_child_form');
}
public function onSaveCondition()
{
$this->restoreCacheConditionDataPayload();
$condition = $this->findConditionObj();
$data = post('Condition', []);
$condition->fill($data);
$condition->validate();
$condition->condition_text = $condition->getConditionObject()->getText();
$condition->applyCustomData();
$this->setCacheConditionData($condition);
return $this->renderConditions($condition);
}
public function onCreateCondition()
{
if (!$className = post('condition_class')) {
throw new ValidationException(['condition_class' => 'Please specify a condition']);
}
$this->restoreCacheConditionDataPayload();
$subcondition = null;
$parts = explode(':', $className);
if (count($parts) > 1) {
$subcondition = $parts[1];
$className = $parts[0];
}
$parentCondition = $this->findConditionObj();
$newCondition = $this->getRelationModel();
$newCondition->class_name = $className;
$newCondition->rule_host_type = $parentCondition->rule_host_type;
if ($subcondition) {
$newCondition->subcondition = $subcondition;
}
$newCondition->save();
$parentCondition->children()->add($newCondition, post('_session_key').'_'.$parentCondition->id);
$this->vars['newConditionId'] = $newCondition->id;
return $this->renderConditions($parentCondition);
}
public function onDeleteCondition()
{
$parentCondition = null;
$condition = $this->findConditionObj();
if ($parentId = $this->getParentIdFromCondition($condition)) {
$parentCondition = $this->findConditionObj($parentId);
if ($parentCondition) {
$parentCondition->children()->remove($condition, post('_session_key').'_'.$parentCondition->id);
}
}
return $this->renderConditions($parentCondition);
}
public function onCancelConditionSettings()
{
$condition = $this->findConditionObj(post('new_condition_id'));
$condition->delete();
return $this->renderConditions();
}
//
// Postback deferring
//
public function getCacheConditionAttributes($condition)
{
return array_get($this->getCacheConditionData($condition), 'attributes');
}
public function getCacheConditionText($condition)
{
return array_get($this->getCacheConditionData($condition), 'text');
}
public function getCacheConditionJoinText($condition)
{
return array_get($this->getCacheConditionData($condition), 'joinText');
}
public function getCacheConditionData($condition, $default = null)
{
$cache = post('condition_data', []);
if (is_array($cache) && array_key_exists($condition->id, $cache)) {
return json_decode($cache[$condition->id], true);
}
if ($default === false) {
return null;
}
return $this->makeCacheConditionData($condition);
}
public function makeCacheConditionData($condition)
{
$data = [
'attributes' => $condition->config_data,
'text' => $condition->getText()
];
if ($condition->isCompound()) {
$data['joinText'] = $condition->getJoinText();
}
return $data;
}
public function setCacheConditionData($condition)
{
$cache = post('condition_data', []);
$cache[$condition->id] = json_encode($this->makeCacheConditionData($condition));
Request::merge([
'condition_data' => $cache
]);
}
public function restoreCacheConditionDataPayload()
{
Request::merge([
'condition_data' => json_decode(post('current_condition_data', []), true)
]);
}
public function getCacheConditionDataPayload()
{
return post('condition_data');
}
//
// Helpers
//
public function getParentIdFromCondition($condition)
{
if ($parentId = post('current_parent_id')) {
return $parentId;
}
$parentIds = post('condition_parent_id', []);
if (isset($parentIds[$condition->id])) {
return $parentIds[$condition->id];
}
}
/**
* Updates the primary rule conditions container
* @return array
*/
protected function renderConditions($currentCondition = null)
{
if ($currentCondition && $this->isRootCondition($currentCondition)) {
$condition = $currentCondition;
}
else {
$condition = $this->getConditionsRoot();
}
return [
'#'.$this->getId() => $this->makePartial('conditions', ['condition' => $condition])
];
}
protected function makeConditionFormWidget()
{
if ($this->conditionFormWidget !== null) {
return $this->conditionFormWidget;
}
if (!$model = $this->findConditionObj(null, false)) {
return null;
}
$config = $model->getFieldConfig();
$config->model = $model;
$config->alias = $this->alias . 'Form';
$config->arrayName = 'Condition';
$widget = $this->makeWidget('Backend\Widgets\Form', $config);
/*
* Set form values based on postback or cached attributes
*/
if (!$data = post('Condition')) {
$data = $this->getCacheConditionAttributes($model);
}
$widget->setFormValues($data);
/*
* Allow conditions to register their own widgets
*/
$model->onPreRender($this->controller, $this);
return $this->conditionFormWidget = $widget;
}
protected function findConditionObj($conditionId = null, $throw = true)
{
$conditionId = $conditionId ? $conditionId : post('current_condition_id');
$condition = null;
if (strlen($conditionId)) {
$condition = $this->getRelationModel()->find($conditionId);
}
if ($throw && !$condition) {
throw new ApplicationException('Condition not found');
}
return $condition;
}
}

View File

@ -0,0 +1,227 @@
.action-set {
margin-top: 10px;
}
.action-set ol {
margin: 0;
padding: 0;
list-style: none;
}
.action-set ol > li {
-webkit-transition: width 1s;
transition: width 1s;
}
.action-set ol > li > div {
font-size: 14px;
font-weight: normal;
position: relative;
}
.action-set ol > li > div > a,
.action-set ol > li > div div.content {
color: #2b3e50;
padding: 10px 45px 10px 91px;
display: block;
line-height: 150%;
text-decoration: none;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.action-set ol > li > div span.icon {
display: block;
position: absolute;
width: 22px;
height: 22px;
left: 20px;
top: 15px;
text-align: center;
font-weight: 500;
line-height: 22px;
color: #999;
}
.action-set ol > li > div span.icon > i {
line-height: 22px;
font-weight: normal;
font-size: 22px;
color: #999;
}
.action-set ol > li > div:not(.no-hover):hover,
.action-set ol > li > div.popover-highlight {
background-color: #4ea5e0 !important;
}
.action-set ol > li > div:not(.no-hover):hover span.icon > i,
.action-set ol > li > div.popover-highlight span.icon > i {
color: #fff !important;
}
.action-set ol > li > div:not(.no-hover):hover > a,
.action-set ol > li > div.popover-highlight > a {
color: #ffffff !important;
}
.action-set ol > li > div:not(.no-hover):hover:before,
.action-set ol > li > div.popover-highlight:before {
background-position: 0px -80px;
}
.action-set ol > li > div:not(.no-hover):hover:after,
.action-set ol > li > div.popover-highlight:after {
top: 0 !important;
bottom: 0 !important;
}
.action-set ol > li > div:not(.no-hover):hover span,
.action-set ol > li > div.popover-highlight span {
color: #ffffff !important;
}
.action-set ol > li > div:not(.no-hover):hover span.drag-handle,
.action-set ol > li > div.popover-highlight span.drag-handle {
cursor: move;
opacity: 1;
filter: alpha(opacity=100);
}
.action-set ol > li > div:not(.no-hover):active {
background-color: #3498db !important;
}
.action-set ol > li > div:not(.no-hover):active > a {
color: #ffffff !important;
}
.action-set ol > li > div span.icon:first-child {
font-size: 11px;
color: #ccc;
}
.action-set ol > li > div span.icon:first-child > i {
color: #ccc;
font-size: 15px;
}
.action-set ol > li > div span.icon:last-child {
left: 52px;
}
.action-set ol > li > div span.comment {
display: block;
font-weight: 400;
color: #95a5a6;
font-size: 13px;
margin-top: 2px;
overflow: hidden;
text-overflow: ellipsis;
}
.action-set ol > li > div > .subpanel {
right: 0;
top: 0;
height: 100%;
position: absolute;
z-index: 200;
padding: 0 20px;
}
.action-set ol > li > div > ul.submenu {
position: absolute;
right: 0;
top: 0;
padding: 0;
list-style: none;
z-index: 200;
height: 100%;
display: none;
margin: 0;
font-size: 0;
}
.action-set ol > li > div > ul.submenu li {
font-size: 12px;
height: 100%;
display: inline-block;
background: #2581b8;
border-right: 1px solid #328ec8;
}
.action-set ol > li > div > ul.submenu li p {
display: table;
height: 100%;
padding: 0;
margin: 0;
}
.action-set ol > li > div > ul.submenu li p a {
display: table-cell;
vertical-align: middle;
height: 100%;
padding: 0 20px;
font-size: 13px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
color: #ffffff;
text-decoration: none;
}
.action-set ol > li > div > ul.submenu li p a i.control-icon {
font-size: 22px;
}
.action-set ol > li > div:hover > ul.submenu {
display: block;
}
.action-set ol > li > div .checkbox {
position: absolute;
top: -2px;
right: 0;
}
.action-set ol > li > div .checkbox label {
margin-right: 0;
}
.action-set ol > li > div .checkbox label:before {
border-color: #cccccc;
}
.action-set ol > li > div.popover-highlight {
background-color: #4ea5e0 !important;
}
.action-set ol > li > div.popover-highlight:before {
background-position: 0px -80px;
}
.action-set ol > li > div.popover-highlight > a {
color: #ffffff !important;
cursor: default;
}
.action-set ol > li > div.popover-highlight span {
color: #ffffff !important;
}
.action-set ol > li > div.popover-highlight > ul.submenu,
.action-set ol > li > div.popover-highlight > span.drag-handle {
display: none !important;
}
.action-set ol > li.active > div {
background: #dddddd;
}
.action-set ol > li.active > div:after {
position: absolute;
width: 4px;
left: 0;
top: -1px;
bottom: -1px;
background: #e67e22;
display: block;
content: ' ';
}
.action-set ol > li.active > div > span.comment,
.action-set ol > li.active > div > span.expand {
color: #8f8f8f;
}
.action-set a.menu-control {
display: block;
margin: 20px;
padding: 13px 15px;
border: dotted 2px #ebebeb;
color: #bdc3c7;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
border-radius: 5px;
vertical-align: middle;
}
.action-set a.menu-control:hover,
.action-set a.menu-control:focus {
text-decoration: none;
background-color: #4ea5e0;
color: #ffffff;
border: none;
padding: 15px 17px;
}
.action-set a.menu-control:active {
background: #3498db;
color: #ffffff;
}
.action-set a.menu-control i {
margin-right: 10px;
font-size: 14px;
}

View File

@ -0,0 +1,156 @@
/*
* Global helpers
*/
function showActionSettings(id) {
var $control = $('[data-action-id='+id+']').closest('[data-control="ruleactions"]')
$control.ruleActions('onShowNewActionSettings', id)
}
/*
* Plugin definition
*/
+function ($) { "use strict";
var Base = $.oc.foundation.base,
BaseProto = Base.prototype
var RuleActions = function (element, options) {
this.$el = $(element)
this.options = options || {}
$.oc.foundation.controlUtils.markDisposable(element)
Base.call(this)
this.init()
}
RuleActions.prototype = Object.create(BaseProto)
RuleActions.prototype.constructor = RuleActions
RuleActions.prototype.init = function() {
this.$el.on('click', '[data-actions-settings]', this.proxy(this.onShowSettings))
this.$el.on('click', '[data-actions-delete]', this.proxy(this.onDeleteAction))
this.$el.one('dispose-control', this.proxy(this.dispose))
}
RuleActions.prototype.dispose = function() {
this.$el.off('click', '[data-actions-settings]', this.proxy(this.onShowSettings))
this.$el.off('click', '[data-actions-delete]', this.proxy(this.onDeleteAction))
this.$el.off('dispose-control', this.proxy(this.dispose))
this.$el.removeData('oc.ruleActions')
this.$el = null
// In some cases options could contain callbacks,
// so it's better to clean them up too.
this.options = null
BaseProto.dispose.call(this)
}
RuleActions.prototype.onDeleteAction = function(event) {
var $el = $(event.target),
actionId = getActionIdFromElement($el)
$el.request(this.options.deleteHandler, {
data: { current_action_id: actionId },
confirm: 'Do you really want to delete this action?'
})
}
RuleActions.prototype.onShowNewActionSettings = function(actionId) {
var $el = $('[data-action-id='+actionId+']')
var popup_size = 'giant';
// Action does not use settings
if ($el.hasClass('no-form')) {
// Popup will contain scheduling option only
popup_size = 'medium'
}
$el.popup({
handler: this.options.settingsHandler,
extraData: { current_action_id: actionId },
size: popup_size
})
// This will not fire on successful save because the target element
// is replaced by the time the popup loader has finished to call it
$el.one('hide.oc.popup', this.proxy(this.onCancelAction))
}
RuleActions.prototype.onCancelAction = function(event) {
var $el = $(event.target),
actionId = getActionIdFromElement($el)
$el.request(this.options.cancelHandler, {
data: { new_action_id: actionId }
})
return false
}
RuleActions.prototype.onShowSettings = function(event) {
var $el = $(event.target),
actionId = getActionIdFromElement($el)
var popup_size = 'giant';
// Action does not use settings
if ($el.closest('li.action-item').hasClass('no-form')) {
popup_size = 'medium'
}
$el.popup({
handler: this.options.settingsHandler,
extraData: { current_action_id: actionId },
size: popup_size
})
return false
}
function getActionIdFromElement($el) {
var $item = $el.closest('li.action-item')
return $item.data('action-id')
}
RuleActions.DEFAULTS = {
settingsHandler: null,
deleteHandler: null,
cancelHandler: null,
createHandler: null
}
// PLUGIN DEFINITION
// ============================
var old = $.fn.ruleActions
$.fn.ruleActions = function (option) {
var args = Array.prototype.slice.call(arguments, 1), items, result
items = this.each(function () {
var $this = $(this)
var data = $this.data('oc.ruleActions')
var options = $.extend({}, RuleActions.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data) $this.data('oc.ruleActions', (data = new RuleActions(this, options)))
if (typeof option == 'string') result = data[option].apply(data, args)
if (typeof result != 'undefined') return false
})
return result ? result : items
}
$.fn.ruleActions.Constructor = RuleActions
$.fn.ruleActions.noConflict = function () {
$.fn.ruleActions = old
return this
}
// Add this only if required
$(document).render(function (){
$('[data-control="ruleactions"]').ruleActions()
})
}(window.jQuery);

View File

@ -0,0 +1,273 @@
@import "../../../../../../../modules/backend/assets/less/core/boot.less";
//
// Actions
// --------------------------------------------------
@color-actions-item-bg: #ffffff;
@color-actions-item-title: #2b3e50;
@color-actions-item-comment: #95a5a6;
@color-actions-control: #bdc3c7;
@color-actions-hover-bg: @highlight-hover-bg;
@color-actions-hover-text: @highlight-hover-text;
@color-actions-active-bg: @highlight-active-bg;
@color-actions-active-text: @highlight-active-text;
@color-actions-item-active-comment: #8f8f8f;
@color-actions-submenu-text: #ffffff;
@color-actions-light-submenu-bg: #2581b8;
@color-actions-light-submenu-border: #328ec8;
.action-set {
margin-top: 10px;
ol {
margin: 0;
padding: 0;
list-style: none;
> li {
.transition(width 1s);
> div {
font-size: @font-size-base;
font-weight: normal;
position: relative;
> a, div.content {
color: @color-actions-item-title;
padding: 10px 45px 10px 91px;
display: block;
line-height: 150%;
text-decoration: none;
.box-sizing(border-box);
}
span.icon {
display: block;
position: absolute;
width: 22px;
height: 22px;
left: 20px;
top: 15px;
text-align: center;
font-weight: 500;
line-height: 22px;
color: #999;
> i {
line-height: 22px;
font-weight: normal;
font-size: 22px;
color: #999;
}
}
&:not(.no-hover):hover, &.popover-highlight {
background-color: @color-actions-hover-bg !important;
span.icon > i {
color: #fff !important;
}
> a {
color: @color-actions-hover-text !important;
}
&:before {
background-position: 0px -80px;
}
&:after {
top: 0 !important;
bottom: 0 !important;
}
span {
color: @color-actions-hover-text !important;
&.drag-handle {
cursor: move;
.opacity(1);
}
}
}
&:not(.no-hover):active {
background-color: @color-actions-active-bg !important;
> a {
color: @color-actions-active-text !important;
}
}
span.icon:first-child {
font-size: 11px;
color: #ccc;
> i {
color: #ccc;
font-size: 15px;
}
}
span.icon:last-child {
left: 52px;
}
span.comment {
display: block;
font-weight: 400;
color: @color-actions-item-comment;
font-size: @font-size-base - 1;
margin-top: 2px;
overflow: hidden;
text-overflow: ellipsis;
}
> .subpanel {
right: 0;
top: 0;
height: 100%;
position: absolute;
z-index: 200;
padding: 0 20px;
}
> ul.submenu {
position: absolute;
right: 0;
top: 0;
padding: 0;
list-style: none;
z-index: 200;
height: 100%;
display: none;
margin: 0;
font-size: 0;
li {
font-size: @font-size-base - 2;
height: 100%;
display: inline-block;
background: @color-actions-light-submenu-bg;
border-right: 1px solid @color-actions-light-submenu-border;
p {
display: table;
height: 100%;
padding: 0;
margin: 0;
a {
display: table-cell;
vertical-align: middle;
height: 100%;
padding: 0 20px;
font-size: @font-size-base - 1;
.box-sizing(border-box);
color: @color-actions-submenu-text;
text-decoration: none;
i.control-icon {
font-size: 22px;
}
}
}
}
}
&:hover {
> ul.submenu {
display: block;
}
}
.checkbox {
position: absolute;
top: -2px;
right: 0;
label {
margin-right: 0;
&:before {
border-color: @color-filelist-cb-border;
}
}
}
&.popover-highlight {
background-color: @color-actions-hover-bg !important;
&:before {
background-position: 0px -80px;
}
> a {
color: @color-actions-hover-text !important;
cursor: default;
}
span {
color: @color-actions-hover-text !important;
}
> ul.submenu, > span.drag-handle {
display: none !important;
}
}
}
&.active {
> div {
background: @color-list-active;
&:after {
position: absolute;
width: 4px;
left: 0;
top: -1px;
bottom: -1px;
background: @color-list-active-border;
display: block;
content: ' ';
}
> span.comment, > span.expand {
color: @color-actions-item-active-comment;
}
}
}
}
}
a.menu-control {
display: block;
margin: 20px;
padding: 13px 15px;
border: dotted 2px #ebebeb;
color: #bdc3c7;
font-size: @font-size-base - 2;
font-weight: 600;
text-transform: uppercase;
border-radius: 5px;
vertical-align: middle;
&:hover, &:focus {
text-decoration: none;
background-color: @color-actions-hover-bg;
color: @color-actions-hover-text;
border: none;
padding: 15px 17px;
}
&:active {
background: @color-actions-active-bg;
color: @color-actions-active-text;
}
i {
margin-right: 10px;
font-size: 14px;
}
}
}

View File

@ -0,0 +1,29 @@
<li
id="<?= 'action'.$action->id ?>"
data-action-id="<?= $action->id ?>"
class="action-item <?= $action->hasFieldConfig() ? '' : 'no-form' ?>">
<div>
<div class="icons">
<span class="icon"><i class="icon-bolt"></i></span>
<span class="icon"><i class="<?= e($action->getActionIcon()) ?>"></i></span>
</div>
<a href="javascript:;" data-actions-settings>
<span class="title"><?= e(__($this->getCacheActionTitle($action))) ?></span>
<span class="comment"><?= e(__($this->getCacheActionText($action))) ?></span>
</a>
<ul class="submenu">
<li>
<p>
<a href="javascript:;" data-actions-delete>
<i class="icon-trash-o control-icon"></i>
</a>
</p>
</li>
</ul>
</div>
<input
type="hidden"
name="action_data[<?= $action->id ?>]"
value="<?= e(json_encode($this->getCacheActionData($action))) ?>" />
</li>

View File

@ -0,0 +1,77 @@
<?= Form::open(['id' => 'propertyForm']) ?>
<div class="modal-header flex-row-reverse">
<button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title">
<?php if (!$this->fatalError): ?>
<?= e(__($action->getTitle())) ?>
<?php else: ?>
<?= e(__('Action')) ?>
<?php endif ?>
</h4>
</div>
<?php if (!$this->fatalError): ?>
<input type="hidden" name="current_action_id" value="<?= $action->id ?>" />
<input type="hidden" name="current_action_data" value="<?= e(json_encode($this->getCacheActionDataPayload())) ?>" />
<div class="modal-body">
<?php if ($actionFormWidget): ?>
<div class="row">
<div class="col-md-8">
<?= $actionFormWidget->render() ?>
</div>
<div class="col-md-4">
<?= $this->makePartial('available_tags') ?>
<?= $this->makePartial('schedule') ?>
</div>
</div>
<?php else: ?>
<?= $this->makePartial('schedule') ?>
<?php endif ?>
</div>
<div class="modal-footer">
<button
class="btn btn-primary"
data-request="<?= $this->getEventHandler('onSaveAction') ?>"
data-hotkey="ctrl+s, cmd+s"
data-popup-load-indicator
class="btn btn-primary">
<?= e(trans('backend::lang.form.save')) ?>
</button>
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
<?= e(trans('backend::lang.form.cancel')) ?>
</button>
<button
type="button"
class="oc-icon-trash-o btn-icon danger pull-right"
data-request="<?= $this->getEventHandler('onDeleteAction') ?>"
data-popup-load-indicator
data-request-confirm="<?= e(__('Do you really want to delete this action?')) ?>">
</button>
</div>
<?php else: ?>
<div class="modal-body">
<p class="flash-message static error"><?= e(trans($this->fatalError)) ?></p>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
<?= e(trans('backend::lang.form.close')) ?>
</button>
</div>
<?php endif ?>
<?php Form::close() ?>

View File

@ -0,0 +1,31 @@
<div class="action-set">
<ol>
<li>
<div class="no-hover">
<div class="icons">
<span class="icon"><?= e(__('ON')) ?></span>
<span class="icon"><i class="icon-user"></i></span>
</div>
<div class="content">
<span class="title"><?= e($formModel->getEventName()) ?></span>
<span class="comment"><?= e($formModel->getEventDescription()) ?></span>
</div>
</div>
</li>
<?php foreach ($actions as $action): ?>
<?= $this->makePartial('action', ['action' => $action]) ?>
<?php endforeach ?>
</ol>
<a
href="javascript:;"
data-control="popup"
data-handler="<?= $this->getEventHandler('onLoadCreateActionForm') ?>"
class="menu-control">
<i class="icon-plus"></i> <?= e(__('Add Action')) ?>
</a>
</div>
<?php if (isset($newActionId)): ?>
<script> showActionSettings('<?= e($newActionId) ?>') </script>
<?php endif ?>

View File

@ -0,0 +1,14 @@
<div class="actions-container">
<div
id="<?= $this->getId() ?>"
class="control-ruleactions"
data-settings-handler="<?= $this->getEventHandler('onLoadActionSetup') ?>"
data-delete-handler="<?= $this->getEventHandler('onDeleteAction') ?>"
data-cancel-handler="<?= $this->getEventHandler('onCancelActionSettings') ?>"
data-create-handler="<?= $this->getEventHandler('onLoadCreateChildAction') ?>"
data-control="ruleactions">
<?= $this->makePartial('actions', ['actions' => $actions]) ?>
</div>
<input type="hidden" name="<?= $name ?>" value="" />
</div>

View File

@ -0,0 +1,23 @@
<?php if ($availableTags): ?>
<div class="form-preview">
<p><?= e(__('These variables are available')) ?></p>
<div class="control-balloon-selector">
<ul>
<?php foreach ($availableTags as $tag => $description): ?>
<li
title="<?= $description ?>"
data-control="dragvalue"
data-drag-click="true"
data-text-value="{{ <?= $tag ?> }}">
{{ <?= $tag ?> }}
</li>
<?php endforeach ?>
</ul>
</div>
<div class="small"><em><?= e(__('Click or drag these in to the content area')) ?></em></div>
</div>
<?php else: ?>
<div class="form-preview">
<div><?= e(__('This action does not provide any variables.')) ?></div>
</div>
<?php endif ?>

View File

@ -0,0 +1,46 @@
<?= Form::open(['id' => 'addRuleActionForm']) ?>
<input type="hidden" name="current_action_data" value="<?= e(json_encode($this->getCacheActionDataPayload())) ?>" />
<div class="modal-header flex-row-reverse">
<button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title"><?= e(__('Add Notification Action')) ?></h4>
</div>
<div class="modal-body">
<?php if ($this->fatalError): ?>
<p class="flash-message static error"><?= $fatalError ?></p>
<?php else: ?>
<div class="control-simplelist is-selectable is-scrollable size-large" data-control="simplelist">
<ul>
<?php foreach ($actions as $class => $action): ?>
<li>
<a
href="javascript:;"
data-request="<?= $this->getEventHandler('onCreateAction') ?>"
data-request-data="action_class: '<?= addslashes($class) ?>'"
data-popup-load-indicator>
<h5 class="heading"><?= e(trans($action->getActionName())) ?></h5>
<p class="description"><?= e(trans($action->getActionDescription())) ?></p>
</a>
</li>
<?php endforeach ?>
</ul>
</div>
<?php endif ?>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
<?= e(trans('backend::lang.form.cancel')) ?>
</button>
</div>
<?= Form::close() ?>

View File

@ -0,0 +1,22 @@
<div class="form-preview">
<p>
<i class="icon-clock-o"></i>
<?= e(__('Schedule')) ?>
</p>
<p>
<?= $actionScheduleFormWidget->render() ?>
</p>
<div class="small">
<em>
<?php if (in_array($queueDriver, ['sync', 'sqs'])): ?>
<i class="icon-warning"></i>
<?= e(__('The configured queue driver does not support delayed execution.')) ?>
<?php else: ?>
<?= e(__('Note that scheduling might not work for certain queue driver configurations.')) ?>
<?php endif ?>
</em>
<a target="_blank" href="https://octobercms.com/docs/services/queues#basic-usage">
<?= e(__('Learn more')) ?>
</a>
</div>
</div>

View File

@ -0,0 +1,227 @@
ul.condition-set {
padding: 5px 0 0;
margin: 0;
list-style: none;
}
ul.condition-set ul {
padding: 0;
margin: 0;
list-style: none;
}
ul.condition-set li {
margin: 0;
padding: 0;
}
ul.condition-set .condition-item {
border-radius: 4px;
}
ul.condition-set .condition-item.is-root {
border-radius: 0;
}
ul.condition-set .condition-item.collapsed .compound-content {
border-left-color: transparent !important;
}
ul.condition-set .condition-item.collapsed .condition-set {
display: none;
}
ul.condition-set .condition-item.collapsed.is-inner .compound-add-item {
display: none;
}
ul.condition-set .condition-item .condition-delete {
position: absolute;
display: block;
width: 10px;
height: 10px;
right: 7px;
top: 7px;
}
ul.condition-set a.condition-collapse {
text-decoration: none;
display: block;
position: absolute;
right: 8px;
top: 2px;
}
ul.condition-set a.condition-collapse > i {
-webkit-transition: transform 0.3s;
transition: transform 0.3s;
width: 15px;
height: 15px;
color: #bdc3c7;
font-size: 12px;
display: block;
}
ul.condition-set a.condition-collapse:hover > i {
color: #999;
}
ul.condition-set li.collapsed a.condition-collapse > i {
-webkit-transform: scale(1, -1);
-ms-transform: scale(1, -1);
transform: scale(1, -1);
}
ul.condition-set .compound-join {
text-align: center;
padding: 3px 0 2px 0;
}
ul.condition-set .compound-join a {
color: #333333;
text-decoration: none;
}
ul.condition-set .compound-join a:hover {
text-decoration: underline;
}
ul.condition-set .condition-item.is-single {
padding: 8px 20px 9px 24px;
background: #fff;
border: 1px solid #eee;
position: relative;
}
ul.condition-set .condition-item.is-single:before {
font-family: FontAwesome;
font-weight: normal;
font-style: normal;
text-decoration: inherit;
-webkit-font-smoothing: antialiased;
*margin-right: .3em;
content: "\f126";
line-height: 100%;
font-size: 16px;
color: #bdc3c7;
position: absolute;
width: 15px;
height: 15px;
left: 12px;
top: 11px;
}
ul.condition-set .condition-item.is-single .condition-text {
padding-left: 10px;
}
ul.condition-set .condition-item.is-single .condition-text a {
color: #333333;
text-decoration: none;
}
ul.condition-set .condition-item.is-single .condition-text a:hover {
text-decoration: underline;
}
ul.condition-set .condition-item.is-single .condition-text a span.operator {
font-weight: 500;
}
ul.condition-set .condition-item.is-single a.condition-delete {
right: 12px;
top: 9px;
}
ul.condition-set .condition-item.is-compound .compound-content {
position: relative;
border-left: 1px solid #dbdee0;
margin-left: 5px;
}
ul.condition-set .condition-item.is-compound .compound-content:before {
color: #bdc3c7;
font-family: FontAwesome;
font-weight: normal;
font-style: normal;
text-decoration: inherit;
-webkit-font-smoothing: antialiased;
*margin-right: .3em;
content: "\f111";
font-size: 8px;
position: absolute;
left: -4px;
top: -2px;
}
ul.condition-set .condition-item.is-compound .compound-content h4 {
padding: 0 30px 5px 5px;
margin: 0;
position: relative;
top: -5px;
font-size: 13px;
line-height: 130%;
font-weight: 400;
color: #666;
}
ul.condition-set .condition-item.is-compound .compound-content h4 a.condition-text {
color: #333333;
padding-left: 10px;
}
ul.condition-set .condition-item.is-compound .compound-content h4 a.condition-text:hover {
text-decoration: underline;
}
ul.condition-set .condition-item.is-compound .compound-content .condition-set {
padding-left: 15px;
}
ul.condition-set .condition-item.is-compound .compound-add-item {
position: relative;
margin-top: 10px;
margin-left: 20px;
border: 2px dotted #e0e0e0;
border-radius: 5px;
}
ul.condition-set .condition-item.is-compound .compound-add-item:before {
color: #bdc3c7;
font-family: FontAwesome;
font-weight: normal;
font-style: normal;
text-decoration: inherit;
-webkit-font-smoothing: antialiased;
*margin-right: .3em;
content: "\f067";
font-size: 16px;
position: absolute;
left: -23px;
top: -11px;
}
ul.condition-set .condition-item.is-compound .compound-add-item > a {
color: #bdc3c7;
text-align: center;
display: block;
text-decoration: none;
padding: 13px 15px;
text-transform: uppercase;
font-weight: 600;
font-size: 12px;
}
ul.condition-set .condition-item.is-compound .compound-add-item:hover,
ul.condition-set .condition-item.is-compound .compound-add-item:focus {
background-color: #4ea5e0;
border-color: #4ea5e0;
}
ul.condition-set .condition-item.is-compound .compound-add-item:hover:before,
ul.condition-set .condition-item.is-compound .compound-add-item:focus:before {
color: #999;
}
ul.condition-set .condition-item.is-compound .compound-add-item:hover > a,
ul.condition-set .condition-item.is-compound .compound-add-item:focus > a {
color: #ffffff;
}
ul.condition-set .condition-item.is-compound .compound-add-item:active {
background: #3498db;
border-color: #3498db;
}
ul.condition-set .condition-item.is-compound .compound-add-item:active > a {
color: #ffffff;
}
ul.condition-set .condition-item.is-compound.is-inner {
padding-right: 20px;
}
ul.condition-set .condition-item.is-compound.is-inner a.condition-delete-compound {
right: -7px;
top: 0;
}
.condition-multi-value .condition-filter-search {
background-position: right -81px !important;
border-top: none !important;
border-left: none !important;
border-right: none !important;
border-radius: 0;
padding-left: 20px;
}
.condition-multi-value .filter-list {
margin: 0 -20px;
}
.condition-multi-value .filter-list .list-footer {
padding-top: 0;
padding-bottom: 0;
}
.condition-multi-value .added-filter-list {
margin: 0 -20px;
}

View File

@ -0,0 +1,184 @@
/*
* Global helpers
*/
function showConditionSettings(id) {
var $control = $('[data-condition-id='+id+']').closest('[data-control="ruleconditions"]')
$control.ruleConditions('onShowNewConditionSettings', id)
}
/*
* Plugin definition
*/
+function ($) { "use strict";
var Base = $.oc.foundation.base,
BaseProto = Base.prototype
var RuleConditions = function (element, options) {
this.$el = $(element)
this.options = options || {}
$.oc.foundation.controlUtils.markDisposable(element)
Base.call(this)
this.init()
}
RuleConditions.prototype = Object.create(BaseProto)
RuleConditions.prototype.constructor = RuleConditions
RuleConditions.prototype.init = function() {
this.$el.on('click', '[data-conditions-collapse]', this.proxy(this.onConditionsToggle))
this.$el.on('click', '[data-conditions-settings]', this.proxy(this.onShowSettings))
this.$el.on('click', '[data-conditions-create]', this.proxy(this.onCreateChildCondition))
this.$el.on('click', '[data-conditions-delete]', this.proxy(this.onDeleteCondition))
this.$el.one('dispose-control', this.proxy(this.dispose))
}
RuleConditions.prototype.dispose = function() {
this.$el.off('click', '[data-conditions-collapse]', this.proxy(this.onConditionsToggle))
this.$el.off('click', '[data-conditions-settings]', this.proxy(this.onShowSettings))
this.$el.off('click', '[data-conditions-create]', this.proxy(this.onCreateChildCondition))
this.$el.off('click', '[data-conditions-delete]', this.proxy(this.onDeleteCondition))
this.$el.off('dispose-control', this.proxy(this.dispose))
this.$el.removeData('oc.ruleConditions')
this.$el = null
// In some cases options could contain callbacks,
// so it's better to clean them up too.
this.options = null
BaseProto.dispose.call(this)
}
RuleConditions.prototype.onDeleteCondition = function(event) {
var $el = $(event.target),
conditionId = getConditionIdFromElement($el)
$el.request(this.options.deleteHandler, {
data: { current_condition_id: conditionId },
confirm: 'Do you really want to delete this condition?'
})
}
RuleConditions.prototype.onCreateChildCondition = function(event) {
var $el = $(event.target),
conditionId = getConditionIdFromElement($el)
$el.popup({
handler: this.options.createHandler,
extraData: { current_condition_id: conditionId },
size: 'large'
})
return false
}
RuleConditions.prototype.onConditionsToggle = function(event) {
var $el = $(event.target),
$item = $el.closest('li'),
newStatusValue = $item.hasClass('collapsed') ? 0 : 1,
conditionId = getConditionIdFromElement($el)
$el.request(this.options.collapseHandler, {
data: {
status: newStatusValue,
group: conditionId
}
})
if (newStatusValue) {
$el.parents('li:first').addClass('collapsed');
}
else {
$el.parents('li:first').removeClass('collapsed');
}
return false
}
RuleConditions.prototype.onShowNewConditionSettings = function(conditionId) {
var $el = $('[data-condition-id='+conditionId+']')
$el.popup({
handler: this.options.settingsHandler,
extraData: { current_condition_id: conditionId },
size: 'large'
})
// This will not fire on successful save because the target element
// is replaced by the time the popup loader has finished to call it
$el.one('hide.oc.popup', this.proxy(this.onCancelCondition))
}
RuleConditions.prototype.onCancelCondition = function(event) {
var $el = $(event.target),
conditionId = getConditionIdFromElement($el)
$el.request(this.options.cancelHandler, {
data: { new_condition_id: conditionId }
})
return false
}
RuleConditions.prototype.onShowSettings = function(event) {
var $el = $(event.target),
conditionId = getConditionIdFromElement($el)
$el.popup({
handler: this.options.settingsHandler,
extraData: { current_condition_id: conditionId },
size: 'large'
})
return false
}
function getConditionIdFromElement($el) {
var $item = $el.closest('li.condition-item')
return $item.data('condition-id')
}
RuleConditions.DEFAULTS = {
collapseHandler: null,
settingsHandler: null,
deleteHandler: null,
cancelHandler: null,
createHandler: null
}
// PLUGIN DEFINITION
// ============================
var old = $.fn.ruleConditions
$.fn.ruleConditions = function (option) {
var args = Array.prototype.slice.call(arguments, 1), items, result
items = this.each(function () {
var $this = $(this)
var data = $this.data('oc.ruleConditions')
var options = $.extend({}, RuleConditions.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data) $this.data('oc.ruleConditions', (data = new RuleConditions(this, options)))
if (typeof option == 'string') result = data[option].apply(data, args)
if (typeof result != 'undefined') return false
})
return result ? result : items
}
$.fn.ruleConditions.Constructor = RuleConditions
$.fn.ruleConditions.noConflict = function () {
$.fn.ruleConditions = old
return this
}
// Add this only if required
$(document).render(function (){
$('[data-control="ruleconditions"]').ruleConditions()
})
}(window.jQuery);

View File

@ -0,0 +1,139 @@
/*
* Plugin definition
*/
+function ($) { "use strict";
var Base = $.oc.foundation.base,
BaseProto = Base.prototype
var ConditionMultiValue = function (element, options) {
this.$el = $(element)
this.options = options || {}
this.$template = $('[data-record-template]', this.$el)
this.$emptyTemplate = $('[data-empty-record-template]', this.$el)
this.$addedRecords = $('[data-added-records]', this.$el)
$.oc.foundation.controlUtils.markDisposable(element)
Base.call(this)
this.init()
this.recompileData()
}
ConditionMultiValue.prototype = Object.create(BaseProto)
ConditionMultiValue.prototype.constructor = ConditionMultiValue
ConditionMultiValue.prototype.init = function() {
this.$el.on('click', '[data-multivalue-add-record]', this.proxy(this.onAddRecord))
this.$el.on('click', '[data-multivalue-remove-record]', this.proxy(this.onRemoveRecord))
this.$el.one('dispose-control', this.proxy(this.dispose))
}
ConditionMultiValue.prototype.dispose = function() {
this.$el.off('click', '[data-multivalue-add-record]', this.proxy(this.onAddRecord))
this.$el.off('click', '[data-multivalue-remove-record]', this.proxy(this.onRemoveRecord))
this.$el.off('dispose-control', this.proxy(this.dispose))
this.$el.removeData('oc.conditionMultiValue')
this.$el = null
// In some cases options could contain callbacks,
// so it's better to clean them up too.
this.options = null
BaseProto.dispose.call(this)
}
ConditionMultiValue.prototype.onAddRecord = function(event) {
var $el = $(event.target),
recordId = $el.closest('[data-record-key]').data('record-key'),
recordValue = $el.closest('a').data('record-value')
if (!!$('[data-record-key='+recordId+']', this.$addedRecords).length) {
return
}
this.$addedRecords.append(this.renderTemplate({
key: recordId,
value: recordValue
}))
this.recompileData()
}
ConditionMultiValue.prototype.onRemoveRecord = function(event) {
var $el = $(event.target),
recordId = $el.closest('[data-record-key]').data('record-key')
$('[data-record-key='+recordId+']', this.$addedRecords).remove()
this.recompileData()
}
ConditionMultiValue.prototype.recompileData = function(params) {
var $recordElements = $('[data-record-key]', this.$addedRecords),
hasData = !!$recordElements.length
if (hasData) {
$('[data-no-record-data]', this.$addedRecords).remove()
}
else {
this.$addedRecords.append(this.renderEmptyTemplate())
}
if (this.options.dataLocker) {
var $locker = $(this.options.dataLocker),
selectedIds = []
$recordElements.each(function(key, record) {
selectedIds.push($(record).data('record-key'))
})
$locker.val(selectedIds.join(','))
}
}
ConditionMultiValue.prototype.renderTemplate = function(params) {
return Mustache.render(this.$template.html(), params)
}
ConditionMultiValue.prototype.renderEmptyTemplate = function() {
return this.$emptyTemplate.html()
}
ConditionMultiValue.DEFAULTS = {
dataLocker: null
}
// PLUGIN DEFINITION
// ============================
var old = $.fn.conditionMultiValue
$.fn.conditionMultiValue = function (option) {
var args = Array.prototype.slice.call(arguments, 1), items, result
items = this.each(function () {
var $this = $(this)
var data = $this.data('oc.conditionMultiValue')
var options = $.extend({}, ConditionMultiValue.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data) $this.data('oc.conditionMultiValue', (data = new ConditionMultiValue(this, options)))
if (typeof option == 'string') result = data[option].apply(data, args)
if (typeof result != 'undefined') return false
})
return result ? result : items
}
$.fn.conditionMultiValue.Constructor = ConditionMultiValue
$.fn.conditionMultiValue.noConflict = function () {
$.fn.conditionMultiValue = old
return this
}
// Add this only if required
$(document).render(function (){
$('[data-control="condition-multivalue"]').conditionMultiValue()
})
}(window.jQuery);

View File

@ -0,0 +1,286 @@
@import "../../../../../../../modules/backend/assets/less/core/boot.less";
//
// Conditions
// --------------------------------------------------
ul.condition-set {
padding: 5px 0 0;
margin: 0;
list-style: none;
ul {
padding: 0;
margin: 0;
list-style: none;
}
li {
margin: 0;
padding: 0;
}
.condition-item {
border-radius: 4px;
&.is-root {
border-radius: 0;
}
&.is-inner {}
&.collapsed {
.compound-content {
border-left-color: transparent !important;
}
.condition-set {
display: none;
}
&.is-inner .compound-add-item {
display: none;
}
}
.condition-delete {
position: absolute;
display: block;
width: 10px;
height: 10px;
right: 7px;
top: 7px;
}
}
//
// Collapse
//
a.condition-collapse {
text-decoration: none;
display: block;
position: absolute;
right: 8px;
top: 2px;
> i {
.transition(~'transform 0.3s');
width: 15px;
height: 15px;
color: #bdc3c7;
font-size: 12px;
display: block;
}
&:hover {
> i {
color: #999;
}
}
}
li.collapsed a.condition-collapse {
> i {
.transform(scale(1,-1));
}
}
//
// Joins
//
.compound-join {
text-align: center;
padding: 3px 0 2px 0;
a {
color: #333333;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
//
// Single
//
.condition-item.is-single {
padding: 8px 20px 9px 24px;
background: #fff;
border: 1px solid #eee;
position: relative;
&:before {
.icon(@code-fork);
line-height: 100%;
font-size: 16px;
color: #bdc3c7;
position: absolute;
width: 15px;
height: 15px;
left: 12px;
top: 11px;
}
.condition-text {
padding-left: 10px;
a {
color: #333333;
text-decoration: none;
&:hover {
text-decoration: underline;
}
span.operator {
font-weight: 500;
}
}
}
a.condition-delete {
right: 12px;
top: 9px;
}
}
//
// Compound
//
.condition-item.is-compound {
.compound-content {
position: relative;
border-left: 1px solid #dbdee0;
margin-left: 5px;
&:before {
color: #bdc3c7;
.icon(@circle);
font-size: 8px;
position: absolute;
left: -4px;
top: -2px;
}
h4 {
padding: 0 30px 5px 5px;
margin: 0;
position: relative;
top: -5px;
font-size: 13px;
line-height: 130%;
font-weight: 400;
color: #666;
a.condition-text {
color: #333333;
padding-left: 10px;
&:hover {
text-decoration: underline;
}
}
}
.condition-set {
padding-left: 15px;
}
}
.condition-item.is-compound {
}
.compound-add-item {
position: relative;
margin-top: 10px;
margin-left: 20px;
border: 2px dotted #e0e0e0;
border-radius: 5px;
&:before {
color: #bdc3c7;
.icon(@plus);
font-size: 16px;
position: absolute;
left: -23px;
top: -11px;
}
> a {
color: #bdc3c7;
text-align: center;
display: block;
text-decoration: none;
padding: 13px 15px;
text-transform: uppercase;
font-weight: 600;
font-size: @font-size-base - 2;
}
&:hover, &:focus {
background-color: @highlight-hover-bg;
border-color: @highlight-hover-bg;
&:before {
color: #999;
}
> a {
color: @highlight-hover-text;
}
}
&:active {
background: @highlight-active-bg;
border-color: @highlight-active-bg;
> a {
color: @highlight-active-text;
}
}
}
}
//
// Compound (Inner)
//
.condition-item.is-compound.is-inner {
padding-right: 20px;
a.condition-delete-compound {
right: -7px;
top: 0;
}
}
}
//
// Multi value
//
.condition-multi-value {
.condition-filter-search {
background-position: right -81px !important;
border-top: none !important;
border-left: none !important;
border-right: none !important;
border-radius: 0;
padding-left: 20px;
}
.filter-list {
margin: 0 -20px;
.list-footer {
padding-top: 0;
padding-bottom: 0;
}
}
.added-filter-list {
margin: 0 -20px;
}
}

View File

@ -0,0 +1,99 @@
<?php
$isCompound = $condition->isCompound();
$isRoot = $treeLevel == 0;
$collapsed = $this->getCollapseStatus($condition->id, false);
?>
<li
id="<?= 'condition'.$condition->id ?>"
data-condition-id="<?= $condition->id ?>"
class="condition-item <?= $isCompound ? 'is-compound' : 'is-single' ?> <?= $isRoot ? 'is-root' : 'is-inner' ?> <?= $collapsed ? 'collapsed' : null ?>">
<?php if ($isCompound): ?>
<div class="compound-content">
<h4>
<a
class="condition-collapse"
href="javascript:;"
title="Collapse / Expand"
data-conditions-collapse>
<i class="icon-chevron-up"></i>
</a>
<a
href="javascript:;"
class="condition-text"
data-conditions-settings>
<?= e($this->getCacheConditionText($condition)) ?>
</a>
<?php if (!$isRoot): ?>
<a
href="javascript:;"
title="<?= e(__('Delete Condition')) ?>"
class="condition-delete condition-delete-compound close"
data-conditions-delete>
<span aria-hidden="true">&times;</span>
</a>
<?php endif ?>
</h4>
<ul class="condition-set">
<?php
$children = $condition->children()->withDeferred($this->sessionKey.'_'.$condition->id)->get();
$lastIndex = $children->count() - 1;
?>
<?php foreach ($children as $index => $childCondition): ?>
<?= $this->makePartial('condition', [
'condition' => $childCondition,
'treeLevel' => $treeLevel + 1,
'parentCondition' => $condition
]) ?>
<?php if ($index < $lastIndex): ?>
<li class="compound-join">
<a href="javascript:;" data-conditions-settings>
<?= e($this->getCacheConditionJoinText($condition)) ?>
</a>
</li>
<?php endif ?>
<?php endforeach?>
</ul>
</div>
<div class="compound-add-item loading-indicator-container indicator-center">
<a
href="javascript:;"
data-conditions-create
data-request="onLoadConditionChildSelector">
<?= e(__('Add Condition')) ?>
</a>
</div>
<?php else: ?>
<div class="condition-text">
<a href="javascript:;" data-conditions-settings>
<?= $this->getCacheConditionText($condition) ?>
</a>
</div>
<a
href="javascript:;"
title="<?= e(__('Delete Condition')) ?>"
class="condition-delete close"
data-conditions-delete>
<span aria-hidden="true">&times;</span>
</a>
<?php endif ?>
<input
type="hidden"
name="condition_data[<?= $condition->id ?>]"
value="<?= e(json_encode($this->getCacheConditionData($condition))) ?>" />
<?php if (isset($parentCondition)): ?>
<input
type="hidden"
name="condition_parent_id[<?= $condition->id ?>]"
value="<?= $parentCondition->id ?>" />
<?php endif ?>
</li>

View File

@ -0,0 +1,68 @@
<?= Form::open(['id' => 'propertyForm']) ?>
<div class="modal-header flex-row-reverse">
<button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title">
<?php if (!$this->fatalError): ?>
<?= e($condition->getTitle()) ?>
<?php else: ?>
Condition
<?php endif ?>
</h4>
</div>
<?php if (!$this->fatalError): ?>
<input type="hidden" name="current_parent_id" value="<?= $this->getParentIdFromCondition($condition) ?>" />
<input type="hidden" name="current_condition_id" value="<?= $condition->id ?>" />
<input type="hidden" name="current_condition_data" value="<?= e(json_encode($this->getCacheConditionDataPayload())) ?>" />
<div class="modal-body">
<?= $conditionFormWidget->render() ?>
</div>
<div class="modal-footer">
<button
class="btn btn-primary"
data-request="<?= $this->getEventHandler('onSaveCondition') ?>"
data-hotkey="ctrl+s, cmd+s"
data-popup-load-indicator
class="btn btn-primary">
<?= e(trans('backend::lang.form.save')) ?>
</button>
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
<?= e(trans('backend::lang.form.cancel')) ?>
</button>
<?php if (!$this->isRootCondition($condition)): ?>
<button
type="button"
class="oc-icon-trash-o btn-icon danger pull-right"
data-request="<?= $this->getEventHandler('onDeleteCondition') ?>"
data-popup-load-indicator
data-request-confirm="<?= e(__('Do you really want to delete this condition?')) ?>">
</button>
<?php endif ?>
</div>
<?php else: ?>
<div class="modal-body">
<p class="flash-message static error"><?= e(trans($this->fatalError)) ?></p>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
<?= e(trans('backend::lang.form.close')) ?>
</button>
</div>
<?php endif ?>
<?php Form::close() ?>

View File

@ -0,0 +1,10 @@
<ul class="condition-set">
<?= $this->makePartial('condition', [
'condition' => $condition,
'treeLevel' => 0
]) ?>
</ul>
<?php if (isset($newConditionId)): ?>
<script> showConditionSettings('<?= e($newConditionId) ?>') </script>
<?php endif ?>

View File

@ -0,0 +1,15 @@
<div class="conditions-container">
<div
id="<?= $this->getId() ?>"
class="control-ruleconditions"
data-collapse-handler="<?= $this->getEventHandler('onSetCollapseStatus') ?>"
data-settings-handler="<?= $this->getEventHandler('onLoadConditionSetup') ?>"
data-delete-handler="<?= $this->getEventHandler('onDeleteCondition') ?>"
data-cancel-handler="<?= $this->getEventHandler('onCancelConditionSettings') ?>"
data-create-handler="<?= $this->getEventHandler('onLoadCreateChildCondition') ?>"
data-control="ruleconditions">
<?= $this->makePartial('conditions', ['condition' => $rootCondition]) ?>
</div>
<input type="hidden" name="<?= $name ?>" value="" />
</div>

View File

@ -0,0 +1,67 @@
<?= Form::open(['id' => 'propertyForm']) ?>
<div class="modal-header flex-row-reverse">
<button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title"><?= e(__('Condition')) ?></h4>
</div>
<?php if (!$this->fatalError): ?>
<input type="hidden" name="current_condition_id" value="<?= $condition->id ?>"/>
<input type="hidden" name="current_condition_data" value="<?= e(json_encode($this->getCacheConditionDataPayload())) ?>" />
<div class="modal-body">
<div class="form-group condition-class">
<label class="form-label"><?= e(__('Condition')) ?></label>
<select
class="form-control custom-select"
data-placeholder="<?= e(__('Please select a condition')) ?>"
name="condition_class">
<option value=""></option>
<?php foreach ($options as $name => $class): ?>
<?php if (!is_array($class)): ?>
<option value="<?= $class ?>"><?= e($name) ?></option>
<?php else: ?>
<optgroup label="<?= e($name) ?>">
<?php foreach ($class as $subName => $subClass): ?>
<option value="<?= $subClass ?>"><?= e($subName) ?></option>
<?php endforeach ?>
</optgroup>
<?php endif ?>
<?php endforeach ?>
</select>
</div>
</div>
<div class="modal-footer">
<button
class="btn btn-primary"
data-request="<?= $this->getEventHandler('onCreateCondition') ?>"
data-popup-load-indicator
class="btn btn-primary">
<?= e(trans('backend::lang.form.create')) ?>
</button>
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
<?= e(trans('backend::lang.form.cancel')) ?>
</button>
</div>
<?php else: ?>
<div class="modal-body">
<p class="flash-message static error"><?= e(trans($this->fatalError)) ?></p>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
<?= e(trans('backend::lang.form.close')) ?>
</button>
</div>
<?php endif ?>
<?= Form::close() ?>

View File

@ -0,0 +1,31 @@
<?php namespace RainLab\Notify\Interfaces;
/**
* This contract represents a notification action.
*/
interface Action
{
/**
* Returns a action text summary when displaying to the user.
* @return string
*/
public function getText();
/**
* Returns a action title for displaying in the action settings form.
* @return string
*/
public function getTitle();
/**
* Returns information about this action, including name and description.
*/
public function actionDetails();
/**
* Triggers this action.
* @param array $params
* @return void
*/
public function triggerAction($params);
}

View File

@ -0,0 +1,19 @@
<?php namespace RainLab\Notify\Interfaces;
/**
* This contract represents a Compound Condition rule.
*/
interface CompoundCondition
{
/**
* Returns the text to use when joining two rules within.
* @return string
*/
public function getJoinText();
/**
* Returns a list of condition types (`ConditionBase::TYPE_*` constants)
* that can be added to this compound condition
*/
public function getAllowedSubtypes();
}

View File

@ -0,0 +1,32 @@
<?php namespace RainLab\Notify\Interfaces;
/**
* This contract represents a Compound Condition rule.
*/
interface Condition
{
/**
* Returns a condition text summary when displaying to the user.
* @return string
*/
public function getText();
/**
* Returns a condition title for displaying in the condition settings form
* @return string
*/
public function getTitle();
/**
* Returns a list of options supported beneth this condition.
*
* Options can contain these keys:
* - ruleType: Rule type as specified by ConditionBase::TYPE_* constants
* - parentIds: An array of parent ids to constrain child conditions
* - extraRules: An array of additional condition classes to use
*
* @param array $options
* @return array
*/
public function getChildOptions(array $options);
}

View File

@ -0,0 +1,21 @@
<?php namespace RainLab\Notify\Interfaces;
/**
* This contract represents a notification event.
*/
interface Event
{
/**
* Returns information about this event, including name and description.
* @return array
*/
public function eventDetails();
/**
* Generates event parameters based on arguments from the triggering system event.
* @param array $args
* @param string $eventName
* @return void
*/
public static function makeParamsFromEvent(array $args, $eventName = null);
}

View File

@ -0,0 +1,61 @@
{
"Notify": "يخطر",
"Notification Services": "خدمات الإخطار",
"Notification Rules": "قواعد الإخطار",
"Manage the events and actions that trigger notifications.": "إدارة الأحداث والإجراءات التي تؤدي إلى تشغيل الإخطارات.",
"Name": "اسم",
"Code": "رمز",
"Notification Rule": "قاعدة الإخطار",
"Add Notification Rule": "أضف قاعدة الإخطار",
"Schedule": "برنامج",
"Note that scheduling might not work for certain queue driver configurations.": "لاحظ أن الجدولة قد لا تعمل مع تكوينات معينة لبرنامج تشغيل قائمة الانتظار.",
"Learn more": "يتعلم أكثر",
"The configured queue driver does not support delayed execution.": "لا يدعم برنامج تشغيل قائمة الانتظار المكون التنفيذ المؤجل.",
"Notifications management": "إدارة الإخطارات",
"Return to Notifications": "العودة إلى الإخطارات",
"Are you sure?": "هل أنت واثق؟",
"Action": "عمل",
"Action Description": "وصف الإجراء",
"Compound Condition": "الحالة المركبة",
"ALL of subconditions should be": "يجب أن تكون جميع الشروط الفرعية",
"ANY of subconditions should be": "يجب أن تكون أي من الشروط الفرعية",
"ON": "على",
"AND": "و",
"OR": "أو",
"ALL subconditions should meet the requirement": "يجب أن تفي جميع الشروط الفرعية بالمتطلبات",
"ANY subconditions should meet the requirement": "يجب أن تفي أي شروط فرعية بالمتطلبات",
"Condition Text": "نص الشرط",
"Condition": "شرط",
"Event": "هدف",
"Event description": "وصف الحدث",
"is": "يكون",
"is not": "ليس",
"equals or greater than": "يساوي أو أكبر من",
"equals or less than": "يساوي أو أقل من",
"contains": "يحتوي على",
"does not contain": "لا يحتوي",
"greater than": "أكثر من",
"less than": "أقل من",
"is one of": "هو واحد من",
"is not one of": "ليس واحدًا من",
"Unknown Attribute": "سمة غير معروفة",
"Unknown attribute selected": "تم تحديد سمة غير معروفة",
"Condition Type": "نوع الشرط",
"Required Value": "قيمة مطلوبة",
"Selected Records": "سجلات مختارة",
"No records added": "لا توجد سجلات مضافة",
"Attribute": "يصف",
"Operator": "المشغل أو العامل",
"Value": "قيمة",
"Delete this notification rule?": "هل تريد حذف قاعدة الإخطار هذه؟",
"New Notification Rule": "قاعدة الإخطار الجديدة",
"Do you really want to delete this action?": "هل تريد حقًا حذف هذا الإجراء؟",
"Add Action": "أضف إجراء",
"These variables are available": "هذه المتغيرات متوفرة",
"Click or drag these in to the content area": "انقر أو اسحب هذه إلى منطقة المحتوى",
"This action does not provide any variables.": "لا يوفر هذا الإجراء أي متغيرات.",
"Delete Condition": "حذف الشرط",
"Add Condition": "أضف شرط",
"Do you really want to delete this condition?": "هل تريد حقا حذف هذا الشرط؟",
"Please select a condition": "الرجاء تحديد الشرط"
}

View File

@ -0,0 +1,61 @@
{
"Notify": "Паведаміць",
"Notification Services": "Паслугі апавяшчэнняў",
"Notification Rules": "Правілы паведамлення",
"Manage the events and actions that trigger notifications.": "Кіруйце падзеямі і дзеяннямі, якія выклікаюць апавяшчэнні.",
"Name": "Імя",
"Code": "Код",
"Notification Rule": "Правіла паведамлення",
"Add Notification Rule": "Дадаць правіла апавяшчэнняў",
"Schedule": "Расклад",
"Note that scheduling might not work for certain queue driver configurations.": "Звярніце ўвагу, што планаванне можа не працаваць для некаторых канфігурацый драйвера чаргі.",
"Learn more": "Даведайцеся больш",
"The configured queue driver does not support delayed execution.": "Настроены драйвер чаргі не падтрымлівае адкладзенае выкананне.",
"Notifications management": "Кіраванне апавяшчэннямі",
"Return to Notifications": "Вярнуцца да апавяшчэнняў",
"Are you sure?": "Вы ўпэўнены?",
"Action": "Дзеянне",
"Action Description": "Апісанне дзеяння",
"Compound Condition": "Складаны стан",
"ALL of subconditions should be": "УСЕ падумовы павінны быць",
"ANY of subconditions should be": "ЛЮБОЕ з падумоў павінна быць",
"ON": "ВКЛ",
"AND": "І",
"OR": "АБО",
"ALL subconditions should meet the requirement": "УСЕ падумовы павінны адпавядаць патрабаванню",
"ANY subconditions should meet the requirement": "ЛЮБЫЯ падумовы павінны адпавядаць патрабаванню",
"Condition Text": "Тэкст стану",
"Condition": "Стан",
"Event": "Падзея",
"Event description": "Апісанне падзеі",
"is": "ёсць",
"is not": "не",
"equals or greater than": "роўна або больш",
"equals or less than": "роўна або менш",
"contains": "змяшчае",
"does not contain": "не ўтрымлівае",
"greater than": "больш, чым",
"less than": "менш чым",
"is one of": "з'яўляецца адным з",
"is not one of": "не з'яўляецца адным з",
"Unknown Attribute": "Невядомы атрыбут",
"Unknown attribute selected": "Выбраны невядомы атрыбут",
"Condition Type": "Тып стану",
"Required Value": "Неабходнае значэнне",
"Selected Records": "Выбраныя запісы",
"No records added": "Запісаў не дададзены",
"Attribute": "Атрыбут",
"Operator": "Аператар",
"Value": "Значэнне",
"Delete this notification rule?": "Выдаліць гэта правіла апавяшчэнняў?",
"New Notification Rule": "Новае правіла апавяшчэнняў",
"Do you really want to delete this action?": "Вы сапраўды хочаце выдаліць гэта дзеянне?",
"Add Action": "Дадаць дзеянне",
"These variables are available": "Гэтыя зменныя даступныя",
"Click or drag these in to the content area": "Націсніце або перацягніце іх у вобласць змесціва",
"This action does not provide any variables.": "Гэта дзеянне не дае ніякіх зменных.",
"Delete Condition": "Умова выдалення",
"Add Condition": "Дадаць умову",
"Do you really want to delete this condition?": "Вы сапраўды хочаце выдаліць гэтую ўмову?",
"Please select a condition": "Выберыце ўмову"
}

View File

@ -0,0 +1,61 @@
{
"Notify": "Уведомете",
"Notification Services": "Уведомителни услуги",
"Notification Rules": "Правила за уведомяване",
"Manage the events and actions that trigger notifications.": "Управлявайте събитията и действията, които задействат известия.",
"Name": "име",
"Code": "код",
"Notification Rule": "Правило за уведомяване",
"Add Notification Rule": "Добавете правило за уведомяване",
"Schedule": "График",
"Note that scheduling might not work for certain queue driver configurations.": "Имайте предвид, че планирането може да не работи за определени конфигурации на драйвери на опашката.",
"Learn more": "Научете повече",
"The configured queue driver does not support delayed execution.": "Конфигурираният драйвер за опашка не поддържа отложено изпълнение.",
"Notifications management": "Управление на известията",
"Return to Notifications": "Върнете се към Известия",
"Are you sure?": "Сигурен ли си?",
"Action": "Действие",
"Action Description": "Описание на действието",
"Compound Condition": "Сложно състояние",
"ALL of subconditions should be": "ВСИЧКИ подусловия трябва да бъдат",
"ANY of subconditions should be": "ВСЯКО от подусловията трябва да бъде",
"ON": "НА",
"AND": "И",
"OR": "ИЛИ",
"ALL subconditions should meet the requirement": "ВСИЧКИ подусловия трябва да отговарят на изискването",
"ANY subconditions should meet the requirement": "ВСЕКИ подусловия трябва да отговарят на изискването",
"Condition Text": "Текст на условието",
"Condition": "Състояние",
"Event": "Събитие",
"Event description": "Описание на събитието",
"is": "е",
"is not": "не е",
"equals or greater than": "равно или по-голямо от",
"equals or less than": "равно или по-малко от",
"contains": "съдържа",
"does not contain": "не съдържа",
"greater than": "по-голяма от",
"less than": "по-малко от",
"is one of": "е един от",
"is not one of": "не е един от",
"Unknown Attribute": "Неизвестен атрибут",
"Unknown attribute selected": "Избран е неизвестен атрибут",
"Condition Type": "Тип на състояние",
"Required Value": "Задължителна стойност",
"Selected Records": "Избрани записи",
"No records added": "Няма добавени записи",
"Attribute": "Атрибут",
"Operator": "Оператор",
"Value": "Стойност",
"Delete this notification rule?": "Да се изтрие ли това правило за уведомяване?",
"New Notification Rule": "Ново правило за уведомяване",
"Do you really want to delete this action?": "Наистина ли искате да изтриете това действие?",
"Add Action": "Добавяне на действие",
"These variables are available": "Тези променливи са налични",
"Click or drag these in to the content area": "Щракнете или плъзнете ги в областта на съдържанието",
"This action does not provide any variables.": "Това действие не предоставя никакви променливи.",
"Delete Condition": "Условие за изтриване",
"Add Condition": "Добавете условие",
"Do you really want to delete this condition?": "Наистина ли искате да изтриете това условие?",
"Please select a condition": "Моля, изберете условие"
}

View File

@ -0,0 +1,61 @@
{
"Notify": "Oznámit",
"Notification Services": "Notifikační služby",
"Notification Rules": "Pravidla oznamování",
"Manage the events and actions that trigger notifications.": "Spravujte události a akce, které spouštějí oznámení.",
"Name": "název",
"Code": "Kód",
"Notification Rule": "Pravidlo oznámení",
"Add Notification Rule": "Přidat pravidlo oznámení",
"Schedule": "Plán",
"Note that scheduling might not work for certain queue driver configurations.": "U některých konfigurací ovladače fronty nemusí plánování fungovat.",
"Learn more": "Zjistěte více",
"The configured queue driver does not support delayed execution.": "Nakonfigurovaný ovladač fronty nepodporuje zpožděné spuštění.",
"Notifications management": "Správa oznámení",
"Return to Notifications": "Vraťte se do Oznámení",
"Are you sure?": "Jsi si jistá?",
"Action": "Akce",
"Action Description": "Popis akce",
"Compound Condition": "Složený stav",
"ALL of subconditions should be": "VŠECHNY dílčí podmínky by měly být",
"ANY of subconditions should be": "KTERÁKOLI podmínka by měla být",
"ON": "NA",
"AND": "A",
"OR": "NEBO",
"ALL subconditions should meet the requirement": "VŠECHNY dílčí podmínky by měly splňovat požadavek",
"ANY subconditions should meet the requirement": "ŽÁDNÉ dílčí podmínky by měly splňovat požadavek",
"Condition Text": "Podmínka Text",
"Condition": "Stav",
"Event": "událost",
"Event description": "Popis události",
"is": "je",
"is not": "není",
"equals or greater than": "se rovná nebo je větší než",
"equals or less than": "se rovná nebo je menší než",
"contains": "obsahuje",
"does not contain": "neobsahuje",
"greater than": "větší než",
"less than": "méně než",
"is one of": "je jedním z",
"is not one of": "není jedním z",
"Unknown Attribute": "Neznámý atribut",
"Unknown attribute selected": "Vybrán neznámý atribut",
"Condition Type": "Typ podmínky",
"Required Value": "Požadovaná hodnota",
"Selected Records": "Vybrané záznamy",
"No records added": "Nebyly přidány žádné záznamy",
"Attribute": "Atribut",
"Operator": "Operátor",
"Value": "Hodnota",
"Delete this notification rule?": "Smazat toto pravidlo oznámení?",
"New Notification Rule": "Nové pravidlo oznamování",
"Do you really want to delete this action?": "Opravdu chcete tuto akci smazat?",
"Add Action": "Přidat akci",
"These variables are available": "Tyto proměnné jsou k dispozici",
"Click or drag these in to the content area": "Klikněte na ně nebo je přetáhněte do oblasti obsahu",
"This action does not provide any variables.": "Tato akce neposkytuje žádné proměnné.",
"Delete Condition": "Smazat podmínku",
"Add Condition": "Přidat podmínku",
"Do you really want to delete this condition?": "Opravdu chcete smazat tuto podmínku?",
"Please select a condition": "Vyberte prosím podmínku"
}

View File

@ -0,0 +1,61 @@
{
"Notify": "Underrette",
"Notification Services": "Underretningstjenester",
"Notification Rules": "Underretningsregler",
"Manage the events and actions that trigger notifications.": "Administrer de begivenheder og handlinger, der udløser notifikationer.",
"Name": "Navn",
"Code": "Kode",
"Notification Rule": "Underretningsregel",
"Add Notification Rule": "Tilføj meddelelsesregel",
"Schedule": "Tidsplan",
"Note that scheduling might not work for certain queue driver configurations.": "Bemærk, at planlægning muligvis ikke virker for visse kødriverkonfigurationer.",
"Learn more": "Lær mere",
"The configured queue driver does not support delayed execution.": "Den konfigurerede kødriver understøtter ikke forsinket udførelse.",
"Notifications management": "Administration af notifikationer",
"Return to Notifications": "Vend tilbage til meddelelser",
"Are you sure?": "Er du sikker?",
"Action": "Handling",
"Action Description": "Handlingsbeskrivelse",
"Compound Condition": "Sammensat tilstand",
"ALL of subconditions should be": "ALLE underbetingelser bør være",
"ANY of subconditions should be": "ENHVER af underbetingelser bør være",
"ON": "PÅ",
"AND": "OG",
"OR": "ELLER",
"ALL subconditions should meet the requirement": "ALLE underbetingelser bør opfylde kravet",
"ANY subconditions should meet the requirement": "ALLE underbetingelser bør opfylde kravet",
"Condition Text": "Tilstandstekst",
"Condition": "Tilstand",
"Event": "Begivenhed",
"Event description": "Begivenhedsbeskrivelse",
"is": "er",
"is not": "er ikke",
"equals or greater than": "lig med eller større end",
"equals or less than": "lig med eller mindre end",
"contains": "indeholder",
"does not contain": "indeholder ikke",
"greater than": "bedre end",
"less than": "Mindre end",
"is one of": "er en af",
"is not one of": "er ikke en af",
"Unknown Attribute": "Ukendt attribut",
"Unknown attribute selected": "Ukendt attribut valgt",
"Condition Type": "Tilstandstype",
"Required Value": "Påkrævet værdi",
"Selected Records": "Udvalgte poster",
"No records added": "Ingen registreringer tilføjet",
"Attribute": "Attribut",
"Operator": "Operatør",
"Value": "Værdi",
"Delete this notification rule?": "Vil du slette denne underretningsregel?",
"New Notification Rule": "Ny meddelelsesregel",
"Do you really want to delete this action?": "Vil du virkelig slette denne handling?",
"Add Action": "Tilføj handling",
"These variables are available": "Disse variabler er tilgængelige",
"Click or drag these in to the content area": "Klik eller træk disse ind til indholdsområdet",
"This action does not provide any variables.": "Denne handling giver ingen variabler.",
"Delete Condition": "Slet tilstand",
"Add Condition": "Tilføj tilstand",
"Do you really want to delete this condition?": "Vil du virkelig slette denne betingelse?",
"Please select a condition": "Vælg venligst en betingelse"
}

View File

@ -0,0 +1,61 @@
{
"Notify": "Benachrichtigen",
"Notification Services": "Benachrichtigungsdienste",
"Notification Rules": "Benachrichtigungsregeln",
"Manage the events and actions that trigger notifications.": "Verwalten Sie die Ereignisse und Aktionen, die Benachrichtigungen auslösen.",
"Name": "Name",
"Code": "Code",
"Notification Rule": "Benachrichtigungsregel",
"Add Notification Rule": "Benachrichtigungsregel hinzufügen",
"Schedule": "Zeitplan",
"Note that scheduling might not work for certain queue driver configurations.": "Beachten Sie, dass die Planung für bestimmte Warteschlangentreiberkonfigurationen möglicherweise nicht funktioniert.",
"Learn more": "Mehr erfahren",
"The configured queue driver does not support delayed execution.": "Der konfigurierte Warteschlangentreiber unterstützt keine verzögerte Ausführung.",
"Notifications management": "Benachrichtigungsverwaltung",
"Return to Notifications": "Zurück zu Benachrichtigungen",
"Are you sure?": "Bist du sicher?",
"Action": "Aktion",
"Action Description": "Aktionsbeschreibung",
"Compound Condition": "Zusammengesetzter Zustand",
"ALL of subconditions should be": "ALLE Unterbedingungen sollten sein",
"ANY of subconditions should be": "JEDE der Unterbedingungen sollte sein",
"ON": "AN",
"AND": "UND",
"OR": "ODER",
"ALL subconditions should meet the requirement": "ALLE Unterbedingungen sollten die Anforderung erfüllen",
"ANY subconditions should meet the requirement": "ALLE Unterbedingungen sollten die Anforderung erfüllen",
"Condition Text": "Bedingungstext",
"Condition": "Zustand",
"Event": "Fall",
"Event description": "Eventbeschreibung",
"is": "ist",
"is not": "ist nicht",
"equals or greater than": "gleich oder größer als",
"equals or less than": "gleich oder kleiner als",
"contains": "enthält",
"does not contain": "beinhaltet nicht",
"greater than": "größer als",
"less than": "weniger als",
"is one of": "ist einer von",
"is not one of": "ist keiner von",
"Unknown Attribute": "Unbekanntes Attribut",
"Unknown attribute selected": "Unbekanntes Attribut ausgewählt",
"Condition Type": "Bedingungstyp",
"Required Value": "Erforderlicher Wert",
"Selected Records": "Ausgewählte Aufzeichnungen",
"No records added": "Keine Datensätze hinzugefügt",
"Attribute": "Attribut",
"Operator": "Operator",
"Value": "Wert",
"Delete this notification rule?": "Diese Benachrichtigungsregel löschen?",
"New Notification Rule": "Neue Benachrichtigungsregel",
"Do you really want to delete this action?": "Möchten Sie diese Aktion wirklich löschen?",
"Add Action": "Aktion hinzufügen",
"These variables are available": "Diese Variablen sind verfügbar",
"Click or drag these in to the content area": "Klicken oder ziehen Sie diese in den Inhaltsbereich",
"This action does not provide any variables.": "Diese Aktion stellt keine Variablen bereit.",
"Delete Condition": "Bedingung löschen",
"Add Condition": "Bedingung hinzufügen",
"Do you really want to delete this condition?": "Möchten Sie diese Bedingung wirklich löschen?",
"Please select a condition": "Bitte wählen Sie eine Bedingung aus"
}

View File

@ -0,0 +1,61 @@
{
"Notify": "Notificar",
"Notification Services": "Servicios de notificación",
"Notification Rules": "Reglas de notificación",
"Manage the events and actions that trigger notifications.": "Administre los eventos y acciones que activan las notificaciones.",
"Name": "Nombre",
"Code": "Código",
"Notification Rule": "Regla de notificación",
"Add Notification Rule": "Agregar regla de notificación",
"Schedule": "Calendario",
"Note that scheduling might not work for certain queue driver configurations.": "Tenga en cuenta que es posible que la programación no funcione para ciertas configuraciones de controladores de cola.",
"Learn more": "Aprende más",
"The configured queue driver does not support delayed execution.": "El controlador de cola configurado no admite la ejecución retrasada.",
"Notifications management": "Gestión de notificaciones",
"Return to Notifications": "Volver a Notificaciones",
"Are you sure?": "¿Está seguro?",
"Action": "Acción",
"Action Description": "Acción Descripción",
"Compound Condition": "Condición compuesta",
"ALL of subconditions should be": "TODAS las subcondiciones deben ser",
"ANY of subconditions should be": "CUALQUIERA de las subcondiciones debe ser",
"ON": "EN",
"AND": "Y",
"OR": "O",
"ALL subconditions should meet the requirement": "TODAS las subcondiciones deben cumplir con el requisito",
"ANY subconditions should meet the requirement": "CUALQUIER subcondición debe cumplir con el requisito",
"Condition Text": "Texto de condición",
"Condition": "Condición",
"Event": "Evento",
"Event description": "Descripción del evento",
"is": "es",
"is not": "no es",
"equals or greater than": "igual o mayor que",
"equals or less than": "igual o menor que",
"contains": "contiene",
"does not contain": "no contiene",
"greater than": "mas grande que",
"less than": "menos que",
"is one of": "es uno de",
"is not one of": "no es uno de",
"Unknown Attribute": "Atributo desconocido",
"Unknown attribute selected": "Atributo desconocido seleccionado",
"Condition Type": "Tipo de condición",
"Required Value": "Valor requerido",
"Selected Records": "Registros seleccionados",
"No records added": "No se agregaron registros",
"Attribute": "Atributo",
"Operator": "Operador",
"Value": "Valor",
"Delete this notification rule?": "¿Eliminar esta regla de notificación?",
"New Notification Rule": "Nueva regla de notificación",
"Do you really want to delete this action?": "¿Realmente desea eliminar esta acción?",
"Add Action": "Agregar acción",
"These variables are available": "Estas variables están disponibles",
"Click or drag these in to the content area": "Haga clic o arrástrelos al área de contenido",
"This action does not provide any variables.": "Esta acción no proporciona ninguna variable.",
"Delete Condition": "Eliminar condición",
"Add Condition": "Añadir condición",
"Do you really want to delete this condition?": "¿Realmente desea eliminar esta condición?",
"Please select a condition": "Seleccione una condición"
}

View File

@ -0,0 +1,61 @@
{
"Notify": "Teavita",
"Notification Services": "Teavitusteenused",
"Notification Rules": "Teavitamise reeglid",
"Manage the events and actions that trigger notifications.": "Hallake märguandeid käivitavaid sündmusi ja toiminguid.",
"Name": "Nimi",
"Code": "Kood",
"Notification Rule": "Teavitamise reegel",
"Add Notification Rule": "Lisa teavitusreegel",
"Schedule": "Ajakava",
"Note that scheduling might not work for certain queue driver configurations.": "Pange tähele, et ajastamine ei pruugi teatud järjekorra draiveri konfiguratsioonide puhul töötada.",
"Learn more": "Lisateavet",
"The configured queue driver does not support delayed execution.": "Konfigureeritud järjekorra draiver ei toeta viivitatud täitmist.",
"Notifications management": "Teatiste haldamine",
"Return to Notifications": "Naaske jaotisesse Teatised",
"Are you sure?": "Oled sa kindel?",
"Action": "Tegevus",
"Action Description": "Toimingu kirjeldus",
"Compound Condition": "Liitseisund",
"ALL of subconditions should be": "KÕIK alamtingimused peaksid olema",
"ANY of subconditions should be": "Mistahes alamtingimused peaksid olema",
"ON": "PEAL",
"AND": "JA",
"OR": "VÕI",
"ALL subconditions should meet the requirement": "KÕIK alamtingimused peaksid vastama nõuetele",
"ANY subconditions should meet the requirement": "KÕIK alamtingimused peaksid nõuetele vastama",
"Condition Text": "Seisundi tekst",
"Condition": "Seisund",
"Event": "Sündmus",
"Event description": "Sündmuse kirjeldus",
"is": "on",
"is not": "ei ole",
"equals or greater than": "võrdne või suurem kui",
"equals or less than": "võrdne või väiksem kui",
"contains": "sisaldab",
"does not contain": "ei sisalda",
"greater than": "suurem kui",
"less than": "vähem kui",
"is one of": "on üks",
"is not one of": "ei ole üks",
"Unknown Attribute": "Tundmatu atribuut",
"Unknown attribute selected": "Valitud on tundmatu atribuut",
"Condition Type": "Seisundi tüüp",
"Required Value": "Nõutav väärtus",
"Selected Records": "Valitud kirjed",
"No records added": "Kirjeid pole lisatud",
"Attribute": "Atribuut",
"Operator": "Operaator",
"Value": "Väärtus",
"Delete this notification rule?": "Kas kustutada see teavitusreegel?",
"New Notification Rule": "Uus teavitamise reegel",
"Do you really want to delete this action?": "Kas soovite tõesti selle toimingu kustutada?",
"Add Action": "Lisa toiming",
"These variables are available": "Need muutujad on saadaval",
"Click or drag these in to the content area": "Klõpsake või lohistage need sisualale",
"This action does not provide any variables.": "See toiming ei paku muutujaid.",
"Delete Condition": "Kustuta tingimus",
"Add Condition": "Lisa tingimus",
"Do you really want to delete this condition?": "Kas soovite tõesti selle tingimuse kustutada?",
"Please select a condition": "Valige tingimus"
}

View File

@ -0,0 +1,61 @@
{
"Notify": "اعلام کردن",
"Notification Services": "خدمات اطلاع رسانی",
"Notification Rules": "قوانین اطلاع رسانی",
"Manage the events and actions that trigger notifications.": "رویدادها و اقداماتی را که اعلان‌ها را راه‌اندازی می‌کنند مدیریت کنید.",
"Name": "نام",
"Code": "کد",
"Notification Rule": "قانون اطلاع رسانی",
"Add Notification Rule": "قانون اعلان را اضافه کنید",
"Schedule": "برنامه",
"Note that scheduling might not work for certain queue driver configurations.": "توجه داشته باشید که زمان‌بندی ممکن است برای پیکربندی‌های درایور صف خاص کار نکند.",
"Learn more": "بیشتر بدانید",
"The configured queue driver does not support delayed execution.": "درایور صف پیکربندی شده از اجرای تاخیری پشتیبانی نمی کند.",
"Notifications management": "مدیریت اعلان ها",
"Return to Notifications": "بازگشت به اطلاعیه ها",
"Are you sure?": "مطمئنی؟",
"Action": "عمل",
"Action Description": "توضیحات اقدام",
"Compound Condition": "وضعیت مرکب",
"ALL of subconditions should be": "همه شرایط فرعی باید باشد",
"ANY of subconditions should be": "هر یک از شرایط فرعی باید باشد",
"ON": "بر",
"AND": "و",
"OR": "یا",
"ALL subconditions should meet the requirement": "همه شرایط فرعی باید الزامات را برآورده کنند",
"ANY subconditions should meet the requirement": "هر شرط فرعی باید الزامات را برآورده کند",
"Condition Text": "متن شرط",
"Condition": "شرایط. شرط",
"Event": "رویداد",
"Event description": "شرح رویداد",
"is": "است",
"is not": "نیست",
"equals or greater than": "مساوی یا بزرگتر از",
"equals or less than": "برابر یا کمتر از",
"contains": "شامل",
"does not contain": "شامل نمی شود",
"greater than": "بزرگتر از",
"less than": "کمتر از",
"is one of": "یکی از",
"is not one of": "یکی از نیست",
"Unknown Attribute": "ویژگی نامشخص",
"Unknown attribute selected": "ویژگی ناشناخته انتخاب شد",
"Condition Type": "نوع شرایط",
"Required Value": "ارزش مورد نیاز",
"Selected Records": "سوابق انتخاب شده",
"No records added": "هیچ رکوردی اضافه نشد",
"Attribute": "صفت",
"Operator": "اپراتور",
"Value": "مقدار",
"Delete this notification rule?": "این قانون اعلان حذف شود؟",
"New Notification Rule": "قانون جدید اطلاع رسانی",
"Do you really want to delete this action?": "آیا واقعاً می خواهید این عمل را حذف کنید؟",
"Add Action": "افزودن اکشن",
"These variables are available": "این متغیرها در دسترس هستند",
"Click or drag these in to the content area": "روی آنها کلیک کنید یا به قسمت محتوا بکشید",
"This action does not provide any variables.": "این عمل هیچ متغیری ارائه نمی دهد.",
"Delete Condition": "شرط حذف",
"Add Condition": "اضافه کردن شرط",
"Do you really want to delete this condition?": "آیا واقعاً می خواهید این شرط را حذف کنید؟",
"Please select a condition": "لطفاً یک شرط را انتخاب کنید"
}

View File

@ -0,0 +1,61 @@
{
"Notify": "Notifier",
"Notification Services": "Services de notification",
"Notification Rules": "Règles de notification",
"Manage the events and actions that trigger notifications.": "Gérez les événements et les actions qui déclenchent des notifications.",
"Name": "Nom",
"Code": "Code",
"Notification Rule": "Règle de notification",
"Add Notification Rule": "Ajouter une règle de notification",
"Schedule": "Programme",
"Note that scheduling might not work for certain queue driver configurations.": "Notez que la planification peut ne pas fonctionner pour certaines configurations de pilote de file d'attente.",
"Learn more": "Apprendre encore plus",
"The configured queue driver does not support delayed execution.": "Le pilote de file d'attente configuré ne prend pas en charge l'exécution différée.",
"Notifications management": "Gestion des notifications",
"Return to Notifications": "Retour aux Notifications",
"Are you sure?": "Êtes-vous sûr?",
"Action": "action",
"Action Description": "Description de l'action",
"Compound Condition": "État composé",
"ALL of subconditions should be": "TOUTES les sous-conditions doivent être",
"ANY of subconditions should be": "TOUTES les sous-conditions doivent être",
"ON": "AU",
"AND": "ET",
"OR": "OU",
"ALL subconditions should meet the requirement": "TOUTES les sous-conditions doivent répondre à l'exigence",
"ANY subconditions should meet the requirement": "TOUTES les sous-conditions doivent répondre à l'exigence",
"Condition Text": "Texte de condition",
"Condition": "État",
"Event": "Événement",
"Event description": "Description de l'évenement",
"is": "est",
"is not": "n'est pas",
"equals or greater than": "égal ou supérieur à",
"equals or less than": "égal ou inférieur à",
"contains": "contient",
"does not contain": "ne contient pas",
"greater than": "plus grand que",
"less than": "moins que",
"is one of": "fait partie de",
"is not one of": "n'est pas l'un des",
"Unknown Attribute": "Attribut inconnu",
"Unknown attribute selected": "Attribut inconnu sélectionné",
"Condition Type": "Type d'état",
"Required Value": "Valeur requise",
"Selected Records": "Enregistrements sélectionnés",
"No records added": "Aucun enregistrement ajouté",
"Attribute": "Attribut",
"Operator": "Opérateur",
"Value": "Évaluer",
"Delete this notification rule?": "Supprimer cette règle de notification ?",
"New Notification Rule": "Nouvelle règle de notification",
"Do you really want to delete this action?": "Voulez-vous vraiment supprimer cette action ?",
"Add Action": "Ajouter une action",
"These variables are available": "Ces variables sont disponibles",
"Click or drag these in to the content area": "Cliquez ou faites-les glisser dans la zone de contenu",
"This action does not provide any variables.": "Cette action ne fournit aucune variable.",
"Delete Condition": "Supprimer la condition",
"Add Condition": "Ajouter une condition",
"Do you really want to delete this condition?": "Voulez-vous vraiment supprimer cette condition ?",
"Please select a condition": "Veuillez sélectionner une condition"
}

View File

@ -0,0 +1,61 @@
{
"Notify": "Értesítés",
"Notification Services": "Értesítési szolgáltatások",
"Notification Rules": "Értesítési szabályok",
"Manage the events and actions that trigger notifications.": "Az értesítéseket kiváltó események és műveletek kezelése.",
"Name": "Név",
"Code": "Kód",
"Notification Rule": "Értesítési szabály",
"Add Notification Rule": "Értesítési szabály hozzáadása",
"Schedule": "Menetrend",
"Note that scheduling might not work for certain queue driver configurations.": "Vegye figyelembe, hogy az ütemezés bizonyos sor-illesztőprogram-konfigurációk esetén előfordulhat, hogy nem működik.",
"Learn more": "Tudj meg többet",
"The configured queue driver does not support delayed execution.": "A konfigurált sor-illesztőprogram nem támogatja a késleltetett végrehajtást.",
"Notifications management": "Értesítések kezelése",
"Return to Notifications": "Vissza az Értesítésekhez",
"Are you sure?": "biztos vagy ebben?",
"Action": "Akció",
"Action Description": "Művelet leírása",
"Compound Condition": "Összetett állapot",
"ALL of subconditions should be": "MINDEN alfeltételnek meg kell lennie",
"ANY of subconditions should be": "BÁRMILYEN alfeltételnek lennie kell",
"ON": "TOVÁBB",
"AND": "ÉS",
"OR": "VAGY",
"ALL subconditions should meet the requirement": "MINDEN alfeltételnek meg kell felelnie a követelménynek",
"ANY subconditions should meet the requirement": "BÁRMELY alfeltételnek meg kell felelnie a követelménynek",
"Condition Text": "Állapot szöveg",
"Condition": "Feltétel",
"Event": "Esemény",
"Event description": "Esemény leírása",
"is": "van",
"is not": "nem",
"equals or greater than": "egyenlő vagy nagyobb mint",
"equals or less than": "egyenlő vagy kisebb mint",
"contains": "tartalmaz",
"does not contain": "nem tartalmaz",
"greater than": "nagyobb, mint",
"less than": "kevesebb, mint",
"is one of": "egyike a",
"is not one of": "nem egyike",
"Unknown Attribute": "Ismeretlen tulajdonság",
"Unknown attribute selected": "Ismeretlen attribútum kiválasztva",
"Condition Type": "Állapot típusa",
"Required Value": "Kötelező érték",
"Selected Records": "Kiválasztott rekordok",
"No records added": "Nincsenek hozzáadva rekordok",
"Attribute": "Tulajdonság",
"Operator": "Operátor",
"Value": "Érték",
"Delete this notification rule?": "Törli ezt az értesítési szabályt?",
"New Notification Rule": "Új értesítési szabály",
"Do you really want to delete this action?": "Biztosan törölni szeretné ezt a műveletet?",
"Add Action": "Művelet hozzáadása",
"These variables are available": "Ezek a változók elérhetők",
"Click or drag these in to the content area": "Kattintson vagy húzza át ezeket a tartalomterületre",
"This action does not provide any variables.": "Ez a művelet nem biztosít változókat.",
"Delete Condition": "Feltétel törlése",
"Add Condition": "Feltétel hozzáadása",
"Do you really want to delete this condition?": "Valóban törölni szeretné ezt a feltételt?",
"Please select a condition": "Kérjük, válasszon egy feltételt"
}

View File

@ -0,0 +1,61 @@
{
"Notify": "Notificare",
"Notification Services": "Servizi di notifica",
"Notification Rules": "Regole di notifica",
"Manage the events and actions that trigger notifications.": "Gestisci gli eventi e le azioni che attivano le notifiche.",
"Name": "Nome",
"Code": "Codice",
"Notification Rule": "Regola di notifica",
"Add Notification Rule": "Aggiungi regola di notifica",
"Schedule": "Programma",
"Note that scheduling might not work for certain queue driver configurations.": "Si noti che la pianificazione potrebbe non funzionare per alcune configurazioni del driver della coda.",
"Learn more": "Per saperne di più",
"The configured queue driver does not support delayed execution.": "Il driver della coda configurato non supporta l'esecuzione ritardata.",
"Notifications management": "Gestione delle notifiche",
"Return to Notifications": "Torna a Notifiche",
"Are you sure?": "Sei sicuro?",
"Action": "Azione",
"Action Description": "Descrizione azione",
"Compound Condition": "Condizione composta",
"ALL of subconditions should be": "TUTTE le sottocondizioni dovrebbero esserlo",
"ANY of subconditions should be": "QUALSIASI delle sottocondizioni dovrebbe essere",
"ON": "SU",
"AND": "E",
"OR": "O",
"ALL subconditions should meet the requirement": "TUTTE le sottocondizioni devono soddisfare il requisito",
"ANY subconditions should meet the requirement": "QUALSIASI sottocondizione deve soddisfare il requisito",
"Condition Text": "Testo delle condizioni",
"Condition": "Condizione",
"Event": "Evento",
"Event description": "Descrizione dell'evento",
"is": "è",
"is not": "non è",
"equals or greater than": "uguale o maggiore di",
"equals or less than": "uguale o minore di",
"contains": "contiene",
"does not contain": "non contiene",
"greater than": "più grande di",
"less than": "meno di",
"is one of": "è uno di",
"is not one of": "non è uno di",
"Unknown Attribute": "Attributo sconosciuto",
"Unknown attribute selected": "Attributo sconosciuto selezionato",
"Condition Type": "Tipo di condizione",
"Required Value": "Valore richiesto",
"Selected Records": "Record selezionati",
"No records added": "Nessun record aggiunto",
"Attribute": "Attributo",
"Operator": "Operatore",
"Value": "Valore",
"Delete this notification rule?": "Eliminare questa regola di notifica?",
"New Notification Rule": "Nuova regola di notifica",
"Do you really want to delete this action?": "Vuoi davvero eliminare questa azione?",
"Add Action": "Aggiungi azione",
"These variables are available": "Queste variabili sono disponibili",
"Click or drag these in to the content area": "Fare clic o trascinarli nell'area del contenuto",
"This action does not provide any variables.": "Questa azione non fornisce alcuna variabile.",
"Delete Condition": "Elimina condizione",
"Add Condition": "Aggiungi condizione",
"Do you really want to delete this condition?": "Vuoi davvero eliminare questa condizione?",
"Please select a condition": "Seleziona una condizione"
}

View File

@ -0,0 +1,61 @@
{
"Notify": "Informeer",
"Notification Services": "Meldingsservices",
"Notification Rules": "Meldingsregels",
"Manage the events and actions that trigger notifications.": "Beheer de gebeurtenissen en acties die meldingen activeren.",
"Name": "Naam",
"Code": "Code",
"Notification Rule": "Meldingsregel",
"Add Notification Rule": "Meldingsregel toevoegen",
"Schedule": "Schema",
"Note that scheduling might not work for certain queue driver configurations.": "Houd er rekening mee dat planning mogelijk niet werkt voor bepaalde configuraties van wachtrijstuurprogramma's.",
"Learn more": "Leer meer",
"The configured queue driver does not support delayed execution.": "Het geconfigureerde wachtrijstuurprogramma ondersteunt geen vertraagde uitvoering.",
"Notifications management": "Beheer van meldingen",
"Return to Notifications": "Keer terug naar Meldingen",
"Are you sure?": "Weet je het zeker?",
"Action": "Actie",
"Action Description": "Actiebeschrijving",
"Compound Condition": "Samengestelde toestand:",
"ALL of subconditions should be": "ALLE subvoorwaarden zouden moeten zijn:",
"ANY of subconditions should be": "ELK van de subvoorwaarden zou moeten zijn:",
"ON": "AAN",
"AND": "EN",
"OR": "OF",
"ALL subconditions should meet the requirement": "ALLE subvoorwaarden moeten aan de eis voldoen",
"ANY subconditions should meet the requirement": "ELKE subvoorwaarden zouden aan de eis moeten voldoen",
"Condition Text": "Voorwaarde Tekst",
"Condition": "Voorwaarde",
"Event": "Evenement",
"Event description": "Beschrijving van het evenement",
"is": "is",
"is not": "is niet",
"equals or greater than": "gelijk aan of groter dan",
"equals or less than": "gelijk aan of kleiner dan",
"contains": "bevat",
"does not contain": "bevat geen",
"greater than": "groter dan",
"less than": "minder dan",
"is one of": "is een van",
"is not one of": "is niet een van",
"Unknown Attribute": "Onbekend kenmerk",
"Unknown attribute selected": "Onbekend kenmerk geselecteerd",
"Condition Type": "Conditietype:",
"Required Value": "Vereiste waarde",
"Selected Records": "Geselecteerde records",
"No records added": "Geen records toegevoegd",
"Attribute": "Attribuut",
"Operator": "Operator",
"Value": "Waarde",
"Delete this notification rule?": "Deze meldingsregel verwijderen?",
"New Notification Rule": "Nieuwe meldingsregel",
"Do you really want to delete this action?": "Wil je deze actie echt verwijderen?",
"Add Action": "Actie toevoegen",
"These variables are available": "Deze variabelen zijn beschikbaar",
"Click or drag these in to the content area": "Klik of sleep deze naar het inhoudsgebied",
"This action does not provide any variables.": "Deze actie biedt geen variabelen.",
"Delete Condition": "Voorwaarde verwijderen",
"Add Condition": "Voorwaarde toevoegen",
"Do you really want to delete this condition?": "Wilt u deze voorwaarde echt verwijderen?",
"Please select a condition": "Selecteer een voorwaarde"
}

View File

@ -0,0 +1,61 @@
{
"Notify": "Уведомлять",
"Notification Services": "Службы уведомлений",
"Notification Rules": "Правила уведомления",
"Manage the events and actions that trigger notifications.": "Управляйте событиями и действиями, вызывающими уведомления.",
"Name": "Имя",
"Code": "Код",
"Notification Rule": "Правило уведомления",
"Add Notification Rule": "Добавить правило уведомления",
"Schedule": "Расписание",
"Note that scheduling might not work for certain queue driver configurations.": "Обратите внимание, что планирование может не работать для определенных конфигураций драйвера очереди.",
"Learn more": "Узнать больше",
"The configured queue driver does not support delayed execution.": "Настроенный драйвер очереди не поддерживает отложенное выполнение.",
"Notifications management": "Управление уведомлениями",
"Return to Notifications": "Вернуться к уведомлениям",
"Are you sure?": "Вы уверены?",
"Action": "Действие",
"Action Description": "Описание действия",
"Compound Condition": "Сложное состояние",
"ALL of subconditions should be": "ВСЕ подусловия должны быть",
"ANY of subconditions should be": "ЛЮБОЕ из подусловий должно быть",
"ON": "НА",
"AND": "А ТАКЖЕ",
"OR": "ИЛИ",
"ALL subconditions should meet the requirement": "ВСЕ подусловия должны соответствовать требованию",
"ANY subconditions should meet the requirement": "ЛЮБЫЕ подусловия должны соответствовать требованию",
"Condition Text": "Текст условия",
"Condition": "Условие",
"Event": "Мероприятие",
"Event description": "Описание события",
"is": "является",
"is not": "не является",
"equals or greater than": "равно или больше, чем",
"equals or less than": "равно или меньше чем",
"contains": "содержит",
"does not contain": "не содержит",
"greater than": "лучше чем",
"less than": "меньше, чем",
"is one of": "один из",
"is not one of": "не является одним из",
"Unknown Attribute": "Неизвестный атрибут",
"Unknown attribute selected": "Выбран неизвестный атрибут",
"Condition Type": "Тип условия",
"Required Value": "Требуемое значение",
"Selected Records": "Выбранные записи",
"No records added": "Записи не добавлены",
"Attribute": "Атрибут",
"Operator": "Оператор",
"Value": "Стоимость",
"Delete this notification rule?": "Удалить это правило уведомлений?",
"New Notification Rule": "Новое правило уведомления",
"Do you really want to delete this action?": "Вы действительно хотите удалить это действие?",
"Add Action": "Добавить действие",
"These variables are available": "Эти переменные доступны",
"Click or drag these in to the content area": "Нажмите или перетащите их в область содержимого.",
"This action does not provide any variables.": "Это действие не предоставляет никаких переменных.",
"Delete Condition": "Удалить условие",
"Add Condition": "Добавить условие",
"Do you really want to delete this condition?": "Вы действительно хотите удалить это условие?",
"Please select a condition": "Пожалуйста, выберите условие"
}

View File

@ -0,0 +1,61 @@
{
"Notify": "Upozorniť",
"Notification Services": "Notifikačné služby",
"Notification Rules": "Pravidlá oznamovania",
"Manage the events and actions that trigger notifications.": "Spravujte udalosti a akcie, ktoré spúšťajú upozornenia.",
"Name": "názov",
"Code": "kód",
"Notification Rule": "Pravidlo oznamovania",
"Add Notification Rule": "Pridať pravidlo upozornenia",
"Schedule": "Rozvrh",
"Note that scheduling might not work for certain queue driver configurations.": "Upozorňujeme, že plánovanie nemusí fungovať pre určité konfigurácie ovládača frontu.",
"Learn more": "Uč sa viac",
"The configured queue driver does not support delayed execution.": "Nakonfigurovaný ovládač frontu nepodporuje oneskorené spustenie.",
"Notifications management": "Správa upozornení",
"Return to Notifications": "Vráťte sa k upozorneniam",
"Are you sure?": "Si si istý?",
"Action": "Akcia",
"Action Description": "Popis akcie",
"Compound Condition": "Zložený stav",
"ALL of subconditions should be": "VŠETKY čiastkové podmienky by mali byť",
"ANY of subconditions should be": "AKÉKOĽVEK z čiastkových podmienok by malo byť",
"ON": "ON",
"AND": "A",
"OR": "ALEBO",
"ALL subconditions should meet the requirement": "VŠETKY čiastkové podmienky by mali spĺňať požiadavku",
"ANY subconditions should meet the requirement": "AKÉKOĽVEK čiastkové podmienky by mali spĺňať požiadavku",
"Condition Text": "Podmienka Text",
"Condition": "Podmienka",
"Event": "Udalosť",
"Event description": "Popis udalosti",
"is": "je",
"is not": "nie je",
"equals or greater than": "sa rovná alebo je väčšia ako",
"equals or less than": "rovná sa alebo je menšia ako",
"contains": "obsahuje",
"does not contain": "neobsahuje",
"greater than": "väčší než",
"less than": "menej ako",
"is one of": "je jeden z",
"is not one of": "nie je jedným z",
"Unknown Attribute": "Neznámy atribút",
"Unknown attribute selected": "Bol vybratý neznámy atribút",
"Condition Type": "Typ stavu",
"Required Value": "Požadovaná hodnota",
"Selected Records": "Vybrané záznamy",
"No records added": "Neboli pridané žiadne záznamy",
"Attribute": "Atribút",
"Operator": "Operátor",
"Value": "Hodnota",
"Delete this notification rule?": "Chcete odstrániť toto pravidlo upozornení?",
"New Notification Rule": "Nové pravidlo oznamovania",
"Do you really want to delete this action?": "Naozaj chcete odstrániť túto akciu?",
"Add Action": "Pridať akciu",
"These variables are available": "Tieto premenné sú k dispozícii",
"Click or drag these in to the content area": "Kliknite alebo presuňte ich do oblasti obsahu",
"This action does not provide any variables.": "Táto akcia neposkytuje žiadne premenné.",
"Delete Condition": "Odstrániť podmienku",
"Add Condition": "Pridať podmienku",
"Do you really want to delete this condition?": "Naozaj chcete odstrániť túto podmienku?",
"Please select a condition": "Vyberte podmienku"
}

View File

@ -0,0 +1,61 @@
{
"Notify": "Obvestiti",
"Notification Services": "Storitve obveščanja",
"Notification Rules": "Pravila obveščanja",
"Manage the events and actions that trigger notifications.": "Upravljajte dogodke in dejanja, ki sprožijo obvestila.",
"Name": "ime",
"Code": "Koda",
"Notification Rule": "Pravilo obveščanja",
"Add Notification Rule": "Dodajte pravilo obveščanja",
"Schedule": "Urnik",
"Note that scheduling might not work for certain queue driver configurations.": "Upoštevajte, da razporejanje morda ne bo delovalo za določene konfiguracije gonilnikov čakalne vrste.",
"Learn more": "Nauči se več",
"The configured queue driver does not support delayed execution.": "Konfiguriran gonilnik čakalne vrste ne podpira zakasnjenega izvajanja.",
"Notifications management": "Upravljanje obvestil",
"Return to Notifications": "Nazaj na Obvestila",
"Are you sure?": "Ali si prepričan?",
"Action": "Ukrep",
"Action Description": "Opis dejanja",
"Compound Condition": "Sestavljeno stanje",
"ALL of subconditions should be": "VSI podpogoji bi morali biti",
"ANY of subconditions should be": "KOT KOLI od podpogojev bi moral biti",
"ON": "VKLOPLJENO",
"AND": "IN",
"OR": "ALI",
"ALL subconditions should meet the requirement": "VSI podpogoji morajo izpolnjevati zahtevo",
"ANY subconditions should meet the requirement": "VSAKI podpogoji bi morali izpolnjevati zahtevo",
"Condition Text": "Besedilo pogoja",
"Condition": "Stanje",
"Event": "Dogodek",
"Event description": "Opis dogodka",
"is": "je",
"is not": "ni",
"equals or greater than": "enak ali večji od",
"equals or less than": "enako ali manjše od",
"contains": "vsebuje",
"does not contain": "ne vsebuje",
"greater than": "večji kot",
"less than": "manj kot",
"is one of": "je eden od",
"is not one of": "ni eden od",
"Unknown Attribute": "Neznan atribut",
"Unknown attribute selected": "Neznan atribut je izbran",
"Condition Type": "Vrsta pogoja",
"Required Value": "Zahtevana vrednost",
"Selected Records": "Izbrani zapisi",
"No records added": "Dodan ni noben zapis",
"Attribute": "atribut",
"Operator": "Operater",
"Value": "vrednost",
"Delete this notification rule?": "Želite izbrisati to pravilo obveščanja?",
"New Notification Rule": "Novo pravilo obveščanja",
"Do you really want to delete this action?": "Ali res želite izbrisati to dejanje?",
"Add Action": "Dodaj dejanje",
"These variables are available": "Te spremenljivke so na voljo",
"Click or drag these in to the content area": "Kliknite ali povlecite jih v območje vsebine",
"This action does not provide any variables.": "To dejanje ne zagotavlja nobenih spremenljivk.",
"Delete Condition": "Izbriši pogoj",
"Add Condition": "Dodaj pogoj",
"Do you really want to delete this condition?": "Ali res želite izbrisati ta pogoj?",
"Please select a condition": "Prosimo, izberite pogoj"
}

View File

@ -0,0 +1,61 @@
{
"Notify": "Notify",
"Notification Services": "Notification Services",
"Notification Rules": "Notification Rules",
"Manage the events and actions that trigger notifications.": "Manage the events and actions that trigger notifications.",
"Name": "Name",
"Code": "Code",
"Notification Rule": "Notification Rule",
"Add Notification Rule": "Add Notification Rule",
"Schedule": "Schedule",
"Note that scheduling might not work for certain queue driver configurations.": "Note that scheduling might not work for certain queue driver configurations.",
"Learn more": "Learn more",
"The configured queue driver does not support delayed execution.": "The configured queue driver does not support delayed execution.",
"Notifications management": "Notifications management",
"Return to Notifications": "Return to Notifications",
"Are you sure?": "Are you sure?",
"Action": "Action",
"Action Description": "Action Description",
"Compound Condition": "Compound Condition",
"ALL of subconditions should be": "ALL of subconditions should be",
"ANY of subconditions should be": "ANY of subconditions should be",
"ON": "ON",
"AND": "AND",
"OR": "OR",
"ALL subconditions should meet the requirement": "ALL subconditions should meet the requirement",
"ANY subconditions should meet the requirement": "ANY subconditions should meet the requirement",
"Condition Text": "Condition Text",
"Condition": "Condition",
"Event": "Event",
"Event description": "Event description",
"is": "is",
"is not": "is not",
"equals or greater than": "equals or greater than",
"equals or less than": "equals or less than",
"contains": "contains",
"does not contain": "does not contain",
"greater than": "greater than",
"less than": "less than",
"is one of": "is one of",
"is not one of": "is not one of",
"Unknown Attribute": "Unknown Attribute",
"Unknown attribute selected": "Unknown attribute selected",
"Condition Type": "Condition Type",
"Required Value": "Required Value",
"Selected Records": "Selected Records",
"No records added": "No records added",
"Attribute": "Attribute",
"Operator": "Operator",
"Value": "Value",
"Delete this notification rule?": "Delete this notification rule?",
"New Notification Rule": "New Notification Rule",
"Do you really want to delete this action?": "Do you really want to delete this action?",
"Add Action": "Add Action",
"These variables are available": "These variables are available",
"Click or drag these in to the content area": "Click or drag these in to the content area",
"This action does not provide any variables.": "This action does not provide any variables.",
"Delete Condition": "Delete Condition",
"Add Condition": "Add Condition",
"Do you really want to delete this condition?": "Do you really want to delete this condition?",
"Please select a condition": "Please select a condition"
}

View File

@ -0,0 +1,80 @@
{
"Notify": "通知",
"Notification Services": "通知服务",
"Notification Rules": "通知规则",
"Manage the events and actions that trigger notifications.": "管理触发通知的事件及动作",
"Settings": "设置",
"Actions": "操作",
"Conditions": "条件",
"Active": "活动",
"Description": "描述",
"API Code": "API 代码",
"Compound condition": "复合条件",
"Execution condition": "执行条件",
"Application environment": "应用环境",
"Request context": "请求上下文",
"Active theme": "活动主题",
"Visitor locale": "访客语言环境",
"Name": "名称",
"Code": "代码",
"Notification Rule": "通知规则",
"Add Notification Rule": "添加通知规则",
"Schedule": "时间安排",
"None": "无",
"Delay execution": "延迟执行",
"Seconds": "秒",
"Minutes": "分",
"Hours": "时",
"Days": "天",
"Months": "月",
"Note that scheduling might not work for certain queue driver configurations.": "请注意,对于某些队列驱动程序配置,调度可能不起作用",
"Learn more": "了解更多",
"The configured queue driver does not support delayed execution.": "配置的队列驱动不支持延迟执行",
"Notifications management": "通知管理",
"Return to Notifications": "返回通知",
"Are you sure?": "你确定吗?",
"Action": "操作",
"Action Description": "操作描述",
"Compound Condition": "复合条件",
"ALL of subconditions should be": "所有子条件都应该是",
"ANY of subconditions should be": "任意一个子条件应该是",
"ON": "在",
"AND": "并且",
"OR": "或者",
"ALL subconditions should meet the requirement": "满足所有子条件",
"ANY subconditions should meet the requirement": "满足任意一个子条件",
"Condition Text": "条件文本",
"Condition": "条件",
"Event": "事件",
"Event description": "事件描述",
"is": "是",
"is not": "不是",
"equals or greater than": "等于或大于",
"equals or less than": "等于或小于",
"contains": "包含",
"does not contain": "不包含",
"greater than": "大于",
"less than": "小于",
"is one of": "是其中之一",
"is not one of": "不是其中之一",
"Unknown Attribute": "未知属性",
"Unknown attribute selected": "选择了未知属性",
"Condition Type": "条件类型",
"Required Value": "必填值",
"Selected Records": "选定记录",
"No records added": "未选定记录",
"Attribute": "属性",
"Operator": "操作",
"Value": "值",
"Delete this notification rule?": "要删除此通知规则吗?",
"New Notification Rule": "新增通知规则",
"Do you really want to delete this action?": "您真的要删除此操作吗?",
"Add Action": "添加操作",
"These variables are available": "这些变量可用",
"Click or drag these in to the content area": "单击或将它们拖入内容区域",
"This action does not provide any variables.": "此操作不提供任何变量",
"Delete Condition": "删除条件",
"Add Condition": "添加条件",
"Do you really want to delete this condition?": "您真的要删除此条件吗?",
"Please select a condition": "请选择条件"
}

View File

@ -0,0 +1,114 @@
<?php namespace RainLab\Notify\Models;
use Model;
use Markdown;
/**
* Notification Model stored in the database
*/
class Notification extends Model
{
/**
* Indicates if the IDs are auto-incrementing.
*
* @var bool
*/
public $incrementing = false;
/**
* @var string The database table used by the model.
*/
public $table = 'rainlab_notify_notifications';
/**
* @var array Guarded fields
*/
protected $guarded = [];
/**
* @var array Fillable fields
*/
protected $fillable = [];
/**
* @var array List of attribute names which are json encoded and decoded from the database.
*/
protected $jsonable = ['data'];
/**
* @var array List of datetime attributes to convert to an instance of Carbon/DateTime objects.
*/
protected $dates = ['read_at'];
/**
* The accessors to append to the model's array form.
*
* @var array
*/
protected $appends = ['parsed_body'];
/**
* @var array Relations
*/
public $morphTo = [
'notifiable' => [],
];
/**
* Mark the notification as read.
*
* @return void
*/
public function markAsRead()
{
if (is_null($this->read_at)) {
$this->forceFill(['read_at' => $this->freshTimestamp()])->save();
}
}
/**
* Determine if a notification has been read.
*
* @return bool
*/
public function read()
{
return $this->read_at !== null;
}
/**
* Determine if a notification has not been read.
*
* @return bool
*/
public function unread()
{
return $this->read_at === null;
}
/**
* Get the entity's unread notifications.
*/
public function scopeApplyUnread($query)
{
return $query->whereNull('read_at');
}
/**
* Get the entity's read notifications.
*/
public function scopeApplyRead($query)
{
return $query->whereNotNull('read_at');
}
/**
* Get the parsed body of the announcement.
*
* @return string
*/
public function getParsedBodyAttribute()
{
return Markdown::parse($this->body);
}
}

View File

@ -0,0 +1,289 @@
<?php namespace RainLab\Notify\Models;
use Lang;
use Model;
use RainLab\Notify\Classes\EventBase;
use RainLab\Notify\Classes\ConditionBase;
/**
* Notification rule
*
* @package rainlab\notify
* @author Alexey Bobkov, Samuel Georges
*/
class NotificationRule extends Model
{
use \October\Rain\Database\Traits\Purgeable;
use \October\Rain\Database\Traits\Validation;
/**
* @var string The database table used by the model.
*/
protected $table = 'rainlab_notify_notification_rules';
/**
* @var array Guarded fields
*/
protected $guarded = ['config_data'];
/**
* @var array Fillable fields
*/
protected $fillable = [];
/**
* @var array List of attribute names which should not be saved to the database.
*/
protected $purgeable = ['event_name'];
/**
* @var array The rules to be applied to the data.
*/
public $rules = [
'name' => 'required'
];
/**
* @var array Relations
*/
public $hasMany = [
'rule_conditions' => [
RuleCondition::class,
'key' => 'rule_host_id',
'conditions' => 'rule_parent_id is null',
'delete' => true
],
'rule_actions' => [
RuleAction::class,
'key' => 'rule_host_id',
'delete' => true
],
];
/**
* Kicks off this notification rule, fires the event to obtain its parameters,
* checks the rule conditions evaluate as true, then spins over each action.
*/
public function triggerRule()
{
$params = $this->getEventObject()->getParams();
$rootCondition = $this->rule_conditions->first();
if ($rootCondition && !$rootCondition->getConditionObject()->isTrue($params)) {
return false;
}
foreach ($this->rule_actions as $action) {
$action->setRelation('notification_rule', $this);
$action->triggerAction($params);
}
}
/**
* Returns extra conditions provided by the event.
* @return array
*/
public function getExtraConditionRules()
{
$rules = [];
$classes = $this->getEventObject()->defineConditions();
foreach ($classes as $class) {
$rules[$class] = new $class;
}
return $rules;
}
/**
* Extends this class with the event class
* @param string $class Class name
* @return boolean
*/
public function applyEventClass($class = null)
{
if (!$class) {
$class = $this->class_name;
}
if (!$class) {
return false;
}
if (!$this->isClassExtendedWith($class)) {
$this->extendClassWith($class);
}
$this->class_name = $class;
$this->event_name = Lang::get(array_get($this->eventDetails(), 'name', 'Unknown'));
return true;
}
/**
* Returns the event class extension object.
* @return \RainLab\Notify\Classes\NotificationEvent
*/
public function getEventObject()
{
$this->applyEventClass();
return $this->asExtension($this->getEventClass());
}
public function getEventClass()
{
return $this->class_name;
}
//
// Events
//
public function afterFetch()
{
$this->applyEventClass();
}
public function beforeValidate()
{
if (!$this->applyEventClass()) {
return;
}
}
//
// Scopes
//
public function scopeApplyEnabled($query)
{
return $query->where('is_enabled', true);
}
public function scopeApplyClass($query, $class)
{
if (!is_string($class)) {
$class = get_class($class);
}
return $query->where('class_name', $class);
}
//
// Presets
//
/**
* Returns an array of rule codes and descriptions.
* @return array
*/
public static function listRulesForEvent($eventClass)
{
$results = [];
$dbRules = self::applyClass($eventClass)->get();
$presets = (array) EventBase::findEventPresetsByClass($eventClass);
foreach ($dbRules as $dbRule) {
if ($dbRule->code) {
unset($presets[$dbRule->code]);
}
if ($dbRule->is_enabled) {
$results[] = $dbRule;
}
}
foreach ($presets as $code => $preset) {
if ($newPreset = self::createFromPreset($code, $preset)) {
$results[] = $newPreset;
}
}
return $results;
}
/**
* Syncronise all file-based presets to the database.
* @return void
*/
public static function syncAll()
{
$presets = (array) EventBase::findEventPresets();
$dbRules = self::where('code', '!=', '')->whereNotNull('code')->lists('is_custom', 'code');
$newRules = array_diff_key($presets, $dbRules);
/*
* Clean up non-customized templates
*/
foreach ($dbRules as $code => $isCustom) {
if ($isCustom) {
continue;
}
if (!array_key_exists($code, $presets) && ($record = self::whereCode($code)->first())) {
$record->delete();
}
}
/*
* Create new rules
*/
foreach ($newRules as $code => $preset) {
self::createFromPreset($code, $preset);
}
}
public static function createFromPreset($code, $preset)
{
$actions = array_get($preset, 'items');
if (!$actions || !is_array($actions)) {
return;
}
$newRule = new self;
$newRule->code = $code;
$newRule->is_enabled = 1;
$newRule->is_custom = 0;
$newRule->name = array_get($preset, 'name');
$newRule->class_name = array_get($preset, 'event');
$newRule->forceSave();
// Add the actions
foreach ($actions as $action) {
$params = array_except($action, 'action');
$newAction = new RuleAction;
$newAction->class_name = array_get($action, 'action');
$newAction->notification_rule = $newRule;
$newAction->fill($params);
$newAction->forceSave();
}
// Add the conditions
$conditions = array_get($preset, 'conditions');
if (!$conditions || !is_array($conditions)) {
return $newRule;
}
// Create the root condition
$rootCondition = new RuleCondition();
$rootCondition->rule_host_type = ConditionBase::TYPE_ANY;
$rootCondition->class_name = $rootCondition->getRootConditionClass();
$rootCondition->notification_rule = $newRule;
$rootCondition->save();
// Add the sub conditions
foreach ($conditions as $condition) {
$params = array_except($condition, 'condition');
$newCondition = new RuleCondition();
$newCondition->class_name = array_get($condition, 'condition');
$newCondition->parent = $rootCondition;
$newCondition->fill($params);
$newCondition->forceSave();
}
return $newRule;
}
}

View File

@ -0,0 +1,201 @@
<?php namespace Rainlab\Notify\Models;
use Model;
use Exception;
use Queue;
use SystemException;
use Rainlab\Notify\Classes\ScheduledAction;
/**
* RuleAction Model
*/
class RuleAction extends Model
{
use \October\Rain\Database\Traits\Validation;
/**
* @var string The database table used by the model.
*/
public $table = 'rainlab_notify_rule_actions';
/**
* @var array Guarded fields
*/
protected $guarded = [];
/**
* @var array Fillable fields
*/
protected $fillable = [];
/**
* @var array The rules to be applied to the data.
*/
public $rules = [];
/**
* @var array List of attribute names which are json encoded and decoded from the database.
*/
protected $jsonable = ['config_data'];
/**
* @var array Relations
*/
public $belongsTo = [
'notification_rule' => [NotificationRule::class, 'key' => 'rule_host_id'],
];
public function triggerAction($params, $scheduled = true)
{
try {
$actionObject = $this->getActionObject();
// Check if action is muted in which case we don't proceed sending a notification
if (method_exists($actionObject, 'isMuted') && $actionObject->isMuted()) {
return;
}
// Apply action schedule
if ($scheduled && $schedule = $this->getSchedule()) {
// We delay the execution using Queues. When dequeued
// ScheduledAction will call this triggerAction
// function with $scheduled=false
Queue::later($schedule, new ScheduledAction($this, $params));
}
else {
// We trigger the action
$actionObject->triggerAction($params);
}
}
catch (Exception $ex) {
// We could log the error here, for now we should suppress
// any exceptions to let other actions proceed as normal
traceLog('Error with ' . $this->getActionClass());
traceLog($ex);
}
}
/**
* Extends this model with the action class
* @param string $class Class name
* @return boolean
*/
public function applyActionClass($class = null)
{
if (!$class) {
$class = $this->class_name;
}
if (!$class) {
return false;
}
if (!$this->isClassExtendedWith($class)) {
$this->extendClassWith($class);
}
$this->class_name = $class;
return true;
}
public function beforeSave()
{
$this->setCustomData();
}
public function afterSave()
{
// Make sure that this record is removed from the DB after being removed from a rule
$removedFromRule = $this->rule_host_id === null && $this->getOriginal('rule_host_id');
if ($removedFromRule && !$this->notification_rule()->withDeferred(post('_session_key'))->exists()) {
$this->delete();
}
}
public function applyCustomData()
{
$this->setCustomData();
$this->loadCustomData();
}
protected function loadCustomData()
{
$this->setRawAttributes((array) $this->getAttributes() + (array) $this->config_data, true);
}
protected function setCustomData()
{
if (!$actionObj = $this->getActionObject()) {
throw new SystemException(sprintf('Unable to find action object [%s]', $this->getActionClass()));
}
/*
* Spin over each field and add it to config_data
*/
$metaAttributes = [
'action_text',
'schedule_type',
'schedule_delay',
'schedule_delay_factor',
];
$formAttributes = [];
$config = $actionObj->getFieldConfig();
if (isset($config->fields)) {
$formAttributes = array_keys($config->fields);
}
$fieldAttributes = array_merge($formAttributes, $metaAttributes);
$dynamicAttributes = array_only($this->getAttributes(), $fieldAttributes);
$this->config_data = $dynamicAttributes;
$this->setRawAttributes(array_except($this->getAttributes(), $fieldAttributes));
}
public function afterFetch()
{
$this->applyActionClass();
$this->loadCustomData();
}
public function getText()
{
if (strlen($this->action_text)) {
return $this->action_text;
}
if ($actionObj = $this->getActionObject()) {
return $actionObj->getText();
}
}
public function getSchedule()
{
if ($this->schedule_type !== 'delayed') {
return false;
}
if (!is_numeric($this->schedule_delay) || !is_numeric($this->schedule_delay_factor)) {
return false;
}
$delay = (int) $this->schedule_delay;
$delay_factor = (int) $this->schedule_delay_factor;
return abs($delay * $delay_factor);
}
public function getActionObject()
{
$this->applyActionClass();
return $this->asExtension($this->getActionClass());
}
public function getActionClass()
{
return $this->class_name;
}
}

View File

@ -0,0 +1,172 @@
<?php namespace RainLab\Notify\Models;
use Model;
use RainLab\Notify\Classes\CompoundCondition;
use RainLab\Notify\Interfaces\CompoundCondition as CompoundConditionInterface;
use SystemException;
/**
* ConditionRule Model
*/
class RuleCondition extends Model
{
use \October\Rain\Database\Traits\Validation;
/**
* @var string The database table used by the model.
*/
public $table = 'rainlab_notify_rule_conditions';
/**
* @var array Guarded fields
*/
protected $guarded = [];
/**
* @var array Fillable fields
*/
protected $fillable = [];
/**
* @var array The rules to be applied to the data.
*/
public $rules = [];
/**
* @var array List of attribute names which are json encoded and decoded from the database.
*/
protected $jsonable = ['config_data'];
/**
* @var array Relations
*/
public $hasMany = [
'children' => [self::class, 'key' => 'rule_parent_id', 'delete' => true],
];
public $belongsTo = [
'parent' => [self::class, 'key' => 'rule_parent_id'],
'notification_rule' => [NotificationRule::class, 'key'=>'rule_host_id']
];
public function filterFields($fields, $context)
{
/*
* Let the condition contribute
*/
$this->getConditionObject()->setFormFields($fields);
}
/**
* Extends this model with the condition class
* @param string $class Class name
* @return boolean
*/
public function applyConditionClass($class = null)
{
if (!$class) {
$class = $this->class_name;
}
if (!$class) {
return false;
}
if (!$this->isClassExtendedWith($class)) {
$this->extendClassWith($class);
}
$this->class_name = $class;
return true;
}
public function beforeSave()
{
$this->setCustomData();
}
public function applyCustomData()
{
$this->setCustomData();
$this->loadCustomData();
}
protected function loadCustomData()
{
$this->setRawAttributes((array) $this->getAttributes() + (array) $this->config_data, true);
}
protected function setCustomData()
{
/*
* Let the condition contribute
*/
$this->getConditionObject()->setCustomData($this);
/*
* Spin over each field and add it to config_data
*/
$config = $this->getFieldConfig();
if (!isset($config->fields)) {
throw new SystemException('Condition class has no fields.');
}
$staticAttributes = ['condition_text'];
$fieldAttributes = array_merge($staticAttributes, array_keys($config->fields));
$dynamicAttributes = array_only($this->getAttributes(), $fieldAttributes);
$this->config_data = $dynamicAttributes;
$this->setRawAttributes(array_except($this->getAttributes(), $fieldAttributes));
}
public function afterFetch()
{
$this->applyConditionClass();
$this->loadCustomData();
}
public function afterSave()
{
// Make sure that this record is removed from the DB after being removed from a rule
$removedFromRule = $this->rule_parent_id === null && $this->getOriginal('rule_parent_id');
if ($removedFromRule && !$this->notification_rule()->withDeferred(post('_session_key'))->exists()) {
$this->delete();
}
}
public function getText()
{
if (strlen($this->condition_text)) {
return $this->condition_text;
}
if ($conditionObj = $this->getConditionObject()) {
return $conditionObj->getText();
}
}
public function isCompound()
{
return $this->getConditionObject() instanceof CompoundConditionInterface;
}
public function getConditionObject()
{
$this->applyConditionClass();
return $this->asExtension($this->getConditionClass());
}
public function getConditionClass()
{
return $this->class_name;
}
public function getRootConditionClass()
{
return CompoundCondition::class;
}
}

View File

@ -0,0 +1,13 @@
# ===================================
# Column Definitions
# ===================================
columns:
name:
label: Name
searchable: true
code:
label: Code
searchable: true

View File

@ -0,0 +1,47 @@
# ===================================
# Field Definitions
# ===================================
fields:
name:
label: Name
placeholder: New notification rule name
attributes:
autofocus: 1
toolbar:
type: partial
path: form_toolbar
cssClass: collapse-visible
tabs:
stretch: true
cssClass: master-area
paneCssClass:
'Actions': 'pane-compact'
fields:
rule_actions:
type: RainLab\Notify\FormWidgets\ActionBuilder
tab: Actions
rule_conditions:
type: RainLab\Notify\FormWidgets\ConditionBuilder
tab: Conditions
is_enabled:
label: Active
type: checkbox
tab: Settings
default: true
description:
label: Description
type: textarea
size: tiny
tab: Settings
code:
label: API Code
span: auto
tab: Settings

View File

@ -0,0 +1,8 @@
# ===================================
# List Column Definitions
# ===================================
columns:
id:
label: ID
searchable: true

View File

@ -0,0 +1,8 @@
# ===================================
# Form Field Definitions
# ===================================
fields:
id:
label: ID
disabled: true

View File

@ -0,0 +1,30 @@
# ===================================
# Form Field Definitions
# ===================================
fields:
schedule_type:
type: dropdown
options:
disabled: None
delayed: Delay execution
schedule_delay:
type: number
span: left
trigger:
action: show
field: schedule_type
condition: value[delayed]
schedule_delay_factor:
type: dropdown
span: right
options:
1: Seconds
60: Minutes
3600: Hours
86400: Days
2592000: Months
trigger:
action: show
field: schedule_type
condition: value[delayed]

View File

@ -0,0 +1,136 @@
<?php namespace RainLab\Notify\NotifyRules;
use Cms\Classes\Theme;
use RainLab\Notify\Classes\ConditionBase;
class ExecutionContextCondition extends ConditionBase
{
protected $operators = [
'is' => 'is',
'is_not' => 'is not',
];
public function getName()
{
return 'Event is triggered from environment';
}
public function getTitle()
{
return 'Execution context';
}
public function getText()
{
$host = $this->host;
$value = $host->value;
$attribute = $host->subcondition;
$subconditions = $this->getSubconditionOptions();
$result = array_get($subconditions, $attribute, 'Execution context');
$result .= ' <span class="operator">'.array_get($this->operators, $host->operator, $host->operator).'</span> ';
if ($attribute == 'locale' || $attribute == 'environment') {
$result .= strtolower($value) ?: '?';
}
elseif ($value) {
$options = $this->getValueOptions();
$result .= strtolower(array_get($options, $value));
}
else {
$result .= '?';
}
return $result;
}
/**
* Returns a title to use for grouping subconditions
* in the Create Condition drop-down menu
*/
public function getGroupingTitle()
{
return 'Execution context';
}
public function listSubconditions()
{
return array_flip($this->getSubconditionOptions());
}
public function initConfigData($host)
{
$host->operator = 'is';
}
public function setFormFields($fields)
{
$attribute = $fields->subcondition->value;
if ($attribute == 'locale' || $attribute == 'environment') {
$fields->value->type = 'text';
}
else {
$fields->value->type = 'dropdown';
}
}
public function getValueOptions()
{
$attribute = $this->host->subcondition;
$result = [];
if ($attribute == 'context') {
$result = [
'backend' => 'Back-end area',
'front' => 'Front-end website',
'console' => 'Command line interface',
];
}
if ($attribute == 'theme') {
foreach (Theme::all() as $theme) {
$result[$theme->getDirName()] = $theme->getDirName();
}
}
return $result;
}
public function getSubconditionOptions()
{
return [
'environment' => 'Application environment',
'context' => 'Request context',
'theme' => 'Active theme',
'locale' => 'Visitor locale',
];
}
public function getOperatorOptions()
{
return $this->operators;
}
/**
* Checks whether the condition is TRUE for specified parameters
* @param array $params
* @return bool
*/
public function isTrue(&$params)
{
$hostObj = $this->host;
$attribute = $hostObj->subcondition;
$conditionValue = $hostObj->value;
$conditionValue = trim(mb_strtolower($conditionValue));
if ($attribute == 'locale') {
return array_get($params, 'appLocale') == $conditionValue;
} else if ($attribute === 'environment') {
return $conditionValue === \App::environment();
}
return false;
}
}

View File

@ -0,0 +1,119 @@
<?php namespace RainLab\Notify\NotifyRules;
use Ramsey\Uuid\Uuid;
use RainLab\Notify\Classes\ActionBase;
use Illuminate\Database\Eloquent\Model as EloquentModel;
use ApplicationException;
class SaveDatabaseAction extends ActionBase
{
protected $tableDefinitions = [];
/**
* Returns information about this event, including name and description.
*/
public function actionDetails()
{
return [
'name' => 'Store in database',
'description' => 'Log event data in the notifications activity log',
'icon' => 'icon-database'
];
}
public function defineFormFields()
{
return 'fields.yaml';
}
public function getText()
{
if ($this->host->related_object) {
$label = array_get($this->getRelatedObjectOptions(), $this->host->related_object);
return 'Log event in the '.$label.' log';
}
return parent::getText();
}
/**
* Triggers this action.
* @param array $params
* @return void
*/
public function triggerAction($params)
{
if (
(!$definition = array_get($this->tableDefinitions, $this->host->related_object)) ||
(!$param = array_get($definition, 'param')) ||
(!$value = array_get($params, $param))
) {
throw new ApplicationException('Error evaluating the save database action: the related object is not found in the action parameters.');
}
if (!$value instanceof EloquentModel) {
// @todo Perhaps value is an ID or a model array,
// look up model $definition[class] from ID ...
}
$rule = $this->host->notification_rule;
$relation = array_get($definition, 'relation');
$value->$relation()->create([
'id' => Uuid::uuid4()->toString(),
'event_type' => $rule->getEventClass(),
'icon' => $this->host->icon,
'type' => $this->host->type,
'body' => $this->host->body,
'data' => $this->getData($params),
'read_at' => null,
]);
}
/**
* Get the data for the notification.
*
* @param array $notifiable
* @return array
*/
protected function getData($params)
{
// This should check for params that cannot be jsonable.
return $params;
}
public function getRelatedObjectOptions()
{
$result = [];
foreach ($this->tableDefinitions as $key => $definition) {
$result[$key] = array_get($definition, 'label');
}
return $result;
}
public function getTableDefinitions()
{
return $this->tableDefinitions;
}
public function addTableDefinition($options)
{
if (!$className = array_get($options, 'class')) {
throw new ApplicationException('Missing class name from table definition.');
}
$options = array_merge([
'label' => 'Undefined table',
'class' => null,
'param' => null,
'relation' => 'notifications',
], $options);
$keyName = $className . '@' . array_get($options, 'relation');
$this->tableDefinitions[$keyName] = $options;
}
}

View File

@ -0,0 +1,225 @@
<?php namespace RainLab\Notify\NotifyRules;
use Mail;
use Lang;
use Config;
use System\Models\MailTemplate;
use RainLab\Notify\Classes\ActionBase;
use Backend\Models\User as AdminUserModel;
use Backend\Models\UserGroup as AdminGroupModel;
use ApplicationException;
class SendMailTemplateAction extends ActionBase
{
public $recipientModes = [
'system' => 'System default',
'user' => 'User email address (if applicable)',
'sender' => 'Sender user email address (if applicable)',
'admin' => 'Back-end administrators',
'custom' => 'Specific email address',
];
/**
* Returns information about this event, including name and description.
*/
public function actionDetails()
{
return [
'name' => 'Compose a mail message',
'description' => 'Send a message to a recipient',
'icon' => 'icon-envelope'
];
}
/**
* Triggers this action.
* @param array $params
* @return void
*/
public function triggerAction($params)
{
$template = $this->host->mail_template;
$recipient = $this->getRecipientAddress($params);
$replyTo = $this->getReplyToAddress($params);
if (!$recipient || !$template) {
throw new ApplicationException('Missing valid recipient or mail template');
}
Mail::sendTo($recipient, $template, $params, function($message) use ($replyTo) {
if ($replyTo) {
$message->replyTo($replyTo);
}
});
}
/**
* Field configuration for the action.
*/
public function defineFormFields()
{
return 'fields.yaml';
}
/**
* Defines validation rules for the custom fields.
* @return array
*/
public function defineValidationRules()
{
return [
'mail_template' => 'required',
'send_to_mode' => 'required',
];
}
public function getTitle()
{
if ($this->isAdminMode()) {
return 'Compose mail to administrators';
}
return parent::getTitle();
}
public function getActionIcon()
{
if ($this->isAdminMode()) {
return 'icon-envelope-square';
}
return parent::getActionIcon();
}
public function getText()
{
$hostObj = $this->host;
$recipient = array_get($this->recipientModes, $hostObj->send_to_mode);
if ($this->isAdminMode()) {
if ($groupId = $this->host->send_to_admin) {
if ($group = AdminGroupModel::find($groupId)) {
$adminText = $group->name;
}
else {
$adminText = '?';
}
$adminText .= ' admin group';
}
else {
$adminText = 'all admins';
}
return sprintf(
'Send a message to %s using template %s',
$adminText,
$hostObj->mail_template
);
}
if ($hostObj->mail_template) {
return sprintf(
'Send a message to %s using template %s',
mb_strtolower($recipient),
$hostObj->mail_template
);
}
return parent::getText();
}
public function getSendToAdminOptions()
{
$options = ['' => '- All administrators -'];
$groups = AdminGroupModel::lists('name', 'id');
return $options + $groups;
}
public function getSendToModeOptions()
{
$modes = $this->recipientModes;
unset($modes['system']);
return $modes;
}
public function getReplyToModeOptions()
{
$modes = $this->recipientModes;
unset($modes['admin']);
return $modes;
}
public function getMailTemplateOptions()
{
$codes = array_keys(MailTemplate::listAllTemplates());
$result = array_combine($codes, $codes);
// Wrap to prevent collisions with language keys
array_walk($result, function(&$value, $key) {
$value = "[$value]";
});
return $result;
}
protected function getReplyToAddress($params)
{
$mode = $this->host->reply_to_mode;
if ($mode == 'custom') {
return $this->host->reply_to_custom;
}
if ($mode == 'user' || $mode == 'sender') {
$obj = array_get($params, $mode);
return $obj->email;
}
}
protected function getRecipientAddress($params)
{
$mode = $this->host->send_to_mode;
if ($mode == 'custom') {
return $this->host->send_to_custom;
}
if ($mode == 'system') {
$name = Config::get('mail.from.name', 'Your Site');
$address = Config::get('mail.from.address', 'admin@domain.tld');
return [$address => $name];
}
if ($mode == 'admin') {
if ($groupId = $this->host->send_to_admin) {
if (!$group = AdminGroupModel::find($groupId)) {
throw new ApplicationException('Unable to find admin group with ID: '.$groupId);
}
return $group->users->lists('full_name', 'email');
}
else {
return AdminUserModel::all()->lists('full_name', 'email');
}
}
if ($mode == 'user' || $mode == 'sender') {
return array_get($params, $mode);
}
}
protected function isAdminMode()
{
return $this->host->send_to_mode == 'admin';
}
}

View File

@ -0,0 +1,21 @@
# ===================================
# Field Definitions
# ===================================
fields:
subcondition:
label: Attribute
span: auto
type: dropdown
operator:
label: Operator
span: auto
type: dropdown
dependsOn: subcondition
value:
label: Value
dependsOn: [subcondition, operator]
type: dropdown

View File

@ -0,0 +1,8 @@
# ===================================
# Field Definitions
# ===================================
fields:
related_object:
label: Related object
type: dropdown

View File

@ -0,0 +1,42 @@
# ===================================
# Field Definitions
# ===================================
fields:
mail_template:
label: Mail template
type: dropdown
placeholder: Select template
send_to_mode:
label: Send to
type: radio
span: left
send_to_custom:
cssClass: radio-align
trigger:
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]
reply_to_mode:
label: Reply-to address
type: radio
span: left
reply_to_custom:
cssClass: radio-align
trigger:
action: show
field: reply_to_mode
condition: value[custom]

View File

@ -0,0 +1,29 @@
<?php namespace RainLab\Notify\Updates;
use Schema;
use October\Rain\Database\Updates\Migration;
class CreateNotificationRulesTable extends Migration
{
public function up()
{
Schema::create('rainlab_notify_notification_rules', function ($table) {
$table->engine = 'InnoDB';
$table->increments('id');
$table->string('name')->nullable();
$table->string('code')->index()->nullable();
$table->string('class_name')->nullable();
$table->text('description')->nullable();
$table->mediumText('config_data')->nullable();
$table->mediumText('condition_data')->nullable();
$table->boolean('is_enabled')->default(0);
$table->boolean('is_custom')->default(1);
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('rainlab_notify_notification_rules');
}
}

View File

@ -0,0 +1,28 @@
<?php namespace RainLab\Notify\Updates;
use Schema;
use October\Rain\Database\Schema\Blueprint;
use October\Rain\Database\Updates\Migration;
class CreateNotificationsTable extends Migration
{
public function up()
{
Schema::create('rainlab_notify_notifications', function(Blueprint $table) {
$table->uuid('id')->primary();
$table->string('event_type');
$table->morphs('notifiable');
$table->string('icon')->nullable();
$table->string('type')->nullable();
$table->text('body')->nullable();
$table->mediumText('data');
$table->timestamp('read_at')->nullable();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('rainlab_notify_notifications');
}
}

View File

@ -0,0 +1,25 @@
<?php namespace RainLab\Notify\Updates;
use Schema;
use October\Rain\Database\Schema\Blueprint;
use October\Rain\Database\Updates\Migration;
class CreateRuleActionsTable extends Migration
{
public function up()
{
Schema::create('rainlab_notify_rule_actions', function(Blueprint $table) {
$table->engine = 'InnoDB';
$table->increments('id');
$table->string('class_name')->nullable();
$table->mediumText('config_data')->nullable();
$table->integer('rule_host_id')->unsigned()->nullable()->index();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('rainlab_notify_rule_actions');
}
}

View File

@ -0,0 +1,29 @@
<?php namespace RainLab\Notify\Updates;
use Schema;
use October\Rain\Database\Schema\Blueprint;
use October\Rain\Database\Updates\Migration;
class CreateConditionsRulesTable extends Migration
{
public function up()
{
Schema::create('rainlab_notify_rule_conditions', function(Blueprint $table) {
$table->engine = 'InnoDB';
$table->increments('id');
$table->string('class_name')->nullable();
$table->mediumText('config_data')->nullable();
$table->string('condition_control_type', 100)->nullable();
$table->string('rule_host_type', 100)->nullable();
$table->integer('rule_host_id')->unsigned()->nullable()->index();
$table->integer('rule_parent_id')->unsigned()->nullable()->index();
$table->index(['rule_host_id', 'rule_host_type'], 'host_rule_id_type');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('rainlab_notify_rule_conditions');
}
}

View File

@ -0,0 +1,13 @@
v1.0.1:
- First version of Notify
- create_notifications_table.php
- create_notification_rules_table.php
- create_rule_conditions_table.php
- create_rule_actions_table.php
v1.0.2: Fixes crashing bug.
v1.0.3: Added Turkish & Russian translations, various bug fixes.
v1.1.0: Fixes support for October CMS 2.0
v1.1.1: Fixes missing template bug when saving notification rule
v1.1.2: Fixes collisions with language keys in compose mail action
v1.2.0: Adds delayed execution to actions
v1.2.1: Improve support with October v3

View File

@ -61,6 +61,8 @@ class User extends UserBase
'transactions' => ['TPS\Birzha\Models\Transaction', 'softDelete' => true], 'transactions' => ['TPS\Birzha\Models\Transaction', 'softDelete' => true],
'exchangerequests' => ['TPS\Birzha\Models\Exchangerequest', 'softDelete' => true], 'exchangerequests' => ['TPS\Birzha\Models\Exchangerequest', 'softDelete' => true],
'sliders' => ['TPS\Birzha\Models\UserSliders', 'softDelete' => true], 'sliders' => ['TPS\Birzha\Models\UserSliders', 'softDelete' => true],
'favourites' => ['TPS\Birzha\Models\Favourites', 'softDelete' => true, 'table' => 'tps_birzha_favourites'],
'comments' => ['TPS\Birzha\Models\Comment','table' => 'tps_birzha_comments'],
]; ];

View File

@ -0,0 +1,19 @@
# MIT license
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,124 @@
<?php namespace RainLab\UserPlus;
use Yaml;
use File;
use System\Classes\PluginBase;
use RainLab\User\Models\User as UserModel;
use RainLab\Notify\Models\Notification as NotificationModel;
use RainLab\User\Controllers\Users as UsersController;
use RainLab\Notify\NotifyRules\SaveDatabaseAction;
use RainLab\User\Classes\UserEventBase;
/**
* UserPlus Plugin Information File
*/
class Plugin extends PluginBase
{
public $require = ['RainLab.User', 'RainLab.Location', 'RainLab.Notify'];
/**
* Returns information about this plugin.
*
* @return array
*/
public function pluginDetails()
{
return [
'name' => 'rainlab.userplus::lang.plugin.name',
'description' => 'rainlab.userplus::lang.plugin.description',
'author' => 'Alexey Bobkov, Samuel Georges',
'icon' => 'icon-user-plus',
'homepage' => 'https://github.com/rainlab/userplus-plugin'
];
}
public function boot()
{
$this->extendUserModel();
$this->extendUsersController();
$this->extendSaveDatabaseAction();
$this->extendUserEventBase();
}
public function registerComponents()
{
return [
\RainLab\UserPlus\Components\Notifications::class => 'notifications',
];
}
protected function extendUserModel()
{
UserModel::extend(function($model) {
$model->addFillable([
'phone',
'mobile',
'company',
'street_addr',
'city',
'zip'
]);
$model->implement[] = 'RainLab.Location.Behaviors.LocationModel';
$model->morphMany['notifications'] = [
NotificationModel::class,
'name' => 'notifiable',
'order' => 'created_at desc'
];
});
}
protected function extendUsersController()
{
UsersController::extendFormFields(function($widget) {
// Prevent extending of related form instead of the intended User form
if (!$widget->model instanceof UserModel) {
return;
}
$configFile = plugins_path('rainlab/userplus/config/profile_fields.yaml');
$config = Yaml::parse(File::get($configFile));
$widget->addTabFields($config);
});
}
public function registerNotificationRules()
{
return [
'events' => [],
'actions' => [],
'conditions' => [
\RainLab\UserPlus\NotifyRules\UserLocationAttributeCondition::class
],
'presets' => '$/rainlab/userplus/config/notify_presets.yaml',
];
}
protected function extendUserEventBase()
{
if (!class_exists(UserEventBase::class)) {
return;
}
UserEventBase::extend(function($event) {
$event->conditions[] = \RainLab\UserPlus\NotifyRules\UserLocationAttributeCondition::class;
});
}
protected function extendSaveDatabaseAction()
{
if (!class_exists(SaveDatabaseAction::class)) {
return;
}
SaveDatabaseAction::extend(function ($action) {
$action->addTableDefinition([
'label' => 'User activity',
'class' => UserModel::class,
'param' => 'user'
]);
});
}
}

View File

@ -0,0 +1,13 @@
# User Plus+ plugin
This plugin extends the [User plugin](http://octobercms.com/plugin/rainlab-user) with extra profile fields and features.
* Adds the following extra fields to a user: `phone`, `mobile`, `company`, `street_addr`, `city`, `zip`.
* A user can belong to a Country and/or State, sourced from the [Location plugin](http://octobercms.com/plugin/rainlab-location).
### Potential features
* A user can befriend other users via a friendship system.
* A user can earn "Experience Points" by performing predefined activities.
> Note these features may or may not be implemented in the future, but act only as an indicator of the plugin's potential.

View File

@ -0,0 +1,74 @@
.rainlab-userplus {
position: relative;
}
.rainlab-userplus .notifications-popover {
background: #fff;
border: 1px solid #f0f0f0;
position: absolute;
width: 480px;
z-index: 2;
right: 0;
display: none;
}
.rainlab-userplus.active .notifications-popover {
display: block;
}
.rainlab-userplus .notifications-header {
border-bottom: 1px solid #f0f0f0;
padding: 15px;
}
.rainlab-userplus .notifications-header h4 {
padding: 0;
margin: 0;
display: inline-block;
}
.rainlab-userplus .notifications-header .close,
.rainlab-userplus .notifications-header .mark-all-read {
float: right;
margin: 0 5px;
}
.rainlab-userplus .notifications-content .notifications-loading,
.notification_area .notifications-loading,
.accord_notification .notifications-loading {
padding: 20px;
font-size: 24px;
text-align: center;
}
.rainlab-userplus .notifications-content .no-notifications,
.notification_area .no-notifications,
.accord_notification .no-notifications {
text-align: center;
padding: 25px;
margin: 0;
}
.rainlab-userplus .notifications-content > ul {
padding: 0;
margin: 0;
list-style: none;
overflow: auto;
max-height: 400px;
}
.rainlab-userplus .notifications-content > ul > li {
position: relative;
padding: 10px 25px 10px 60px;
border-bottom: 1px solid #f9f9f9;
}
.rainlab-userplus .notifications-content > ul > li > i {
position: absolute;
left: 25px;
top: 12px;
font-size: 18px;
opacity: .5;
}
.rainlab-userplus .notifications-content > ul > li .parsed-body > *:last-child {
margin-bottom: 0;
}
.rainlab-userplus .notifications-content > ul > li .date {
white-space: nowrap;
opacity: .75;
}
.rainlab-userplus .notifications-footer {
border-top: 1px solid #f0f0f0;
padding: 15px;
text-align: center;
}

View File

@ -0,0 +1,13 @@
function toggleNotificationsPopover(el) {
if($(el).next().hasClass('notification_area')) {
$(el).request('onLoadNotifications', {
update: { '@notifications-list': '#notification_area' }
})
} else if($(el).next().hasClass('accord_notification')) {
$(el).request('onLoadNotifications', {
update: { '@notifications-list': '#accord_notification' }
})
}
}

Some files were not shown because too many files have changed in this diff Show More