This commit is contained in:
Kerim 2023-07-24 16:45:53 +05:00
parent 0516411193
commit 8af3e1d541
427 changed files with 1897 additions and 21754 deletions

View File

@ -1,41 +1,6 @@
<?php <?php
return [ return [
/*
'throttle' => [
/*
|--------------------------------------------------------------------------
| Enable throttling of Backend authentication attempts
|--------------------------------------------------------------------------
|
| If set to true, users will be given a limited number of attempts to sign
| in to the Backend before being blocked for a specified number of minutes.
|
*/
'enabled' => true,
/*
|--------------------------------------------------------------------------
| Failed Authentication Attempt Limit
|--------------------------------------------------------------------------
|
| Number of failed attempts allowed while trying to authenticate a user.
|
*/
'attemptLimit' => 5,
/*
|--------------------------------------------------------------------------
| Suspension Time
|--------------------------------------------------------------------------
|
| The number of minutes to suspend further attempts on authentication once
| the attempt limit is reached.
|
*/
'suspensionTime' => 15,
],
/*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Authentication Defaults | Authentication Defaults
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

126
config/authBackup.php Normal file
View File

@ -0,0 +1,126 @@
<?php
return [
'throttle' => [
/*
|--------------------------------------------------------------------------
| Enable throttling of Backend authentication attempts
|--------------------------------------------------------------------------
|
| If set to true, users will be given a limited number of attempts to sign
| in to the Backend before being blocked for a specified number of minutes.
|
*/
'enabled' => true,
/*
|--------------------------------------------------------------------------
| Failed Authentication Attempt Limit
|--------------------------------------------------------------------------
|
| Number of failed attempts allowed while trying to authenticate a user.
|
*/
'attemptLimit' => 5,
/*
|--------------------------------------------------------------------------
| Suspension Time
|--------------------------------------------------------------------------
|
| The number of minutes to suspend further attempts on authentication once
| the attempt limit is reached.
|
*/
'suspensionTime' => 15,
],
/*
|--------------------------------------------------------------------------
| Authentication Defaults
|--------------------------------------------------------------------------
|
| This option controls the default authentication "guard" and password
| reset options for your application. You may change these defaults
| as required, but they're a perfect start for most applications.
|
*/
'defaults' => [
'guard' => env('AUTH_DEFAULT_GUARD', 'web'),
'passwords' => env('AUTH_DEFAULT_PASSWORDS', 'users'),
],
/*
|--------------------------------------------------------------------------
| Authentication Guards
|--------------------------------------------------------------------------
|
| Next, you may define every authentication guard for your application.
| Of course, a great default configuration has been defined for you
| here which uses session storage and the Eloquent user provider.
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| Supported: "session", "token"
|
*/
'guards' => [
'web' => [
'driver' => env('AUTH_GUARDS_WEB_DRIVER', 'session'),
'provider' => env('AUTH_GUARDS_WEB_PROVIDER', 'users'),
],
'api' => [
'driver' => env('AUTH_GUARDS_API_DRIVER', 'token'),
'provider' => env('AUTH_GUARDS_API_PROVIDER', 'users'),
],
],
/*
|--------------------------------------------------------------------------
| User Providers
|--------------------------------------------------------------------------
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| If you have multiple user tables or models you may configure multiple
| sources which represent each model / table. These sources may then
| be assigned to any extra authentication guards you have defined.
|
| Supported: "database", "eloquent"
|
*/
'providers' => [
'users' => [
'driver' => env('AUTH_PROVIDERS_USERS_DRIVER', 'eloquent'),
'model' => env('AUTH_PROVIDERS_USERS_MODEL', '\RainLab\User\Models\User'),
],
],
/*
|--------------------------------------------------------------------------
| Resetting Passwords
|--------------------------------------------------------------------------
|
| Here you may set the options for resetting passwords including the view
| that is your password reset e-mail. You may also set the name of the
| table that maintains all of the reset tokens for your application.
|
| You may specify multiple password reset configurations if you have more
| than one user table or model in the application and you want to have
| separate password reset settings based on the specific user types.
|
| The expire time is the number of minutes that the reset token should be
| considered valid. This security feature keeps tokens short-lived so
| they have less time to be guessed. You may change this as needed.
|
*/
'passwords' => [
'users' => [
'provider' => env('AUTH_PASSWORDS_USERS_PROVIDER', 'users'),
'email' => env('AUTH_PASSWORDS_USERS_EMAIL', 'auth.emails.password'),
'table' => env('AUTH_PASSWORDS_USERS_TABLE', 'password_resets'),
'expire' => env('AUTH_PASSWORDS_USERS_EXPIRE', 60),
],
],
];

View File

@ -1,19 +0,0 @@
# 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

@ -1,69 +0,0 @@
<?php namespace RainLab\Notify;
use Backend;
use System\Classes\PluginBase;
use System\Classes\SettingsManager;
use TPS\Birzha\Events\MessageReceivedEvent;
use TPS\Birzha\Events\PaymentRecievedEvent;
use TPS\Birzha\Events\PaymentReviewedEvent;
use TPS\Birzha\Events\ProductRecievedEvent;
use TPS\Birzha\Events\ProductReviewedEvent;
/**
* The plugin.php file (called the plugin initialization script) defines the plugin information class.
*/
class Plugin extends PluginBase
{
public function pluginDetails()
{
return [
'name' => 'rainlab.notify::lang.plugin.name',
'description' => 'rainlab.notify::lang.plugin.description',
'author' => 'Alexey Bobkov, Samuel Georges',
'icon' => 'icon-bullhorn'
];
}
public function registerSettings()
{
return [
'notifications' => [
'label' => 'rainlab.notify::lang.notifications.menu_label',
'description' => 'rainlab.notify::lang.notifications.menu_description',
'category' => SettingsManager::CATEGORY_NOTIFICATIONS,
'icon' => 'icon-bullhorn',
'url' => Backend::url('rainlab/notify/notifications'),
'permissions' => ['rainlab.notify.manage_notifications'],
'order' => 600
],
];
}
public function registerNotificationRules()
{
return [
'groups' => [],
'events' => [
],
'actions' => [
\RainLab\Notify\NotifyRules\SaveDatabaseAction::class,
\RainLab\Notify\NotifyRules\SendMailTemplateAction::class,
],
'conditions' => [
\RainLab\Notify\NotifyRules\ExecutionContextCondition::class,
],
];
}
public function registerPermissions()
{
return [
'rainlab.notify.manage_notifications' => [
'tab' => SettingsManager::CATEGORY_NOTIFICATIONS,
'label' => 'rainlab.notify::lang.permissions.manage_notifications'
],
];
}
}

View File

@ -1,400 +0,0 @@
# 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

@ -1,168 +0,0 @@
<?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

@ -1,169 +0,0 @@
<?php namespace RainLab\Notify\Classes;
use RainLab\Notify\Interfaces\CompoundCondition as CompoundConditionInterface;
/**
* Compound Condition Class
*/
class CompoundCondition extends ConditionBase implements CompoundConditionInterface
{
/**
* Returns a condition title for displaying in the condition settings form
* @return string
*/
public function getTitle()
{
return 'Compound condition';
}
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;
}
/**
* 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 [];
}
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;
}
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

@ -1,211 +0,0 @@
<?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

@ -1,237 +0,0 @@
<?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

@ -1,59 +0,0 @@
<?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

@ -1,731 +0,0 @@
<?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;
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
//
public function getSubconditionOptions()
{
return $this->listModelAttributes();
}
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', 'October\Rain\Database\Traits\SimpleTree'];
$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', $host->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

@ -1,134 +0,0 @@
<?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

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

View File

@ -1,10 +0,0 @@
<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

@ -1,107 +0,0 @@
<?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>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">
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

@ -1,22 +0,0 @@
# ===================================
# 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

@ -1,25 +0,0 @@
{
"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

@ -1,137 +0,0 @@
<?php namespace RainLab\Notify\Controllers;
use Str;
use Lang;
use File;
use Mail;
use Flash;
use Backend;
use Redirect;
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

@ -1,41 +0,0 @@
<?= Form::open(['id' => 'addRuleEventForm']) ?>
<div class="modal-header">
<button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title"><?= e(trans('rainlab.notify::lang.notifications.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="list-preview">
<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>
</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

@ -1,49 +0,0 @@
<?= Form::open(['id' => 'addRuleGroupForm']) ?>
<div class="modal-header">
<button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title"><?= e(trans('rainlab.notify::lang.notifications.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="list-preview">
<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>
</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

@ -1,49 +0,0 @@
<?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 ?>
<!-- Preview -->
<a
href="<?= URL::to($pageUrl) ?>"
target="_blank"
class="btn btn-primary oc-icon-crosshairs <?php if (!false): ?>hide<?php endif ?>"
data-control="preview-button">
<?= e(trans('backend::lang.form.preview_title')) ?>
</a>
<?php if (!$isCreate): ?>
<!-- Delete -->
<button
type="button"
class="btn btn-default empty oc-icon-trash-o"
data-request="onDelete"
data-request-confirm="Delete this notification rule?"
data-control="delete-button"></button>
<?php endif ?>
</div>

View File

@ -1,9 +0,0 @@
<div data-control="toolbar">
<a
href="javascript:;"
data-control="popup"
data-handler="onLoadRuleGroupForm"
class="btn btn-primary oc-icon-plus">
New notification rule
</a>
</div>

View File

@ -1,16 +0,0 @@
# ===================================
# Form Behavior Config
# ===================================
name: rainlab.notify::lang.notifications.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

@ -1,20 +0,0 @@
# ===================================
# List Behavior Config
# ===================================
title: rainlab.notify::lang.notifications.menu_label
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

@ -1,31 +0,0 @@
<?php Block::put('breadcrumb') ?>
<ul>
<li><a href="<?= Backend::url('rainlab/notify/notifications') ?>"><?= e(trans('rainlab.notify::lang.notifications.menu_label')) ?></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(trans('rainlab.notify::lang.notifications.close_confirm')),
'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(trans('rainlab.notify::lang.notifications.return')) ?></a></p>
</div>
<?php endif ?>

View File

@ -1,30 +0,0 @@
<?php Block::put('breadcrumb') ?>
<ul>
<li><a href="<?= Backend::url('rainlab/notify/notifications') ?>"><?= e(trans('rainlab.notify::lang.notifications.menu_label')) ?></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(trans('rainlab.notify::lang.notifications.close_confirm')),
'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(trans('rainlab.notify::lang.notifications.return')) ?></a></p>
</div>
<?php endif ?>

View File

@ -1,350 +0,0 @@
<?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;
/**
* {@inheritDoc}
*/
public function init()
{
// $this->fillFromConfig([
// ]);
if ($widget = $this->makeActionFormWidget()) {
$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['formModel'] = $this->model;
$this->vars['actions'] = $this->getActions();
$this->vars['actionFormWidget'] = $this->actionFormWidget;
$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', []);
$action->fill($data);
$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);
$this->actionFormWidget->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 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

@ -1,454 +0,0 @@
<?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['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

@ -1,227 +0,0 @@
.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

@ -1,153 +0,0 @@
/*
* 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+']')
// Action does not use settings
if ($el.hasClass('no-form')) {
return
}
$el.popup({
handler: this.options.settingsHandler,
extraData: { current_action_id: actionId },
size: 'giant'
})
// 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)
// Action does not use settings
if ($el.closest('li.action-item').hasClass('no-form')) {
return
}
$el.popup({
handler: this.options.settingsHandler,
extraData: { current_action_id: actionId },
size: 'giant'
})
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

@ -1,273 +0,0 @@
@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

@ -1,29 +0,0 @@
<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

@ -1,71 +0,0 @@
<?= Form::open(['id' => 'propertyForm']) ?>
<div class="modal-header">
<button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title">
<?php if (!$this->fatalError): ?>
<?= e($action->getTitle()) ?>
<?php else: ?>
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">
<div class="row">
<div class="col-md-8">
<?= $actionFormWidget->render() ?>
</div>
<div class="col-md-4">
<?= $this->makePartial('available_tags') ?>
</div>
</div>
</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">
<u>S</u>ave
</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="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

@ -1,31 +0,0 @@
<div class="action-set">
<ol>
<li>
<div class="no-hover">
<div class="icons">
<span class="icon">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> Add action
</a>
</div>
<?php if (isset($newActionId)): ?>
<script> showActionSettings('<?= e($newActionId) ?>') </script>
<?php endif ?>

View File

@ -1,12 +0,0 @@
<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>
</div>

View File

@ -1,23 +0,0 @@
<?php if ($availableTags): ?>
<div class="form-preview">
<p>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>Click or drag these in to the content area</em></div>
</div>
<?php else: ?>
<div class="form-preview">
<div>This action does not provide any variables.</div>
</div>
<?php endif ?>

View File

@ -1,48 +0,0 @@
<?= Form::open(['id' => 'addRuleActionForm']) ?>
<input type="hidden" name="current_action_data" value="<?= e(json_encode($this->getCacheActionDataPayload())) ?>" />
<div class="modal-header">
<button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title"><?= e(trans('rainlab.notify::lang.action.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="list-preview">
<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>
</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

@ -1,227 +0,0 @@
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

@ -1,184 +0,0 @@
/*
* 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

@ -1,139 +0,0 @@
/*
* 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

@ -1,286 +0,0 @@
@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

@ -1,99 +0,0 @@
<?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="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">
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="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

@ -1,68 +0,0 @@
<?= Form::open(['id' => 'propertyForm']) ?>
<div class="modal-header">
<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">
<u>S</u>ave
</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="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

@ -1,10 +0,0 @@
<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

@ -1,13 +0,0 @@
<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>
</div>

View File

@ -1,67 +0,0 @@
<?= Form::open(['id' => 'propertyForm']) ?>
<div class="modal-header">
<button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title">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>Condition</label>
<select
class="form-control custom-select"
data-placeholder="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">
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

@ -1,31 +0,0 @@
<?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

@ -1,19 +0,0 @@
<?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

@ -1,32 +0,0 @@
<?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

@ -1,21 +0,0 @@
<?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

@ -1,22 +0,0 @@
<?php
return [
'plugin' => [
'name' => 'Notify',
'description' => 'Notification services',
],
'notifications' => [
'menu_label' => 'Notification rules',
'menu_description' => 'Manage the events and actions that trigger notifications.',
'name' => 'Name',
'code' => 'Code',
'notification_rule' => 'Notification rule',
'add_notification_rule' => 'Add notification rule',
],
'action' => [
'add_notification_action' => 'Add notification action',
],
'permissions' => [
'manage_notifications' => 'Notifications management',
],
];

View File

@ -1,22 +0,0 @@
<?php
return [
'plugin' => [
'name' => 'Notificari',
'description' => 'Servicii notificari',
],
'notifications' => [
'menu_label' => 'Reguli notificari',
'menu_description' => 'Gestionati evenimentele si actiunile care declanșează notificari',
'name' => 'Nume',
'code' => 'Cod',
'notification_rule' => 'Regula notificare',
'add_notification_rule' => 'Adauga o regula pentru notificare',
],
'action' => [
'add_notification_action' => 'Adauga o actiune pentru notificare',
],
'permissions' => [
'manage_notifications' => 'Managementul notificarilor',
],
];

View File

@ -1,22 +0,0 @@
<?php
return [
'plugin' => [
'name' => 'Уведомления',
'description' => 'Службы уведомлений',
],
'notifications' => [
'menu_label' => 'Правила уведомлений',
'menu_description' => 'Управление событиями и действиями, которые вызывают уведомления.',
'name' => 'Название',
'code' => 'Код',
'notification_rule' => 'Правило уведомления',
'add_notification_rule' => 'Добавить правило',
],
'action' => [
'add_notification_action' => 'Добавить действие',
],
'permissions' => [
'manage_notifications' => 'Управление уведомлениями',
],
];

View File

@ -1,22 +0,0 @@
<?php
return [
'plugin' => [
'name' => 'Obvestila',
'description' => 'Storitve obveščanja',
],
'notifications' => [
'menu_label' => 'Nastavitve obveščanja',
'menu_description' => 'Upravljanje dogodkov in dejanj, ki prožijo obvestila.',
'name' => 'Ime',
'code' => 'Koda',
'notification_rule' => 'Pravilo obveščanja',
'add_notification_rule' => 'Dodaj pravilo obveščanja',
],
'action' => [
'add_notification_action' => 'Dodaj dejanje obveščanja',
],
'permissions' => [
'manage_notifications' => 'Upravljanje z nastavitvami obveščanja',
],
];

View File

@ -1,22 +0,0 @@
<?php
return [
'plugin' => [
'name' => 'Bildirim',
'description' => 'Bildirim Servisi',
],
'notifications' => [
'menu_label' => 'Bildirim kuralları',
'menu_description' => 'Bildirimleri tetikleyen olayları ve eylemleri yönetin.',
'name' => 'İsim',
'code' => 'Kod',
'notification_rule' => 'Bildirim kuralı',
'add_notification_rule' => 'Bildirim kuralı ekle',
],
'action' => [
'add_notification_action' => 'Bildirim eylemi ekle',
],
'permissions' => [
'manage_notifications' => 'Bildirim yönetimi',
],
];

View File

@ -1,22 +0,0 @@
<?php
return [
'plugin' => [
'name' => '通知',
'description' => '通知系統',
],
'notifications' => [
'menu_label' => '通知規則',
'menu_description' => '管理觸發通知的事件及動作。',
'name' => '名稱',
'code' => '代碼',
'notification_rule' => '通知規則',
'add_notification_rule' => '新增通知規則',
],
'action' => [
'add_notification_action' => '新增通知動作',
],
'permissions' => [
'manage_notifications' => '管理通知功能',
],
];

View File

@ -1,249 +0,0 @@
<?php namespace RainLab\Notify\Models;
use Guzzle\Http\Url;
use Model;
use Markdown;
use TPS\Birzha\Events\MessageReceivedEvent;
use TPS\Birzha\Events\PaymentGiftedEvent;
use TPS\Birzha\Events\PaymentReviewedEvent;
use TPS\Birzha\Events\ProductReviewedEvent;
/**
* 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);
}
/**
* Get the description of the notification
*
* @return string
*/
public function getDescriptionAttribute()
{
$e = new $this->event_type;
if($e instanceof MessageReceivedEvent) {
return trans('validation.new_message');
} elseif($e instanceof ProductReviewedEvent) {
return trans('validation.product_reviewed');
} elseif($e instanceof PaymentReviewedEvent) {
return trans('validation.payment_reviewed');
} elseif($e instanceof PaymentGiftedEvent) {
return trans('validation.payment_gifted');
}
return 'Unknown type notification';
}
/**
* Get the localized description of the notification for api
*
* @return string
*/
public function getDescriptionForApiAttribute()
{
$e = new $this->event_type;
if($e instanceof MessageReceivedEvent) {
return [
'ru' => trans('validation.new_message', [], 'ru'),
'en' => trans('validation.new_message', [], 'en'),
'tm' => trans('validation.new_message', [], 'tm'),
];
} elseif($e instanceof ProductReviewedEvent) {
return [
'ru' => trans('validation.product_reviewed', [], 'ru'),
'en' => trans('validation.product_reviewed', [], 'en'),
'tm' => trans('validation.product_reviewed', [], 'tm'),
];
} elseif($e instanceof PaymentReviewedEvent) {
return [
'ru' => trans('validation.payment_reviewed', [], 'ru'),
'en' => trans('validation.payment_reviewed', [], 'en'),
'tm' => trans('validation.payment_reviewed', [], 'tm'),
];
} elseif($e instanceof PaymentGiftedEvent) {
return [
'ru' => trans('validation.payment_gifted', [], 'ru'),
'en' => trans('validation.payment_gifted', [], 'en'),
'tm' => trans('validation.payment_gifted', [], 'tm'),
];
}
return 'Unknown type notification';
}
/**
* Get the screen where to redirect when clicking on the notification
*
* @return string
*/
public function getRedirectToScreenForApiAttribute()
{
$e = new $this->event_type;
if($e instanceof MessageReceivedEvent) {
return 'messages_screen';
} elseif($e instanceof ProductReviewedEvent) {
return 'my_posts_screen';
} elseif($e instanceof PaymentReviewedEvent) {
return 'balance_history_screen';
} elseif($e instanceof PaymentGiftedEvent) {
return 'balance_history_screen';
}
return 'main_screen';
}
/**
* Get the link where to redirect when the notification is clicked
*
* @return string
*/
public function getLinkAttribute()
{
$e = new $this->event_type;
if($e instanceof MessageReceivedEvent) {
return \Url::to('/messages');
} elseif($e instanceof ProductReviewedEvent) {
return \Url::to('/my-posts');
} elseif($e instanceof PaymentReviewedEvent) {
return \Url::to('/balans-taryhy');
} elseif($e instanceof PaymentGiftedEvent) {
return \Url::to('/balans-taryhy');
}
return \Url::to('/');
}
}

View File

@ -1,289 +0,0 @@
<?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

@ -1,164 +0,0 @@
<?php namespace Rainlab\Notify\Models;
use Model;
use Exception;
use SystemException;
/**
* 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)
{
try {
$this->getActionObject()->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
*/
$config = $actionObj->getFieldConfig();
/*
* Action class has no fields
*/
if (!isset($config->fields)) {
return;
}
$staticAttributes = ['action_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->applyActionClass();
$this->loadCustomData();
}
public function getText()
{
if (strlen($this->action_text)) {
return $this->action_text;
}
if ($actionObj = $this->getActionObject()) {
return $actionObj->getText();
}
}
public function getActionObject()
{
$this->applyActionClass();
return $this->asExtension($this->getActionClass());
}
public function getActionClass()
{
return $this->class_name;
}
}

View File

@ -1,172 +0,0 @@
<?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

@ -1,13 +0,0 @@
# ===================================
# Column Definitions
# ===================================
columns:
name:
label: rainlab.notify::lang.notifications.name
searchable: true
code:
label: rainlab.notify::lang.notifications.code
searchable: true

View File

@ -1,47 +0,0 @@
# ===================================
# 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

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

View File

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

View File

@ -1,136 +0,0 @@
<?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

@ -1,125 +0,0 @@
<?php namespace RainLab\Notify\NotifyRules;
use Illuminate\Support\Facades\Log;
use Ramsey\Uuid\Uuid;
use RainLab\Notify\Classes\ActionBase;
use Illuminate\Database\Eloquent\Model as EloquentModel;
use ApplicationException;
use Illuminate\Support\Arr;
use RainLab\User\Models\User;
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', 'user')) ||
(!$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 ...
$model = array_get($definition,'class', User::class);
$value = $model::find($value);
}
$rule = $this->host->notification_rule;
$relation = array_get($definition, 'relation', 'notifications');
$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

@ -1,223 +0,0 @@
<?php namespace RainLab\Notify\NotifyRules;
use Illuminate\Support\Facades\Log;
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)
{
if($this->host->send_to_mode == 'user' && $params['user']) {
if(!$params['user']['email_verified'])
return;
}
$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);
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

@ -1,21 +0,0 @@
# ===================================
# 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

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

View File

@ -1,42 +0,0 @@
# ===================================
# 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

@ -1,29 +0,0 @@
<?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

@ -1,28 +0,0 @@
<?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

@ -1,25 +0,0 @@
<?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

@ -1,29 +0,0 @@
<?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

@ -1,9 +0,0 @@
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

3
plugins/rainlab/user/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.phpunit.result.cache
composer.lock
vendor

View File

@ -2,7 +2,6 @@
use App; use App;
use Auth; use Auth;
use Carbon\Carbon;
use Event; use Event;
use Backend; use Backend;
use System\Classes\PluginBase; use System\Classes\PluginBase;
@ -33,7 +32,7 @@ class Plugin extends PluginBase
public function register() public function register()
{ {
$alias = AliasLoader::getInstance(); $alias = AliasLoader::getInstance();
$alias->alias('Auth', 'RainLab\User\Facades\Auth'); $alias->alias('Auth', \RainLab\User\Facades\Auth::class);
App::singleton('user.auth', function () { App::singleton('user.auth', function () {
return \RainLab\User\Classes\AuthManager::instance(); return \RainLab\User\Classes\AuthManager::instance();
@ -70,8 +69,8 @@ class Plugin extends PluginBase
public function registerComponents() public function registerComponents()
{ {
return [ return [
\RainLab\User\Components\Session::class => 'session', \RainLab\User\Components\Session::class => 'session',
\RainLab\User\Components\Account::class => 'account', \RainLab\User\Components\Account::class => 'account',
\RainLab\User\Components\ResetPassword::class => 'resetPassword' \RainLab\User\Components\ResetPassword::class => 'resetPassword'
]; ];
} }
@ -116,12 +115,12 @@ class Plugin extends PluginBase
'url' => Backend::url('rainlab/user/users'), 'url' => Backend::url('rainlab/user/users'),
'permissions' => ['rainlab.users.access_users'] 'permissions' => ['rainlab.users.access_users']
], ],
// 'usergroups' => [ 'usergroups' => [
// 'label' => 'rainlab.user::lang.groups.menu_label', 'label' => 'rainlab.user::lang.groups.menu_label',
// 'icon' => 'icon-users', 'icon' => 'icon-users',
// 'url' => Backend::url('rainlab/user/usergroups'), 'url' => Backend::url('rainlab/user/usergroups'),
// 'permissions' => ['rainlab.users.access_groups'] 'permissions' => ['rainlab.users.access_groups']
// ] ]
] ]
] ]
]; ];
@ -134,7 +133,7 @@ class Plugin extends PluginBase
'label' => 'rainlab.user::lang.settings.menu_label', 'label' => 'rainlab.user::lang.settings.menu_label',
'description' => 'rainlab.user::lang.settings.menu_description', 'description' => 'rainlab.user::lang.settings.menu_description',
'category' => SettingsManager::CATEGORY_USERS, 'category' => SettingsManager::CATEGORY_USERS,
'icon' => 'icon-cog', 'icon' => class_exists('System') ? 'octo-icon-user-actions-key' : 'icon-cog',
'class' => 'RainLab\User\Models\Settings', 'class' => 'RainLab\User\Models\Settings',
'order' => 500, 'order' => 500,
'permissions' => ['rainlab.users.access_settings'] 'permissions' => ['rainlab.users.access_settings']
@ -191,28 +190,4 @@ class Plugin extends PluginBase
]); ]);
}); });
} }
public function registerListColumnTypes()
{
return [
'email_verified' => [$this, 'emailVerified'],
'phone_verified' => [$this, 'phoneVerified'],
];
}
public function emailVerified($value, $column, $record){
switch ($value){
case 0 : return '<span class="btn btn-default btn-xs">Not verified</span>';
case 1 : return '<span class="btn btn-primary btn-xs">Verified</span>';
default : return $value;
}
}
public function phoneVerified($value, $column, $record){
switch ($value){
case 0 : return '<span class="btn btn-default btn-xs">Not verified</span>';
case 1 : return '<span class="btn btn-primary btn-xs">Verified</span>';
default : return $value;
}
}
} }

View File

@ -1,14 +1,25 @@
# Front-end user plugin # Front-end user plugin
[![Build Status](https://img.shields.io/github/workflow/status/rainlab/user-plugin/Tests)](https://github.com/rainlab/user-plugin/actions)
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/rainlab/user-plugin/blob/master/LICENCE.md)
Front-end user management for October CMS. Front-end user management for October CMS.
## Requirements ## Requirements
This plugin requires the [Ajax Framework](https://octobercms.com/docs/cms/ajax) to be included in your layout/page in order to handle form requests. This plugin requires the [Ajax Framework](https://octobercms.com/docs/cms/ajax) to be included in your layout/page in order to handle form requests.
## Installation Instructions
Run the following to install this plugin:
```bash
php artisan plugin:install RainLab.User
```
To uninstall this plugin:
```bash
php artisan plugin:remove RainLab.User
```
## Managing users ## Managing users
Users are managed on the Users tab found in the back-end. Each user provides minimal data fields - **Name**, **Surname**, **Email** and **Password**. The Name can represent either the person's first name or their full name, making the Surname field optional, depending on the complexity of your site. Users are managed on the Users tab found in the back-end. Each user provides minimal data fields - **Name**, **Surname**, **Email** and **Password**. The Name can represent either the person's first name or their full name, making the Surname field optional, depending on the complexity of your site.
@ -47,7 +58,17 @@ As a security precaution, you may restrict users from having sessions across mul
#### Notifications #### Notifications
When a user is first activated -- either by registration, email confirmation or administrator approval -- they are sent a welcome email. To disable the welcome email, select "Do not send a notification" from the **Welcome mail template** dropdown. The default message template used is `rainlab.user::mail.welcome` and you can customize this by selecting **Mail > Mail Templates** from the settings menu. This feature is implemented by the Notify plugin. How to use this feature:
- Install the **RainLab.Notify** plugin
- Navigate to **Settings > Notification** rules
- Click **New notification rule**
- Select **User > Activated**
- Click **Add action**
- Select **Compose a mail message**
- Select **User email address** for the **Send to** field
- Here you may select the Mail template previously defined in the user settings.
- Click **Save**
## Extended features ## Extended features

View File

@ -53,12 +53,12 @@
} }
BulkActions.prototype.setActiveItem = function($el) { BulkActions.prototype.setActiveItem = function($el) {
this.$toggleBtn.blur() this.$toggleBtn.blur();
this.activeAction = $el.data('action') this.activeAction = $el.data('action');
this.$primaryBtn.text($el.text()) this.$primaryBtn.text($el.text());
this.$primaryBtn.attr('class', this.baseCss) this.$primaryBtn.attr('class', this.baseCss);
this.$primaryBtn.addClass($el.attr('class')) this.$primaryBtn.addClass($el.attr('class')).removeClass('dropdown-item');
this.$primaryBtn.data('request-confirm', $el.data('confirm')) this.$primaryBtn.data('request-confirm', $el.data('confirm'));
} }
// BULK ACTIONS PLUGIN DEFINITION // BULK ACTIONS PLUGIN DEFINITION

View File

@ -1,5 +1,6 @@
<?php namespace RainLab\User\Classes; <?php namespace RainLab\User\Classes;
use Event;
use October\Rain\Auth\Manager as RainAuthManager; use October\Rain\Auth\Manager as RainAuthManager;
use RainLab\User\Models\Settings as UserSettings; use RainLab\User\Models\Settings as UserSettings;
use RainLab\User\Models\UserGroup as UserGroupModel; use RainLab\User\Models\UserGroup as UserGroupModel;
@ -29,6 +30,9 @@ class AuthManager extends RainAuthManager
public function extendUserQuery($query) public function extendUserQuery($query)
{ {
$query->withTrashed(); $query->withTrashed();
// Extensibility
Event::fire('rainlab.user.extendUserAuthQuery', [$query]);
} }
/** /**
@ -43,6 +47,25 @@ class AuthManager extends RainAuthManager
return parent::register($credentials, $activate, $autoLogin); return parent::register($credentials, $activate, $autoLogin);
} }
/**
* findUserByEmail finds a user by the email value, which includes
* deactivated (trashed) user records.
* @param string $email
* @return Authenticatable|null
*/
public function findUserByEmail($email)
{
if (!$email) {
return null;
}
$query = $this->createUserModelQuery();
$user = $query->where('email', $email)->first();
return $this->validateUserModel($user) ? $user : null;
}
// //
// Guest users // Guest users
// //
@ -60,10 +83,11 @@ class AuthManager extends RainAuthManager
{ {
$query = $this->createUserModelQuery(); $query = $this->createUserModelQuery();
return $user = $query return $query
->where('email', $email) ->where('email', $email)
->where('is_guest', 1) ->where('is_guest', 1)
->first(); ->first()
;
} }
/** /**

View File

@ -17,8 +17,6 @@ use Cms\Classes\ComponentBase;
use RainLab\User\Models\User as UserModel; use RainLab\User\Models\User as UserModel;
use RainLab\User\Models\Settings as UserSettings; use RainLab\User\Models\Settings as UserSettings;
use Exception; use Exception;
use Throwable;
use TPS\Birzha\Classes\SMS;
/** /**
* Account component * Account component
@ -28,11 +26,6 @@ use TPS\Birzha\Classes\SMS;
*/ */
class Account extends ComponentBase class Account extends ComponentBase
{ {
/**
* Validation messages
*/
public $accountValidationMsgs;
public function componentDetails() public function componentDetails()
{ {
return [ return [
@ -56,6 +49,12 @@ class Account extends ComponentBase
'type' => 'string', 'type' => 'string',
'default' => 'code' 'default' => 'code'
], ],
'activationPage' => [
'title' => /* Activation Page */'rainlab.user::lang.account.activation_page',
'description' => /* Select a page to use for activating the user account */'rainlab.user::lang.account.activation_page_comment',
'type' => 'dropdown',
'default' => ''
],
'forceSecure' => [ 'forceSecure' => [
'title' => /*Force secure protocol*/'rainlab.user::lang.account.force_secure', 'title' => /*Force secure protocol*/'rainlab.user::lang.account.force_secure',
'description' => /*Always redirect the URL with the HTTPS schema.*/'rainlab.user::lang.account.force_secure_desc', 'description' => /*Always redirect the URL with the HTTPS schema.*/'rainlab.user::lang.account.force_secure_desc',
@ -68,19 +67,28 @@ class Account extends ComponentBase
'type' => 'checkbox', 'type' => 'checkbox',
'default' => 0 'default' => 0
], ],
'view' => [
'title' => 'View',
'description' => 'Choose view type',
'type' => 'dropdown',
'options' => ['signin','signup','profile'],
'default' => 'signin'
]
]; ];
} }
/**
* getRedirectOptions
*/
public function getRedirectOptions() public function getRedirectOptions()
{ {
return [''=>'- refresh page -', '0' => '- no redirect -'] + Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName'); return [
'' => '- refresh page -',
'0' => '- no redirect -'
] + Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName');
}
/**
* getActivationPageOptions
*/
public function getActivationPageOptions()
{
return [
'' => '- current page -',
] + Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName');
} }
/** /**
@ -111,9 +119,9 @@ class Account extends ComponentBase
/* /*
* Activation code supplied * Activation code supplied
*/ */
// if ($code = $this->activationCode()) { if ($code = $this->activationCode()) {
// $this->onActivate($code); $this->onActivate($code);
// } }
$this->prepareVars(); $this->prepareVars();
} }
@ -177,6 +185,23 @@ class Account extends ComponentBase
return UserSettings::get('remember_login', UserSettings::REMEMBER_ALWAYS); return UserSettings::get('remember_login', UserSettings::REMEMBER_ALWAYS);
} }
/**
* useRememberLogin returns true if persistent authentication should be used.
*/
protected function useRememberLogin(): bool
{
switch ($this->rememberLoginMode()) {
case UserSettings::REMEMBER_ALWAYS:
return true;
case UserSettings::REMEMBER_NEVER:
return false;
case UserSettings::REMEMBER_ASK:
return (bool) post('remember', false);
}
}
/** /**
* Looks for the activation code from the URL parameter. If nothing * Looks for the activation code from the URL parameter. If nothing
* is found, the GET parameter 'activate' is used instead. * is found, the GET parameter 'activate' is used instead.
@ -213,7 +238,7 @@ class Account extends ComponentBase
? 'required|between:2,255' ? 'required|between:2,255'
: 'required|email|between:6,255'; : 'required|email|between:6,255';
$rules['password'] = 'required|between:4,255'; $rules['password'] = 'required|between:' . UserModel::getMinPasswordLength() . ',255';
if (!array_key_exists('login', $data)) { if (!array_key_exists('login', $data)) {
$data['login'] = post('username', post('email')); $data['login'] = post('username', post('email'));
@ -221,7 +246,13 @@ class Account extends ComponentBase
$data['login'] = trim($data['login']); $data['login'] = trim($data['login']);
$validation = Validator::make($data, $rules); $validation = Validator::make(
$data,
$rules,
$this->getValidatorMessages(),
$this->getCustomAttributes()
);
if ($validation->fails()) { if ($validation->fails()) {
throw new ValidationException($validation); throw new ValidationException($validation);
} }
@ -230,31 +261,16 @@ class Account extends ComponentBase
* Authenticate user * Authenticate user
*/ */
$credentials = [ $credentials = [
'login' => array_get($data, 'dial_code') . array_get($data, 'login'), 'login' => array_get($data, 'login'),
'password' => array_get($data, 'password') 'password' => array_get($data, 'password')
]; ];
/*
* Login remember mode
*/
switch ($this->rememberLoginMode()) {
case UserSettings::REMEMBER_ALWAYS:
$remember = true;
break;
case UserSettings::REMEMBER_NEVER:
$remember = false;
break;
case UserSettings::REMEMBER_ASK:
$remember = (bool) array_get($data, 'remember', false);
break;
}
Event::fire('rainlab.user.beforeAuthenticate', [$this, $credentials]); Event::fire('rainlab.user.beforeAuthenticate', [$this, $credentials]);
$user = Auth::authenticate($credentials, $remember); $user = Auth::authenticate($credentials, $this->useRememberLogin());
if ($user->isBanned()) { if ($user->isBanned()) {
Auth::logout(); Auth::logout();
throw new AuthException(/*Sorry, this user is currently not activated. Please contact us for further assistance.*/'rainlab.user::lang.account.banned'); throw new AuthException(Lang::get(/*Sorry, this user is currently not activated. Please contact us for further assistance.*/'rainlab.user::lang.account.banned'));
} }
/* /*
@ -272,12 +288,7 @@ class Account extends ComponentBase
} }
} }
catch (Exception $ex) { catch (Exception $ex) {
if ($ex instanceof AuthException) { if (Request::ajax()) throw $ex;
throw new ValidationException(['no_user' => trans('validation.no_user')]);
}
// if (Request::ajax()) throw $ex;
if (Request::ajax()) info($ex);
else Flash::error($ex->getMessage()); else Flash::error($ex->getMessage());
} }
} }
@ -305,27 +316,19 @@ class Account extends ComponentBase
$data['password_confirmation'] = post('password'); $data['password_confirmation'] = post('password');
} }
$rules = array_merge((new UserModel)->rules, [ $rules = (new UserModel)->rules;
'username' => [
'required',
'numeric',
'unique' => function($attribute, $value, $fail) use($data) {
$u = UserModel::where('username', $data['dial_code'] . $value)->first();
if(!is_null($u)) $fail(trans('validation.auth_profile.phone_number_unique'));
}
],
]);
if ($this->loginAttribute() !== UserSettings::LOGIN_USERNAME) { if ($this->loginAttribute() !== UserSettings::LOGIN_USERNAME) {
unset($rules['username']); unset($rules['username']);
} }
$validation = Validator::make($data, $rules, [ $validation = Validator::make(
'username.required' => trans('validation.auth_profile.phone_number_required'), $data,
'username.numeric' => trans('validation.auth_profile.phone_number_numeric'), $rules,
'username.digits_between' => trans('validation.auth_profile.phone_number_digits_between'), $this->getValidatorMessages(),
'username.unique' => trans('validation.auth_profile.phone_number_unique'), $this->getCustomAttributes()
]); );
if ($validation->fails()) { if ($validation->fails()) {
throw new ValidationException($validation); throw new ValidationException($validation);
} }
@ -346,9 +349,6 @@ class Account extends ComponentBase
$automaticActivation = UserSettings::get('activate_mode') == UserSettings::ACTIVATE_AUTO; $automaticActivation = UserSettings::get('activate_mode') == UserSettings::ACTIVATE_AUTO;
$userActivation = UserSettings::get('activate_mode') == UserSettings::ACTIVATE_USER; $userActivation = UserSettings::get('activate_mode') == UserSettings::ACTIVATE_USER;
$adminActivation = UserSettings::get('activate_mode') == UserSettings::ACTIVATE_ADMIN; $adminActivation = UserSettings::get('activate_mode') == UserSettings::ACTIVATE_ADMIN;
// $user = Auth::register(array_merge($data, [
// 'username' => $data['dial_code'] . $data['username']
// ]), $automaticActivation);
$user = Auth::register($data, $automaticActivation); $user = Auth::register($data, $automaticActivation);
Event::fire('rainlab.user.register', [$user, $data]); Event::fire('rainlab.user.register', [$user, $data]);
@ -357,12 +357,13 @@ class Account extends ComponentBase
* Activation is by the user, send the email * Activation is by the user, send the email
*/ */
if ($userActivation) { if ($userActivation) {
// $this->sendActivationEmail($user); $this->sendActivationEmail($user);
//
// Flash::success(Lang::get(/*An activation email has been sent to your email address.*/'rainlab.user::lang.account.activation_email_sent')); Flash::success(Lang::get(/*An activation email has been sent to your email address.*/'rainlab.user::lang.account.activation_email_sent'));
//todo open activation view
} }
$intended = false;
/* /*
* Activation is by the admin, show message * Activation is by the admin, show message
* For automatic email on account activation RainLab.Notify plugin is needed * For automatic email on account activation RainLab.Notify plugin is needed
@ -375,17 +376,16 @@ class Account extends ComponentBase
* Automatically activated or not required, log the user in * Automatically activated or not required, log the user in
*/ */
if ($automaticActivation || !$requireActivation) { if ($automaticActivation || !$requireActivation) {
Auth::login($user); Auth::login($user, $this->useRememberLogin());
$intended = true;
} }
/* /*
* Redirect to the intended page after successful sign in * Redirect to the intended page after successful sign in
*/ */
// if ($redirect = $this->makeRedirection(true)) { if ($redirect = $this->makeRedirection($intended)) {
// return $redirect; return $redirect;
// } }
return \Redirect::to('/profile');
} }
catch (Exception $ex) { catch (Exception $ex) {
if (Request::ajax()) throw $ex; if (Request::ajax()) throw $ex;
@ -393,108 +393,6 @@ class Account extends ComponentBase
} }
} }
/**
* Send sms with 6 digits code to user
*/
public function onSendSmsCode()
{
try {
if($this->user()->dial_code == '+993' && !$this->user()->phone_verified) {
$code = random_int(1000, 9999);
$result = SMS::send(str_replace(array('+', ' ', '(' , ')', '-'), '', $this->user()->username), $code);
// $result = 0;
\Log::info('send sms result');
\Log::info($result);
switch ($result) {
case 0:
$this->user()->phone_activation_code = $code;
$this->user()->save();
\Log::info('case 0');
break;
case 1:
return \Redirect::to('/error');
\Log::info('case 1');
break;
default:
return \Redirect::to('/error');
\Log::info('case default');
break;
}
}
} catch(Throwable $th) {
\Log::info($th);
return \Redirect::to('/error');
}
}
/**
* Check SMS code sent by user
*/
public function onCheckSmsCode()
{
$data = post();
$validator = \Validator::make($data, [
'sms_code' => 'required|digits:4',
]);
if($validator->fails()) {
throw new ValidationException($validator);
}
if($this->user()->phone_activation_code == $data['sms_code']) {
$this->user()->phone_verified = true;
$this->user()->phone_activation_code = null;
$this->user()->save();
Flash::success(Lang::get('rainlab.user::lang.account.phone_verified_message'));
return \Redirect::to('profile');
} else {
Flash::error(Lang::get('rainlab.user::lang.account.phone_verification_code_invalid'));
return \Redirect::to('profile');
}
}
/**
* Send email with the verification link to the user
*/
public function onSendEmailVerificationLink()
{
if(!$this->user()->email_verified) {
$code = sha1(time());
$vars = [
'verification_link' => $this->controller->pageUrl('kabinet/verify_email.htm', [
'id' => $this->user()->id, 'code' => $code
])
];
try {
\Mail::queue('tps.birzha::mail.' . app()->getLocale() . '.email_verify', $vars, function($message) {
$message->to($this->user()->email, 'Birzha User');
$message->subject(\Lang::get('rainlab.user::lang.account.email_verification'));
});
} catch(Throwable $th) {
\Log::info($th);
Flash::error('Ошибка. Неверный Email.');
return \Redirect::to('profile');
}
$this->user()->email_activation_code = $code;
$this->user()->save();
Flash::success(\Lang::get('rainlab.user::lang.account.email_verification_check_message'));
return \Redirect::to('profile');
}
}
/** /**
* Activate the user * Activate the user
* @param string $code Activation code * @param string $code Activation code
@ -533,7 +431,7 @@ class Account extends ComponentBase
/* /*
* Sign in the user * Sign in the user
*/ */
Auth::login($user); Auth::login($user, $this->useRememberLogin());
} }
catch (Exception $ex) { catch (Exception $ex) {
@ -551,28 +449,7 @@ class Account extends ComponentBase
return; return;
} }
$data = Input::except(['username']); $data = post();
$rules = [
'email' => 'required|between:6,191|email',
'name' => 'required',
'surname' => 'required',
// 'username' => 'required|digits_between:8,20|numeric',
'iu_company' => 'max:191',
'iu_about' => 'digits:6|numeric',
];
$validation = Validator::make($data, $rules, [
'username.required' => trans('validation.auth_profile.phone_number_required'),
'username.numeric' => trans('validation.auth_profile.phone_number_numeric'),
'username.digits_between' => trans('validation.auth_profile.phone_number_digits_between'),
'username.unique' => trans('validation.auth_profile.phone_number_unique'),
'zip.digits' => trans('validation.auth_profile.iu_about_digits'),
'company.max' => trans('validation.auth_profile.iu_company_max'),
]);
if ($validation->fails()) {
throw new ValidationException($validation);
}
if ($this->updateRequiresPassword()) { if ($this->updateRequiresPassword()) {
if (!$user->checkHashValue('password', $data['password_current'])) { if (!$user->checkHashValue('password', $data['password_current'])) {
@ -584,11 +461,6 @@ class Account extends ComponentBase
$user->avatar = Input::file('avatar'); $user->avatar = Input::file('avatar');
} }
if($user->email != $data['email']) {
$user->email_verified = false;
$user->save();
}
$user->fill($data); $user->fill($data);
$user->save(); $user->save();
@ -596,9 +468,14 @@ class Account extends ComponentBase
* Password has changed, reauthenticate the user * Password has changed, reauthenticate the user
*/ */
if (array_key_exists('password', $data) && strlen($data['password'])) { if (array_key_exists('password', $data) && strlen($data['password'])) {
Auth::login($user->reload(), true); Auth::login($user->reload(), $this->useRememberLogin());
} }
/*
* Update Event to hook into the plugins function
*/
Event::fire('rainlab.user.update', [$user, $data]);
Flash::success(post('flash', Lang::get(/*Settings successfully saved!*/'rainlab.user::lang.account.success_saved'))); Flash::success(post('flash', Lang::get(/*Settings successfully saved!*/'rainlab.user::lang.account.success_saved')));
/* /*
@ -728,12 +605,13 @@ class Account extends ComponentBase
{ {
$method = $intended ? 'intended' : 'to'; $method = $intended ? 'intended' : 'to';
$property = trim((string) $this->property('redirect')); $property = post('redirect', $this->property('redirect'));
// No redirect // No redirect
if ($property === '0') { if ($property === '0') {
return; return;
} }
// Refresh page // Refresh page
if ($property === '') { if ($property === '') {
return Redirect::refresh(); return Redirect::refresh();
@ -741,7 +619,7 @@ class Account extends ComponentBase
$redirectUrl = $this->pageUrl($property) ?: $property; $redirectUrl = $this->pageUrl($property) ?: $property;
if ($redirectUrl = post('redirect', $redirectUrl)) { if ($redirectUrl) {
return Redirect::$method($redirectUrl); return Redirect::$method($redirectUrl);
} }
} }
@ -776,4 +654,26 @@ class Account extends ComponentBase
return UserModel::isRegisterThrottled(Request::ip()); return UserModel::isRegisterThrottled(Request::ip());
} }
/**
* getValidatorMessages
*/
protected function getValidatorMessages(): array
{
return (array) (new UserModel)->customMessages;
}
/**
* getCustomAttributes
*/
protected function getCustomAttributes(): array
{
return [
'login' => $this->loginAttributeLabel(),
'password' => Lang::get('rainlab.user::lang.account.password'),
'email' => Lang::get('rainlab.user::lang.account.email'),
'username' => Lang::get('rainlab.user::lang.user.username'),
'name' => Lang::get('rainlab.user::lang.account.full_name')
];
}
} }

View File

@ -6,11 +6,12 @@ use Mail;
use Validator; use Validator;
use ValidationException; use ValidationException;
use ApplicationException; use ApplicationException;
use Cms\Classes\Page;
use Cms\Classes\ComponentBase; use Cms\Classes\ComponentBase;
use RainLab\User\Models\User as UserModel; use RainLab\User\Models\User as UserModel;
/** /**
* Password reset workflow * ResetPassword controls the password reset workflow
* *
* When a user has forgotten their password, they are able to reset it using * When a user has forgotten their password, they are able to reset it using
* a unique token that, sent to their email address upon request. * a unique token that, sent to their email address upon request.
@ -33,10 +34,26 @@ class ResetPassword extends ComponentBase
'description' => /*The page URL parameter used for the reset code*/'rainlab.user::lang.reset_password.code_param_desc', 'description' => /*The page URL parameter used for the reset code*/'rainlab.user::lang.reset_password.code_param_desc',
'type' => 'string', 'type' => 'string',
'default' => 'code' 'default' => 'code'
] ],
'resetPage' => [
'title' => /* Reset Page */'rainlab.user::lang.account.reset_page',
'description' => /* Select a page to use for resetting the account password */'rainlab.user::lang.account.reset_page_comment',
'type' => 'dropdown',
'default' => ''
],
]; ];
} }
/**
* getResetPageOptions
*/
public function getResetPageOptions()
{
return [
'' => '- current page -',
] + Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName');
}
// //
// Properties // Properties
// //
@ -74,8 +91,8 @@ class ResetPassword extends ComponentBase
throw new ValidationException($validation); throw new ValidationException($validation);
} }
$user = UserModel::findByEmail(post('email')); $user = Auth::findUserByEmail(post('email'));
if (!$user || $user->is_guest) { if (!$user || $user->is_guest || $user->trashed()) {
throw new ApplicationException(Lang::get(/*A user was not found with the given credentials.*/'rainlab.user::lang.account.invalid_user')); throw new ApplicationException(Lang::get(/*A user was not found with the given credentials.*/'rainlab.user::lang.account.invalid_user'));
} }
@ -90,7 +107,7 @@ class ResetPassword extends ComponentBase
'code' => $code 'code' => $code
]; ];
Mail::queue('tps.birzha::mail.' . app()->getLocale() . '.reset_password', $data, function($message) use ($user) { Mail::send('rainlab.user::mail.restore', $data, function($message) use ($user) {
$message->to($user->email, $user->full_name); $message->to($user->email, $user->full_name);
}); });
} }
@ -133,6 +150,11 @@ class ResetPassword extends ComponentBase
if (!$user->attemptResetPassword($code, post('password'))) { if (!$user->attemptResetPassword($code, post('password'))) {
throw new ValidationException($errorFields); throw new ValidationException($errorFields);
} }
// Check needed for compatibility with legacy systems
if (method_exists(\RainLab\User\Classes\AuthManager::class, 'clearThrottleForUserId')) {
Auth::clearThrottleForUserId($user->id);
}
} }
// //
@ -140,7 +162,7 @@ class ResetPassword extends ComponentBase
// //
/** /**
* Returns a link used to reset the user account. * makeResetUrl returns a link used to reset the user account.
* @return string * @return string
*/ */
protected function makeResetUrl($code) protected function makeResetUrl($code)
@ -149,10 +171,14 @@ class ResetPassword extends ComponentBase
$this->property('paramCode') => $code $this->property('paramCode') => $code
]; ];
// Locate the current page
$url = '';
if ($pageName = $this->property('resetPage')) { if ($pageName = $this->property('resetPage')) {
$url = $this->pageUrl($pageName, $params); $url = $this->pageUrl($pageName, $params);
} }
else {
if (!$url) {
$url = $this->currentPageUrl($params); $url = $this->currentPageUrl($params);
} }

View File

@ -5,15 +5,14 @@ use Auth;
use Event; use Event;
use Flash; use Flash;
use Request; use Request;
use Response;
use Redirect; use Redirect;
use Cms\Classes\Page; use Cms\Classes\Page;
use Cms\Classes\ComponentBase; use Cms\Classes\ComponentBase;
use RainLab\User\Models\UserGroup; use RainLab\User\Models\UserGroup;
use ValidationException; use SystemException;
/** /**
* User session * Session component
* *
* This will inject the user object to every page and provide the ability for * This will inject the user object to every page and provide the ability for
* the user to sign out. This can also be used to restrict access to pages. * the user to sign out. This can also be used to restrict access to pages.
@ -62,11 +61,17 @@ class Session extends ComponentBase
]; ];
} }
/**
* getRedirectOptions
*/
public function getRedirectOptions() public function getRedirectOptions()
{ {
return [''=>'- none -'] + Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName'); return [''=>'- none -'] + Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName');
} }
/**
* getAllowedUserGroupsOptions
*/
public function getAllowedUserGroupsOptions() public function getAllowedUserGroupsOptions()
{ {
return UserGroup::lists('name','code'); return UserGroup::lists('name','code');
@ -77,9 +82,12 @@ class Session extends ComponentBase
*/ */
public function init() public function init()
{ {
if (Request::ajax() && !$this->checkUserSecurity()) { // Inject security logic pre-AJAX
abort(403, 'Access denied'); $this->controller->bindEvent('page.init', function() {
} if (Request::ajax() && ($redirect = $this->checkUserSecurityRedirect())) {
return ['X_OCTOBER_REDIRECT' => $redirect->getTargetUrl()];
}
});
} }
/** /**
@ -87,13 +95,8 @@ class Session extends ComponentBase
*/ */
public function onRun() public function onRun()
{ {
if (!$this->checkUserSecurity()) { if ($redirect = $this->checkUserSecurityRedirect()) {
if (empty($this->property('redirect'))) { return $redirect;
throw new \InvalidArgumentException('Redirect property is empty');
}
$redirectUrl = $this->controller->pageUrl($this->property('redirect'));
return Redirect::guest($redirectUrl);
} }
$this->page['user'] = $this->user(); $this->page['user'] = $this->user();
@ -172,13 +175,31 @@ class Session extends ComponentBase
} }
/** /**
* Checks if the user can access this page based on the security rules * checkUserSecurityRedirect will return a redirect if the user cannot access the page.
* @return bool
*/ */
protected function checkUserSecurity() protected function checkUserSecurityRedirect()
{
// No security layer enabled
if ($this->checkUserSecurity()) {
return;
}
if (!$this->property('redirect')) {
throw new SystemException('Redirect property is empty on Session component.');
}
$redirectUrl = $this->controller->pageUrl($this->property('redirect'));
return Redirect::guest($redirectUrl);
}
/**
* checkUserSecurity checks if the user can access this page based on the security rules.
*/
protected function checkUserSecurity(): bool
{ {
$allowedGroup = $this->property('security', self::ALLOW_ALL); $allowedGroup = $this->property('security', self::ALLOW_ALL);
$allowedUserGroups = $this->property('allowedUserGroups', []); $allowedUserGroups = (array) $this->property('allowedUserGroups', []);
$isAuthenticated = Auth::check(); $isAuthenticated = Auth::check();
if ($isAuthenticated) { if ($isAuthenticated) {

View File

@ -1,5 +0,0 @@
<div class="pass_mail">
<div class="pass_title">
{{ 'auth.password_reset_check'|_ }}
</div>
</div>

View File

@ -1,14 +1 @@
<div class="pass_mail"> <p>Password reset complete, you may now sign in.</p>
<div class="pass_title">
{{ 'auth.password_reset_complete'|_ }}
</div>
<form
class="password_form"
action="/"
method="get">
<div class="btn_bg">
<button type="submit" class="pass_btn">OK</button>
</div>
</form>
</div>

View File

@ -1,12 +1,7 @@
<section class="register active"> <div id="partialUserResetForm">
<div class="auto_container"> {% if __SELF__.code == null %}
<div class="pass_wrap" id="partialUserResetForm"> {% partial __SELF__ ~ '::restore' %}
{% if __SELF__.code == null %} {% else %}
{% partial __SELF__ ~ '::restore' %} {% partial __SELF__ ~ '::reset' %}
{% else %} {% endif %}
{% partial __SELF__ ~ '::reset' %} </div>
{% endif %}
</div>
</div>
</section>

View File

@ -1,22 +1,19 @@
<div class="pass_change active"> <p class="lead">
<div class="pass_title"> Please check your email for the activation code.
{{ 'account.new_password'|_ }} </p>
<form
data-request="{{ __SELF__ }}::onResetPassword"
data-request-update="'{{ __SELF__ }}::complete': '#partialUserResetForm'">
<div class="form-group">
<label for="resetCode">Activation Code</label>
<input name="code" type="text" class="form-control" id="resetCode" placeholder="Enter the activation code" value="{{ __SELF__.code }}">
</div> </div>
<form
class="password_form"
data-request="{{ __SELF__ }}::onResetPassword"
data-request-update="'{{ __SELF__ }}::complete': '#partialUserResetForm'">
<div class="pass_input">
<input name="code" type="hidden" class="form-control" id="resetCode" placeholder="{{ 'auth.password_reset_code'|_ }}" value="{{ __SELF__.code }}" required>
</div>
<div class="pass_input"> <div class="form-group">
<input name="password" type="password" class="form-control" id="resetPassword" placeholder="{{ 'account.new_password'|_ }}" required> <label for="resetPassword">New Password</label>
</div> <input name="password" type="password" class="form-control" id="resetPassword" placeholder="Enter a new password">
</div>
<div class="btn_bg">
<button type="submit" class="pass_btn" data-attach-loading>{{ 'auth.password_reset_btn'|_ }}</button>
</div>
</form>
</div>
<button type="submit" class="btn btn-default">Reset password</button>
</form>

View File

@ -1,17 +1,14 @@
<div class="pass_mail"> <p class="lead">
<div class="pass_title"> <strong>Lost your password?</strong> No problem! Enter your email address to verify your account.
{{ 'auth.password_reset'|_ }} </p>
</div>
<form
class="password_form"
data-request="{{ __SELF__ }}::onRestorePassword"
data-request-update="'{{ __SELF__ }}::check': '#partialUserResetForm'">
<div class="pass_input">
<input name="email" type="email" required class="form-control" id="userRestoreEmail" placeholder="{{'auth.email'|_}}">
</div>
<div class="btn_bg">
<button type="submit" class="pass_btn" data-attach-loading>{{ 'auth.password_restore_btn'|_ }}</button>
</div>
</form> <form
</div> data-request="{{ __SELF__ }}::onRestorePassword"
data-request-update="'{{ __SELF__ }}::reset': '#partialUserResetForm'">
<div class="form-group">
<label for="userRestoreEmail">Email</label>
<input name="email" type="email" class="form-control" id="userRestoreEmail" placeholder="Enter your email">
</div>
<button type="submit" class="btn btn-default">Restore password</button>
</form>

View File

@ -1,8 +1,25 @@
{ {
"name": "rainlab/user-plugin", "name": "rainlab/user-plugin",
"type": "october-plugin", "type": "october-plugin",
"description": "None", "description": "User plugin for October CMS",
"homepage": "https://octobercms.com/plugin/rainlab-user",
"keywords": ["october", "octobercms", "user"],
"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": { "require": {
"php": ">=5.5.9",
"composer/installers": "~1.0" "composer/installers": "~1.0"
} },
"minimum-stability": "dev"
} }

View File

@ -64,7 +64,7 @@ return [
| |
*/ */
'minPasswordLength' => 6, 'minPasswordLength' => 8,
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View File

@ -279,6 +279,7 @@ class Users extends Controller
break; break;
case 'deactivate': case 'deactivate':
$user->clearPersistCode();
$user->delete(); $user->delete();
break; break;

View File

@ -1,16 +1,15 @@
<?= Form::open(['id' => 'convertGuestForm']) ?> <?= Form::open(['id' => 'convertGuestForm']) ?>
<div class="modal-header"> <div class="modal-header flex-row-reverse">
<button type="button" class="close" data-dismiss="popup">&times;</button> <button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title">Convert guest user</h4> <h4 class="modal-title">Convert Guest User</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<?php if ($this->fatalError): ?> <?php if ($this->fatalError): ?>
<p class="flash-message static error"><?= $fatalError ?></p> <p class="flash-message static error"><?= $fatalError ?></p>
<?php endif ?> <?php endif ?>
<div class="form-group dropdown-field span-full"> <div class="form-group dropdown-field span-full">
<label>Select a new user group</label> <label class="form-label">Select a new user group</label>
<select class="form-control custom-select" name="new_group"> <select class="form-control custom-select" name="new_group">
<option selected="selected" value="">- No group -</option> <option selected="selected" value="">- No group -</option>
<?php foreach ($groups as $group): ?> <?php foreach ($groups as $group): ?>
@ -23,9 +22,9 @@
<div class="checkbox custom-checkbox"> <div class="checkbox custom-checkbox">
<input type="hidden" class="checkbox" value="" name="send_registration_notification"> <input type="hidden" class="checkbox" value="" name="send_registration_notification">
<input name="send_registration_notification" value="1" type="checkbox" id="sendNotification" checked="checked" /> <input name="send_registration_notification" value="1" type="checkbox" id="sendNotification" checked="checked" />
<label for="sendNotification">Send registration notification</label> <label class="storm-icon-pseudo" for="sendNotification">Send registration notification</label>
<p class="help-block">Use this checkbox to generate new random password for the user and send a registration notification email.</p>
</div> </div>
<p class="help-block form-text">Use this checkbox to generate new random password for the user and send a registration notification email.</p>
</div> </div>
</div> </div>
@ -36,7 +35,7 @@
class="btn btn-primary" class="btn btn-primary"
data-popup-load-indicator data-popup-load-indicator
data-request="onConvertGuest"> data-request="onConvertGuest">
Convert to registered Convert to Registered
</button> </button>
<button <button
type="button" type="button"

View File

@ -20,7 +20,7 @@
</button> </button>
<button <button
type="button" type="button"
class="btn btn-default dropdown-toggle" class="btn btn-default dropdown-toggle dropdown-toggle-split"
data-trigger-action="enable" data-trigger-action="enable"
data-trigger=".control-list input[type=checkbox]" data-trigger=".control-list input[type=checkbox]"
data-trigger-condition="checked" data-trigger-condition="checked"
@ -28,12 +28,12 @@
<span class="caret"></span> <span class="caret"></span>
</button> </button>
<ul class="dropdown-menu" data-dropdown-title="<?= e(trans('rainlab.user::lang.users.bulk_actions')) ?>"> <ul class="dropdown-menu" data-dropdown-title="<?= e(trans('rainlab.user::lang.users.bulk_actions')) ?>">
<!-- <li> <li>
<a href="javascript:;" class="oc-icon-trash-o" data-action="delete" data-confirm="<?= e(trans('rainlab.user::lang.users.delete_selected_confirm')) ?>"> <a href="javascript:;" class="oc-icon-trash-o" data-action="delete" data-confirm="<?= e(trans('rainlab.user::lang.users.delete_selected_confirm')) ?>">
<?= e(trans('rainlab.user::lang.users.delete_selected')) ?> <?= e(trans('rainlab.user::lang.users.delete_selected')) ?>
</a> </a>
</li> --> </li>
<!-- <li role="separator" class="divider"></li> <li role="separator" class="divider"></li>
<li> <li>
<a href="javascript:;" class="oc-icon-user-plus" data-action="activate" data-confirm="<?= e(trans('rainlab.user::lang.users.activate_selected_confirm')) ?>"> <a href="javascript:;" class="oc-icon-user-plus" data-action="activate" data-confirm="<?= e(trans('rainlab.user::lang.users.activate_selected_confirm')) ?>">
<?= e(trans('rainlab.user::lang.users.activate_selected')) ?> <?= e(trans('rainlab.user::lang.users.activate_selected')) ?>
@ -49,7 +49,7 @@
<a href="javascript:;" class="oc-icon-user-plus" data-action="restore" data-confirm="<?= e(trans('rainlab.user::lang.users.restore_selected_confirm')) ?>"> <a href="javascript:;" class="oc-icon-user-plus" data-action="restore" data-confirm="<?= e(trans('rainlab.user::lang.users.restore_selected_confirm')) ?>">
<?= e(trans('rainlab.user::lang.users.restore_selected')) ?> <?= e(trans('rainlab.user::lang.users.restore_selected')) ?>
</a> </a>
</li> --> </li>
<li role="separator" class="divider"></li> <li role="separator" class="divider"></li>
<li> <li>
<a href="javascript:;" class="oc-icon-ban" data-action="ban" data-confirm="<?= e(trans('rainlab.user::lang.users.ban_selected_confirm')) ?>"> <a href="javascript:;" class="oc-icon-ban" data-action="ban" data-confirm="<?= e(trans('rainlab.user::lang.users.ban_selected_confirm')) ?>">

View File

@ -9,13 +9,13 @@
<?= e(trans('rainlab.user::lang.users.update_details')) ?> <?= e(trans('rainlab.user::lang.users.update_details')) ?>
</a> </a>
<?php if ($this->user->hasAccess('rainlab.users.impersonate_user')): ?> <?php if ($this->user->hasAccess('rainlab.users.impersonate_user')): ?>
<!-- <a <a
href="javascript:;" href="javascript:;"
data-request="onImpersonateUser" data-request="onImpersonateUser"
data-request-confirm="<?= e(trans('rainlab.user::lang.users.impersonate_confirm')) ?>" data-request-confirm="<?= e(trans('rainlab.user::lang.users.impersonate_confirm')) ?>"
class="btn btn-default oc-icon-user-secret"> class="btn btn-default oc-icon-user-secret">
<?= e(trans('rainlab.user::lang.users.impersonate_user')) ?> <?= e(trans('rainlab.user::lang.users.impersonate_user')) ?>
</a> --> </a>
<?php endif ?> <?php endif ?>
<?php if ($formModel->isSuspended()): ?> <?php if ($formModel->isSuspended()): ?>

View File

@ -1,30 +0,0 @@
# ===================================
# Filter Scope Definitions
# ===================================
scopes:
# groups:
# # Filter name
# label: rainlab.user::lang.group.label
# # Model Class name
# modelClass: RainLab\User\Models\UserGroup
# # Model attribute to display for the name
# nameFrom: name
# # Filter scope
# scope: filterByGroup
created_date:
label: rainlab.user::lang.user.created_at
type: daterange
conditions: created_at >= ':after' AND created_at <= ':before'
# activated:
# # Filter name
# label: rainlab.user::lang.user.status_activated
# # Filter type
# type: switch
# # SQL conditions
# conditions:
# - is_activated = '0'
# - is_activated = '1'

View File

@ -8,6 +8,9 @@ title: rainlab.user::lang.users.list_title
# Model List Column configuration # Model List Column configuration
list: $/rainlab/user/models/user/columns.yaml list: $/rainlab/user/models/user/columns.yaml
# Filter widget configuration
filter: $/rainlab/user/models/user/scopes.yaml
# Model Class name # Model Class name
modelClass: RainLab\User\Models\User modelClass: RainLab\User\Models\User
@ -26,9 +29,6 @@ showCheckboxes: true
# Displays the list column set up button # Displays the list column set up button
showSetup: true showSetup: true
# Filter widget configuration
filter: config_filter.yaml
# Toolbar widget configuration # Toolbar widget configuration
toolbar: toolbar:

View File

@ -0,0 +1,228 @@
<?php
return [
'plugin' => [
'name' => 'المستخدم',
'description' => 'إدارة المستخدم الأمامية.',
'tab' => 'المستخدمون',
'access_users' => 'ادارة المستخدمين',
'access_groups' => 'إدارة مجموعات المستخدمين',
'access_settings' => 'إدارة إعدادات المستخدم',
'impersonate_user' => 'انتحال شخصية المستخدمين'
],
'users' => [
'menu_label' => 'المستخدمون',
'all_users' => 'جميع المستخدمين',
'new_user' => 'مستخدم جديد',
'list_title' => 'ادارة المستخدمين',
'trashed_hint_title' => 'قام المستخدم بإلغاء تنشيط حسابه',
'trashed_hint_desc' => 'قام هذا المستخدم بإلغاء تنشيط حسابه ولم يعد يريد الظهور على الموقع. يمكنهم استعادة حسابهم في أي وقت عن طريق تسجيل الدخول مرة أخرى.',
'banned_hint_title' => 'تم حظر المستخدم',
'banned_hint_desc' => 'تم حظر هذا المستخدم من قبل المسؤول ولن يتمكن من تسجيل الدخول.',
'guest_hint_title' => 'هذا مستخدم ضيف',
'guest_hint_desc' => 'يتم تخزين هذا المستخدم لأغراض مرجعية فقط ويحتاج إلى التسجيل قبل تسجيل الدخول.',
'activate_warning_title' => 'المستخدم غير مفعل!',
'activate_warning_desc' => 'لم يتم تنشيط هذا المستخدم وقد يتعذر عليه تسجيل الدخول.',
'activate_confirm' => 'هل تريد فعلاً تفعيل هذا المستخدم؟',
'activated_success' => 'تم تنشيط المستخدم',
'activate_manually' => 'قم بتنشيط هذا المستخدم يدويًا',
'convert_guest_confirm' => 'تحويل هذا الضيف إلى مستخدم؟',
'convert_guest_manually' => 'التحويل إلى مستخدم مسجل',
'convert_guest_success' => 'تم تحويل المستخدم إلى حساب مسجل',
'impersonate_user' => 'انتحال شخصية المستخدم',
'impersonate_confirm' => 'انتحال شخصية هذا المستخدم؟ يمكنك العودة إلى حالتك الأصلية بتسجيل الخروج.',
'impersonate_success' => 'أنت الآن تنتحل شخصية هذا المستخدم',
'delete_confirm' => 'هل تريد حقًا حذف هذا المستخدم؟',
'unban_user' => 'إلغاء حظر هذا المستخدم',
'unban_confirm' => 'هل تريد حقًا إلغاء حظر هذا المستخدم؟',
'unbanned_success' => 'تم إلغاء حظر المستخدم',
'return_to_list' => 'العودة إلى قائمة المستخدمين',
'update_details' => 'تحديث التفاصيل',
'bulk_actions' => 'إجراءات ',
'delete_selected' => 'احذف المختار',
'delete_selected_confirm' => 'حذف المستخدمين المختارين؟',
'delete_selected_empty' => 'لا يوجد مستخدمون محددون لحذفهم.',
'delete_selected_success' => 'تم حذف المستخدمين المحددين بنجاح.',
'activate_selected' => 'تفعيل المحدد',
'activate_selected_confirm' => 'تفعيل المستخدمين المختارين؟',
'activate_selected_empty' => 'لا يوجد مستخدمون محددون لتنشيطهم.',
'activate_selected_success' => 'تم تفعيل المستخدمين المختارين بنجاح.',
'deactivate_selected' => 'تم تحديد إلغاء التنشيط',
'deactivate_selected_confirm' => 'تعطيل المستخدمين المختارين؟',
'deactivate_selected_empty' => 'لا يوجد مستخدمون محددون لإلغاء تنشيطهم.',
'deactivate_selected_success' => 'تم إلغاء تنشيط المستخدمين المحددين بنجاح.',
'restore_selected' => 'استعادة المختارة',
'restore_selected_confirm' => 'استعادة المستخدمين المختارين؟',
'restore_selected_empty' => 'لا يوجد مستخدمون محددون لاستعادتهم.',
'restore_selected_success' => 'تمت استعادة المستخدمين المحددين بنجاح.',
'ban_selected' => 'تم تحديد الحظر',
'ban_selected_confirm' => 'حظر المستخدمين المختارين؟',
'ban_selected_empty' => 'لا يوجد مستخدمون محددون لحظرهم.',
'ban_selected_success' => 'تم بنجاح حظر المستخدمين المحددين.',
'unban_selected' => 'تم تحديد إلغاء الحظر',
'unban_selected_confirm' => 'إلغاء حظر المستخدمين المحددين؟',
'unban_selected_empty' => 'لا يوجد مستخدمون محددون لإلغاء الحظر.',
'unban_selected_success' => 'تم بنجاح فك الحظر عن المستخدمين المحددين.',
'unsuspend' => 'غير معلق',
'unsuspend_success' => 'تم إلغاء تعليق المستخدم.',
'unsuspend_confirm' => 'إلغاء تعليق هذا المستخدم؟'
],
'settings' => [
'users' => 'المستخدمون',
'menu_label' => 'إعدادات المستخدم',
'menu_description' => 'إدارة مصادقة المستخدم والتسجيل وإعدادات التنشيط.',
'activation_tab' => 'التنشيط',
'signin_tab' => 'تسجيل الدخول',
'registration_tab' => 'تسجيل',
'profile_tab' => 'الملف الشخصي',
'notifications_tab' => 'إشعارات',
'allow_registration' => 'السماح بتسجيل المستخدم',
'allow_registration_comment' => 'إذا تم تعطيل هذا ، فلا يمكن إنشاء المستخدمين إلا بواسطة المسؤولين.',
'activate_mode' => 'وضع التنشيط',
'activate_mode_comment' => 'حدد كيفية تنشيط حساب المستخدم.',
'activate_mode_auto' => 'تلقائي',
'activate_mode_auto_comment' => 'يتم تفعيله تلقائيًا عند التسجيل.',
'activate_mode_user' => 'اسم االمستخدم',
'activate_mode_user_comment' => 'يقوم المستخدم بتنشيط حسابه الخاص باستخدام البريد.',
'activate_mode_admin' => 'مدير',
'activate_mode_admin_comment' => 'يمكن للمسؤول فقط تنشيط المستخدم.',
'require_activation' => 'يتطلب تسجيل الدخول التنشيط',
'require_activation_comment' => 'يجب أن يكون لدى المستخدمين حساب نشط لتسجيل الدخول.',
'use_throttle' => 'محاولات خنق',
'use_throttle_comment' => 'ستؤدي محاولات تسجيل الدخول الفاشلة المتكررة إلى تعليق المستخدم مؤقتًا.',
'use_register_throttle' => 'تسجيل الخانق',
'use_register_throttle_comment' => 'منع التسجيلات المتعددة من نفس عنوان IP في تتابع قصير.',
'block_persistence' => 'منع الجلسات المتزامنة',
'block_persistence_comment' => 'عند التمكين ، لا يمكن للمستخدمين تسجيل الدخول إلى أجهزة متعددة في نفس الوقت.',
'login_attribute' => 'سمة تسجيل الدخول',
'login_attribute_comment' => 'حدد تفاصيل المستخدم الأساسية التي يجب استخدامها لتسجيل الدخول.',
'remember_login' => 'تذكر وضع تسجيل الدخول',
'remember_login_comment' => 'حدد ما إذا كانت جلسة المستخدم يجب أن تكون مستمرة.',
'remember_always' => 'دائماً',
'remember_never' => 'أبداً',
'remember_ask' => 'اسأل المستخدم عند تسجيل الدخول',
],
'user' => [
'label' => 'المستخدم',
'id' => 'ID',
'username' => 'اسم المستخدم',
'name' => 'اسم',
'name_empty' => 'مجهول',
'surname' => 'اسم العائلة',
'email' => 'البريد الإلكتروني',
'created_at' => 'مسجل',
'last_seen' => 'اخر ظهور',
'is_guest' => 'زائر',
'joined' => 'انضم',
'is_online' => 'متواجد حاليا',
'is_offline' => 'حاليا غير متصل',
'send_invite' => 'إرسال دعوة عبر البريد الإلكتروني',
'send_invite_comment' => 'يرسل رسالة ترحيب تحتوي على معلومات تسجيل الدخول وكلمة المرور.',
'create_password' => 'أنشئ كلمة مرور',
'create_password_comment' => 'أدخل كلمة مرور جديدة مستخدمة لتسجيل الدخول.',
'reset_password' => 'إعادة تعيين كلمة المرور',
'reset_password_comment' => 'لإعادة تعيين كلمة مرور هذا المستخدم ، أدخل كلمة مرور جديدة هنا.',
'confirm_password' => 'تأكيد كلمة المرور',
'confirm_password_comment' => 'أدخل كلمة المرور مرة أخرى لتأكيدها.',
'groups' => 'مجموعات',
'empty_groups' => 'لا توجد مجموعات مستخدمين متاحة.',
'avatar' => 'الصورة الرمزية',
'details' => 'تفاصيل',
'account' => 'الحساب',
'block_mail' => 'حظر كل البريد الصادر المرسل إلى هذا المستخدم.',
'status_label' => 'حالة',
'status_guest' => 'زائر',
'status_activated' => 'مفعل',
'status_registered' => 'مسجل',
'created_ip_address' => 'عنوان IP الذي تم إنشاؤه',
'last_ip_address' => 'عنوان IP الأخير',
],
'group' => [
'label' => 'مجموعة',
'id' => 'ID',
'name' => 'الاسم',
'description_field' => 'الوصف',
'code' => 'رمز',
'code_comment' => 'أدخل رمزًا فريدًا يستخدم لتعريف هذه المجموعة.',
'created_at' => 'أنشئت',
'users_count' => 'المستخدمون'
],
'groups' => [
'menu_label' => 'مجموعات',
'all_groups' => 'مجموعات الاعضاء',
'new_group' => 'مجموعة جديدة',
'delete_selected_confirm' => 'هل تريد حقًا حذف المجموعات المختارة؟',
'list_title' => 'إدارة المجموعات',
'delete_confirm' => 'هل تريد حقًا حذف هذه المجموعة؟',
'delete_selected_success' => 'تم حذف المجموعات المحددة بنجاح.',
'delete_selected_empty' => 'لا توجد مجموعات محددة لحذفها.',
'return_to_list' => 'رجوع إلى قائمة المجموعات',
'return_to_users' => 'رجوع إلى قائمة المستخدمين',
'create_title' => 'إنشاء مجموعة مستخدمين',
'update_title' => 'تحرير مجموعة المستخدمين',
'preview_title' => 'معاينة مجموعة المستخدمين'
],
'login' => [
'attribute_email' => 'البريد الإلكتروني',
'attribute_username' => 'اسم المستخدم'
],
'account' => [
'account' => 'الحساب',
'account_desc' => 'نموذج إدارة المستخدم.',
'banned' => 'عذرا ، هذا المستخدم غير مفعل حاليا. يرجى الاتصال بنا للحصول على مزيد من المساعدة.',
'redirect_to' => 'إعادة توجيه ل',
'redirect_to_desc' => 'اسم الصفحة المراد إعادة التوجيه إليه بعد التحديث أو تسجيل الدخول أو التسجيل.',
'code_param' => 'تفعيل كود بارام',
'code_param_desc' => 'معلمة URL للصفحة المستخدمة في رمز تنشيط التسجيل',
'force_secure' => 'فرض بروتوكول آمن',
'force_secure_desc' => 'قم دائمًا بإعادة توجيه عنوان URL باستخدام مخطط HTTPS.',
'invalid_user' => 'لم يتم العثور على مستخدم ببيانات الاعتماد المقدمة.',
'invalid_activation_code' => 'تم توفير رمز التفعيل غير صالح.',
'invalid_deactivation_pass' => 'كلمة المرور التي أدخلتها غير صالحة.',
'invalid_current_pass' => 'كلمة المرور الحالية التي أدخلتها غير صالحة.',
'success_activation' => 'تم تفعيل حسابك بنجاح.',
'success_deactivation' => 'تم إلغاء تنشيط حسابك بنجاح. اسف لرؤيتك تذهب!',
'success_saved' => 'تم حفظ الإعدادات بنجاح!',
'login_first' => 'يجب عليك تسجيل الدخول أولا!',
'already_active' => 'تم تنشيط حسابك بالفعل!',
'activation_email_sent' => 'تم ارسال رسالة التحقق الى بريدك الالكتروني.',
'activation_by_admin' => 'لقد قمت بالتسجيل بنجاح. حسابك ليس نشطًا بعد ويجب أن يوافق عليه المسؤول.',
'registration_disabled' => 'التسجيلات معطلة حاليا.',
'registration_throttled' => 'التسجيل مخنق. الرجاء معاودة المحاولة في وقت لاحق.',
'sign_in' => 'تسجيل الدخول',
'register' => 'تسجيل',
'full_name' => 'الاسم الكامل',
'email' => 'البريد الإلكتروني',
'password' => 'كلمة المرور',
'login' => 'تسجيل الدخول',
'new_password' => 'كلمة السر الجديدة',
'new_password_confirm' => 'تأكيد كلمة المرور الجديدة',
'update_requires_password' => 'تأكيد كلمة المرور عند التحديث',
'update_requires_password_comment' => 'طلب كلمة المرور الحالية للمستخدم عند تغيير ملف التعريف الخاص به.',
'activation_page' => 'صفحة التفعيل',
'activation_page_comment' => 'حدد صفحة لاستخدامها في تنشيط حساب المستخدم',
'reset_page' => 'إعادة ضبط الصفحة',
'reset_page_comment' => 'حدد صفحة لاستخدامها في إعادة تعيين كلمة مرور الحساب',
],
'reset_password' => [
'reset_password' => 'إعادة تعيين كلمة المرور',
'reset_password_desc' => 'استمارة كلمة السر المنسية.',
'code_param' => 'إعادة تعيين الرمز',
'code_param_desc' => 'معلمة URL للصفحة المستخدمة في إعادة تعيين رمز'
],
'session' => [
'session' => 'جلسة',
'session_desc' => 'يضيف جلسة المستخدم إلى الصفحة ويمكنه تقييد الوصول إلى الصفحة.',
'security_title' => 'السماح فقط',
'security_desc' => 'من المسموح له الوصول إلى هذه الصفحة.',
'all' => 'الجميع',
'users' => 'المستخدمون',
'guests' => 'ضيوف',
'allowed_groups_title' => 'السماح للمجموعات',
'allowed_groups_description' => 'اختر المجموعات المسموح بها أو لا تختارها للسماح لجميع المجموعات',
'redirect_title' => 'إعادة توجيه ل',
'redirect_desc' => 'اسم الصفحة لإعادة التوجيه إذا تم رفض الوصول.',
'logout' => 'لقد تم تسجيل الخروج بنجاح!',
'stop_impersonate_success' => 'لم تعد تنتحل شخصية مستخدم.',
]
];

View File

@ -70,7 +70,7 @@ return [
'settings' => [ 'settings' => [
'users' => 'Users', 'users' => 'Users',
'menu_label' => 'User settings', 'menu_label' => 'User settings',
'menu_description' => 'Manage user based settings.', 'menu_description' => 'Manage user authentication, registration and activation settings.',
'activation_tab' => 'Activation', 'activation_tab' => 'Activation',
'signin_tab' => 'Sign in', 'signin_tab' => 'Sign in',
'registration_tab' => 'Registration', 'registration_tab' => 'Registration',
@ -199,13 +199,10 @@ return [
'new_password_confirm' => 'Confirm New Password', 'new_password_confirm' => 'Confirm New Password',
'update_requires_password' => 'Confirm password on update', 'update_requires_password' => 'Confirm password on update',
'update_requires_password_comment' => 'Require the current password of the user when changing their profile.', 'update_requires_password_comment' => 'Require the current password of the user when changing their profile.',
'email_verification' => 'Email verification', 'activation_page' => 'Activation Page',
'email_verification_check_message' => 'Please check your email', 'activation_page_comment' => 'Select a page to use for activating the user account',
'email_verified_message' => 'Your email address has been succesfully verified', 'reset_page' => 'Reset Page',
'email_verification_link_invalid' => 'Invalid verification link', 'reset_page_comment' => 'Select a page to use for resetting the account password',
'email_already_verified' => 'You have already verified your email address',
'phone_verified_message' => 'Your phone number has been succesfully verified',
'phone_verification_code_invalid' => 'Invalid sms code',
], ],
'reset_password' => [ 'reset_password' => [
'reset_password' => 'Reset Password', 'reset_password' => 'Reset Password',

View File

@ -199,13 +199,10 @@ return [
'new_password_confirm' => 'Подтверждение пароля', 'new_password_confirm' => 'Подтверждение пароля',
'update_requires_password' => 'Подтверждать пароль при обновлении', 'update_requires_password' => 'Подтверждать пароль при обновлении',
'update_requires_password_comment' => 'Запрашивать текущий пароль пользователя при редактировании его профиля.', 'update_requires_password_comment' => 'Запрашивать текущий пароль пользователя при редактировании его профиля.',
'email_verification' => 'Подтверждение Email', 'activation_page' => 'Страница активации',
'email_verification_check_message' => 'Пожалуйста, проверьте Еmail', 'activation_page_comment' => 'Выберите страницу для активации аккаунта пользователя.',
'email_verified_message' => 'Ваш Email успешно подтвержден', 'reset_page' => 'Страница сброса пароля',
'email_verification_link_invalid' => 'Неверный код подтверждения', 'reset_page_comment' => 'Выберите страницу для сброса проля аккаунта.',
'email_already_verified' => 'Вы уже подтвердили свой Email',
'phone_verified_message' => 'Ваш номер телефона подтвержден',
'phone_verification_code_invalid' => 'Неверный СМС код',
], ],
'reset_password' => [ 'reset_password' => [
'reset_password' => 'Сброс пароля', 'reset_password' => 'Сброс пароля',

View File

@ -0,0 +1,228 @@
<?php
return [
'plugin' => [
'name' => 'පරිශීලක',
'description' => 'ඉදිරිපස පරිශීලක කළමනාකරණය.',
'tab' => 'පරිශීලකයන්',
'access_users' => 'පරිශීලකයන් කළමනාකරණය කරන්න',
'access_groups' => 'පරිශීලක කණ්ඩායම් කළමනාකරණය කරන්න',
'access_settings' => 'පරිශීලක සැකසුම් කළමනාකරණය කරන්න',
'impersonate_user' => 'පරිශීලකයන් ලෙස පෙනී සිටින්න'
],
'users' => [
'menu_label' => 'පරිශීලකයන්',
'all_users' => 'සියලුම පරිශීලකයින්',
'new_user' => 'නව පරිශීලක',
'list_title' => 'පරිශීලකයන් කළමනාකරණය කරන්න',
'trashed_hint_title' => 'පරිශීලකයා ඔවුන්ගේ ගිණුම අක්රිය කර ඇත',
'trashed_hint_desc' => 'මෙම පරිශීලකයා ඔවුන්ගේ ගිණුම අක්‍රිය කර ඇති අතර තවදුරටත් අඩවියේ පෙනී සිටීමට අවශ්‍ය නැත. ඔවුන්ට නැවත පුරනය වීමෙන් ඕනෑම වේලාවක ඔවුන්ගේ ගිණුම ප්‍රතිසාධනය කළ හැක.',
'banned_hint_title' => 'පරිශීලකයා තහනම් කර ඇත',
'banned_hint_desc' => 'මෙම පරිශීලකයා පරිපාලකයෙකු විසින් තහනම් කර ඇති අතර ඔහුට පුරනය වීමට නොහැකි වනු ඇත.',
'guest_hint_title' => 'මෙය ආගන්තුක පරිශීලකයෙකි',
'guest_hint_desc' => 'මෙම පරිශීලකයා ගබඩා කර ඇත්තේ යොමු අරමුණු සඳහා පමණක් වන අතර පුරනය වීමට පෙර ලියාපදිංචි වීමට අවශ්‍ය වේ.',
'activate_warning_title' => 'පරිශීලකයා සක්රිය කර නැත!',
'activate_warning_desc' => 'මෙම පරිශීලකයා සක්‍රිය කර නොමැති අතර පුරනය වීමට නොහැකි විය හැක.',
'activate_confirm' => 'ඔබට ඇත්තටම මෙම පරිශීලකයා සක්‍රිය කිරීමට අවශ්‍යද?',
'activated_success' => 'පරිශීලකයා සක්රිය කර ඇත',
'activate_manually' => 'මෙම පරිශීලකයා අතින් සක්රිය කරන්න',
'convert_guest_confirm' => 'මෙම අමුත්තා පරිශීලකයෙකු බවට පරිවර්තනය කරන්නද?',
'convert_guest_manually' => 'ලියාපදිංචි පරිශීලකයා බවට පරිවර්තනය කරන්න',
'convert_guest_success' => 'පරිශීලක ලියාපදිංචි ගිණුමක් බවට පරිවර්තනය කර ඇත',
'impersonate_user' => 'පරිශීලක ලෙස පෙනී සිටින්න',
'impersonate_confirm' => 'මෙම පරිශීලකයා ලෙස පෙනී සිටින්නද? ලොග් අවුට් වීමෙන් ඔබට ඔබේ මුල් තත්වයට ආපසු යා හැක.',
'impersonate_success' => 'ඔබ දැන් මෙම පරිශීලකයා ලෙස පෙනී සිටියි',
'delete_confirm' => 'ඔබට ඇත්තටම මෙම පරිශීලකයා මකා දැමීමට අවශ්‍යද?',
'unban_user' => 'මෙම පරිශීලක තහනම ඉවත් කරන්න',
'unban_confirm' => 'ඔබට ඇත්තටම මෙම පරිශීලකයාගේ තහනම ඉවත් කිරීමට අවශ්‍යද?',
'unbanned_success' => 'පරිශීලක තහනම ඉවත් කර ඇත',
'return_to_list' => 'පරිශීලක ලැයිස්තුව වෙත ආපසු යන්න',
'update_details' => 'යාවත්කාලීන විස්තර',
'bulk_actions' => 'තොග ක්රියාවන්',
'delete_selected' => 'තෝරාගත් මකන්න',
'delete_selected_confirm' => 'තෝරාගත් පරිශීලකයින් මකන්නද?',
'delete_selected_empty' => 'මකා දැමීමට තෝරාගත් පරිශීලකයන් නැත.',
'delete_selected_success' => 'තෝරාගත් පරිශීලකයින් සාර්ථකව මකා දමන ලදී.',
'activate_selected' => 'තෝරාගත් සක්රිය කරන්න',
'activate_selected_confirm' => 'තෝරාගත් පරිශීලකයන් සක්රිය කරන්නද?',
'activate_selected_empty' => 'සක්‍රිය කිරීමට තෝරාගත් පරිශීලකයන් නොමැත.',
'activate_selected_success' => ' තෝරාගත් පරිශීලකයින් සාර්ථකව සක්‍රිය කර ඇත.',
'deactivate_selected' => 'තෝරාගත් අක්රිය කරන්න',
'deactivate_selected_confirm' => 'තෝරාගත් පරිශීලකයන් අක්‍රිය කරන්නද?',
'deactivate_selected_empty' => 'අක්‍රිය කිරීමට තෝරාගත් පරිශීලකයන් නැත.',
'deactivate_selected_success' => 'තෝරාගත් පරිශීලකයින් සාර්ථකව අක්‍රිය කර ඇත.',
'restore_selected' => 'තෝරාගත් ප්‍රතිසාධනය කරන්න',
'restore_selected_confirm' => 'තෝරාගත් පරිශීලකයන් ප්‍රතිසාධනය කරන්නද?',
'restore_selected_empty' => 'ප්‍රතිසාධනය කිරීමට තෝරාගත් පරිශීලකයන් නැත.',
'restore_selected_success' => 'තෝරාගත් පරිශීලකයින් සාර්ථකව ප්‍රතිසාධනය කරන ලදී.',
'ban_selected' => 'තහනම් තෝරන ලදී',
'ban_selected_confirm' => 'තෝරාගත් පරිශීලකයින් තහනම් කරන්නද?',
'ban_selected_empty' => 'තහනම් කිරීමට තෝරාගත් පරිශීලකයන් නොමැත.',
'ban_selected_success' => 'තෝරාගත් පරිශීලකයින් සාර්ථකව තහනම් කරන ලදී.',
'unban_selected' => 'තහනම් ඉවත් කිරීම තෝරා ඇත',
'unban_selected_confirm' => 'තෝරාගත් පරිශීලකයින් තහනම් නොකරන්නද?',
'unban_selected_empty' => 'තහනම් කිරීම ඉවත් කිරීමට තෝරාගත් පරිශීලකයන් නොමැත.',
'unban_selected_success' => 'තෝරාගත් පරිශීලකයින් සාර්ථකව තහනම් කිරීම ඉවත් කරන ලදී.',
'unsuspend' => 'අත්හිටුවීම ඉවත් කරන්න',
'unsuspend_success' => 'පරිශීලකයාගේ අත්හිටුවීම ඉවත් කර ඇත.',
'unsuspend_confirm' => 'මෙම පරිශීලකයාගේ අත්හිටුවීම ඉවත් කරන්නද?'
],
'settings' => [
'users' => 'පරිශීලකයන්',
'menu_label' => 'පරිශීලක සැකසුම්',
'menu_description' => 'පරිශීලක සත්‍යාපනය, ලියාපදිංචිය සහ සක්‍රිය කිරීමේ සැකසුම් කළමනාකරණය කරන්න.',
'activation_tab' => 'සක්රිය කිරීම',
'signin_tab' => 'පුරන්න',
'registration_tab' => 'ලියාපදිංචි කිරීම',
'profile_tab' => 'පැතිකඩ',
'notifications_tab' => 'දැනුම්දීම්',
'allow_registration' => 'පරිශීලක ලියාපදිංචියට ඉඩ දෙන්න',
'allow_registration_comment' => 'මෙය ආබාධිත නම්, පරිශීලකයින් නිර්මාණය කළ හැක්කේ පරිපාලකයින්ට පමණි.',
'activate_mode' => 'සක්රිය කිරීමේ මාදිලිය',
'activate_mode_comment' => 'පරිශීලක ගිණුමක් සක්රිය කළ යුතු ආකාරය තෝරන්න.',
'activate_mode_auto' => 'ස්වයංක්රීය',
'activate_mode_auto_comment' => 'ලියාපදිංචි වීමෙන් පසු ස්වයංක්රීයව සක්රිය කර ඇත.',
'activate_mode_user' => 'පරිශීලක',
'activate_mode_user_comment' => 'පරිශීලකයා තැපෑල භාවිතයෙන් තමන්ගේම ගිණුම සක්රිය කරයි.',
'activate_mode_admin' => 'පරිපාලක',
'activate_mode_admin_comment' => 'පරිශීලකයෙකු සක්‍රිය කළ හැක්කේ පරිපාලකයෙකුට පමණි.',
'require_activation' => 'පුරනය වීමට සක්‍රිය කිරීම අවශ්‍ය වේ',
'require_activation_comment' => 'පුරනය වීමට පරිශීලකයින්ට සක්‍රිය කළ ගිණුමක් තිබිය යුතුය.',
'use_throttle' => 'Throttle උත්සාහයන්',
'use_throttle_comment' => 'නැවත නැවත අසාර්ථක පුරනය වීමේ උත්සාහයන් පරිශීලකයා තාවකාලිකව අත්හිටුවනු ඇත.',
'use_register_throttle' => 'Throttle ලියාපදිංචිය',
'use_register_throttle_comment' => 'කෙටි කාලීනව එකම IP වෙතින් බහු ලියාපදිංචි කිරීම් වලක්වන්න.',
'block_persistence' => 'සමගාමී සැසි වැළැක්වීම',
'block_persistence_comment' => 'සබල කළ විට පරිශීලකයින්ට එකවර උපාංග කිහිපයකට පුරනය විය නොහැක.',
'login_attribute' => 'පිවිසුම් ගුණාංගය',
'login_attribute_comment' => 'පුරනය වීම සඳහා භාවිතා කළ යුතු මූලික පරිශීලක විස්තර තෝරන්න.',
'remember_login' => 'පිවිසුම් මාදිලිය මතක තබා ගන්න',
'remember_login_comment' => 'පරිශීලක සැසිය ස්ථීර විය යුතුද යන්න තෝරන්න.',
'remember_always' => 'සැමවිටම',
'remember_never' => 'කවදාවත් නැහැ',
'remember_ask' => 'පුරනය වීමේදී පරිශීලකයාගෙන් විමසන්න',
],
'user' => [
'label' => 'පරිශීලක',
'id' => 'හැඳුනුම්පත',
'username' => 'පරිශීලක නාමය',
'name' => 'නම',
'name_empty' => 'නිර්නාමික',
'surname' => 'වාසගම',
'email' => 'විද්යුත් තැපෑල',
'created_at' => 'ලියාපදිංචි',
'last_seen' => 'අන්තිමට දැක්කේ',
'is_guest' => 'අමුත්තන්ගේ',
'joined' => 'එකතු වුණා',
'is_online' => 'දැන් ඔන්ලයින්',
'is_offline' => 'දැනට නොබැඳි',
'send_invite' => 'විද්‍යුත් තැපෑලෙන් ආරාධනාවක් යවන්න',
'send_invite_comment' => 'පිවිසුම් සහ මුරපද තොරතුරු අඩංගු පිළිගැනීමේ පණිවිඩයක් යවයි.',
'create_password' => 'මුරපදය සාදන්න',
'create_password_comment' => 'පුරනය වීමට භාවිතා කරන නව මුරපදයක් ඇතුලත් කරන්න.',
'reset_password' => 'මුරපදය නැවත සකසන්න',
'reset_password_comment' => 'මෙම පරිශීලක මුරපදය නැවත සැකසීමට, මෙහි නව මුරපදයක් ඇතුළත් කරන්න.',
'confirm_password' => 'මුරපදය තහවුරු කිරීම',
'confirm_password_comment' => 'එය තහවුරු කිරීමට මුරපදය නැවත ඇතුල් කරන්න.',
'groups' => 'කණ්ඩායම්',
'empty_groups' => 'ලබා ගත හැකි පරිශීලක කණ්ඩායම් නොමැත.',
'avatar' => 'පැතිකඩ පින්තූරය',
'details' => 'විස්තර',
'account' => 'ගිණුම',
'block_mail' => 'මෙම පරිශීලකයාට යවන සියලුම පිටතට යන තැපැල් අවහිර කරන්න.',
'status_label' => 'තත්ත්වය',
'status_guest' => 'අමුත්තන්ගේ',
'status_activated' => 'සක්රිය කර ඇත',
'status_registered' => 'ලියාපදිංචි',
'created_ip_address' => 'සාදන ලද IP ලිපිනය',
'last_ip_address' => 'අවසාන IP ලිපිනය',
],
'group' => [
'label' => 'සමූහය',
'id' => 'හැඳුනුම්පත',
'name' => 'නම',
'description_field' => 'විස්තර',
'code' => 'කේතය',
'code_comment' => 'මෙම කණ්ඩායම හඳුනා ගැනීමට භාවිතා කරන අද්විතීය කේතයක් ඇතුළත් කරන්න.',
'created_at' => 'නිර්මාණය කළා',
'users_count' => 'පරිශීලකයන්'
],
'groups' => [
'menu_label' => 'කණ්ඩායම්',
'all_groups' => 'පරිශීලක කණ්ඩායම්',
'new_group' => 'නව සමූහය',
'delete_selected_confirm' => 'ඔබට ඇත්තටම තෝරාගත් කණ්ඩායම් මැකීමට අවශ්‍යද?',
'list_title' => 'කණ්ඩායම් කළමනාකරණය කරන්න',
'delete_confirm' => 'ඔබට ඇත්තටම මෙම සමූහය මැකීමට අවශ්‍යද?',
'delete_selected_success' => 'තෝරාගත් කණ්ඩායම් සාර්ථකව මකා දමන ලදී.',
'delete_selected_empty' => 'මකා දැමීමට තෝරාගත් කණ්ඩායම් නොමැත.',
'return_to_list' => 'කණ්ඩායම් ලැයිස්තුව වෙත ආපසු යන්න',
'return_to_users' => 'පරිශීලක ලැයිස්තුව වෙත ආපසු යන්න',
'create_title' => 'පරිශීලක කණ්ඩායමක් සාදන්න',
'update_title' => 'පරිශීලක කණ්ඩායම සංස්කරණය කරන්න',
'preview_title' => 'පරිශීලක කණ්ඩායම පෙරදසුන් කරන්න'
],
'login' => [
'attribute_email' => 'විද්යුත් තැපෑල',
'attribute_username' => 'පරිශීලක නාමය'
],
'account' => [
'account' => 'ගිණුම',
'account_desc' => 'පරිශීලක කළමනාකරණ පෝරමය.',
'banned' => 'සමාවන්න, මෙම පරිශීලකයා දැනට සක්‍රිය කර නැත. වැඩිදුර සහාය සඳහා කරුණාකර අප හා සම්බන්ධ වන්න.',
'redirect_to' => 'වෙත හරවා යවන්න',
'redirect_to_desc' => 'යාවත්කාලීන කිරීම, පුරනය වීම හෝ ලියාපදිංචි වීමෙන් පසු නැවත යොමු කිරීමට පිටුවේ නම.',
'code_param' => 'සක්රිය කිරීමේ කේතය පරාමිතිය',
'code_param_desc' => 'ලියාපදිංචි සක්‍රිය කිරීමේ කේතය සඳහා භාවිතා කරන පිටු URL පරාමිතිය',
'force_secure' => 'ආරක්ෂිත ප්‍රොටෝකෝලය බල කරන්න',
'force_secure_desc' => 'සෑම විටම URL HTTPS ක්‍රමයෙන් හරවා යවන්න.',
'invalid_user' => 'ලබා දී ඇති අක්තපත්‍ර සමඟ පරිශීලකයෙකු හමු නොවීය.',
'invalid_activation_code' => 'වලංගු නොවන සක්‍රීය කිරීමේ කේතයක් සපයා ඇත.',
'invalid_deactivation_pass' => 'ඔබ ඇතුළු කළ මුරපදය වලංගු නොවේ.',
'invalid_current_pass' => 'ඔබ ඇතුළත් කළ වත්මන් මුරපදය වලංගු නොවේ.',
'success_activation' => 'ඔබගේ ගිණුම සාර්ථකව සක්‍රිය කර ඇත.',
'success_deactivation' => 'ඔබගේ ගිණුම සාර්ථකව අක්‍රිය කර ඇත. ඔබ යනවා දැකීම ගැන කණගාටුයි!',
'success_saved' => 'සැකසීම් සාර්ථකව සුරකින ලදී!',
'login_first' => 'ඔබ මුලින්ම ලොග් විය යුතුය!',
'already_active' => 'ඔබගේ ගිණුම දැනටමත් සක්‍රිය කර ඇත!',
'activation_email_sent' => 'ඔබගේ ඊමේල් ලිපිනයට සක්‍රිය කිරීමේ විද්‍යුත් තැපෑලක් යවා ඇත.',
'activation_by_admin' => 'ඔබ සාර්ථකව ලියාපදිංචි වී ඇත. ඔබගේ ගිණුම තවමත් සක්‍රිය නොවන අතර පරිපාලකයෙකු විසින් අනුමත කළ යුතුය.',
'registration_disabled' => 'ලියාපදිංචි කිරීම් දැනට අබල කර ඇත.',
'registration_throttled' => 'ලියාපදිංචිය අවහිර කර ඇත. කරුණාකර පසුව නැවත උත්සාහ කරන්න.',
'sign_in' => 'පුරන්න',
'register' => 'ලියාපදිංචි කරන්න',
'full_name' => 'සම්පූර්ණ නම',
'email' => 'විද්යුත් තැපෑල',
'password' => 'මුරපදය',
'login' => 'ඇතුල් වන්න',
'new_password' => 'නව මුරපදය',
'new_password_confirm' => 'නව මුරපදය තහවුරු කරන්න',
'update_requires_password' => 'යාවත්කාලීන කිරීමේදී මුරපදය තහවුරු කරන්න',
'update_requires_password_comment' => 'ඔවුන්ගේ පැතිකඩ වෙනස් කිරීමේදී පරිශීලකයාගේ වත්මන් මුරපදය අවශ්‍ය වේ.',
'activation_page' => 'සක්රිය කිරීමේ පිටුව',
'activation_page_comment' => 'පරිශීලක ගිණුම සක්රිය කිරීම සඳහා භාවිතා කිරීමට පිටුවක් තෝරන්න',
'reset_page' => 'පිටුව යළි පිහිටුවන්න',
'reset_page_comment' => 'ගිණුමේ මුරපදය යළි පිහිටුවීම සඳහා භාවිතා කිරීමට පිටුවක් තෝරන්න',
],
'reset_password' => [
'reset_password' => 'මුරපදය නැවත සකසන්න',
'reset_password_desc' => ' අමතක වූ මුරපද පෝරමය.',
'code_param' => 'පරාමිතිය කේතය නැවත සකසන්න',
'code_param_desc' => 'යළි පිහිටුවීමේ කේතය සඳහා භාවිතා කරන ලද පිටු URL පරාමිතිය'
],
'session' => [
'session' => 'වාරය',
'session_desc' => 'පරිශීලක සැසිය පිටුවකට එක් කරන අතර පිටු ප්‍රවේශය සීමා කළ හැක.',
'security_title' => 'පමණක් ඉඩ දෙන්න',
'security_desc' => 'මෙම පිටුවට ප්‍රවේශ වීමට අවසර ඇත්තේ කාටද.',
'all' => 'සියලුම',
'users' => 'පරිශීලකයන්',
'guests' => 'අමුත්තන්',
'allowed_groups_title' => 'කණ්ඩායම් වලට ඉඩ දෙන්න',
'allowed_groups_description' => 'සියලුම කණ්ඩායම්වලට ඉඩ දීමට අවසර දී ඇති කණ්ඩායම් හෝ කිසිවෙකුට ඉඩ නොදෙන්න',
'redirect_title' => 'වෙත හරවා යවන්න',
'redirect_desc' => 'ප්‍රවේශය ප්‍රතික්ෂේප කළහොත් යළි-යොමු කිරීමට පිටුවේ නම.',
'logout' => 'ඔබ සාර්ථකව ලොග් අවුට් වී ඇත!',
'stop_impersonate_success' => 'ඔබ තවදුරටත් පරිශීලකයෙකු ලෙස පෙනී සිටින්නේ නැත.',
]
];

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