new: [mailing-list] Added mailing list feature - WiP
parent
b9bcb14bea
commit
fe9fbe2e99
|
@ -0,0 +1,122 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Migrations\AbstractMigration;
|
||||||
|
use Phinx\Db\Adapter\MysqlAdapter;
|
||||||
|
|
||||||
|
class MailingLists extends AbstractMigration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Change Method.
|
||||||
|
*
|
||||||
|
* More information on this method is available here:
|
||||||
|
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
|
||||||
|
public $autoId = false; // turn off automatic `id` column create. We want it to be `int(10) unsigned`
|
||||||
|
|
||||||
|
|
||||||
|
public function change()
|
||||||
|
{
|
||||||
|
$mailinglists = $this->table('mailing_lists', [
|
||||||
|
'signed' => false,
|
||||||
|
'collation' => 'utf8mb4_unicode_ci',
|
||||||
|
]);
|
||||||
|
$mailinglists
|
||||||
|
->addColumn('id', 'integer', [
|
||||||
|
'autoIncrement' => true,
|
||||||
|
'limit' => 10,
|
||||||
|
'signed' => false,
|
||||||
|
])
|
||||||
|
->addPrimaryKey('id')
|
||||||
|
->addColumn('uuid', 'uuid', [
|
||||||
|
'default' => null,
|
||||||
|
'null' => false,
|
||||||
|
])
|
||||||
|
->addColumn('name', 'string', [
|
||||||
|
'default' => null,
|
||||||
|
'null' => false,
|
||||||
|
'limit' => 191,
|
||||||
|
'comment' => 'The name of the mailing list',
|
||||||
|
])
|
||||||
|
->addColumn('recipients', 'string', [
|
||||||
|
'default' => null,
|
||||||
|
'null' => true,
|
||||||
|
'limit' => 191,
|
||||||
|
'comment' => 'Human-readable description of who the intended recipients.',
|
||||||
|
])
|
||||||
|
->addColumn('description', 'text', [
|
||||||
|
'default' => null,
|
||||||
|
'null' => true,
|
||||||
|
'comment' => 'Additional description of the mailing list'
|
||||||
|
])
|
||||||
|
->addColumn('user_id', 'integer', [
|
||||||
|
'default' => null,
|
||||||
|
'null' => true,
|
||||||
|
'signed' => false,
|
||||||
|
'length' => 10,
|
||||||
|
])
|
||||||
|
->addColumn('active', 'boolean', [
|
||||||
|
'default' => 0,
|
||||||
|
'null' => false,
|
||||||
|
])
|
||||||
|
->addColumn('deleted', 'boolean', [
|
||||||
|
'default' => 0,
|
||||||
|
'null' => false,
|
||||||
|
])
|
||||||
|
->addColumn('created', 'datetime', [
|
||||||
|
'default' => null,
|
||||||
|
'null' => false,
|
||||||
|
])
|
||||||
|
->addColumn('modified', 'datetime', [
|
||||||
|
'default' => null,
|
||||||
|
'null' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
$mailinglists->addForeignKey('user_id', 'users', 'id', ['delete' => 'CASCADE', 'update' => 'CASCADE']);
|
||||||
|
|
||||||
|
$mailinglists->addIndex(['uuid'], ['unique' => true])
|
||||||
|
->addIndex('name')
|
||||||
|
->addIndex('recipients')
|
||||||
|
->addIndex('user_id')
|
||||||
|
->addIndex('active')
|
||||||
|
->addIndex('deleted')
|
||||||
|
->addIndex('created')
|
||||||
|
->addIndex('modified');
|
||||||
|
|
||||||
|
$mailinglists->create();
|
||||||
|
|
||||||
|
|
||||||
|
$mailinglists_individuals = $this->table('mailing_lists_individuals', [
|
||||||
|
'signed' => false,
|
||||||
|
'collation' => 'utf8mb4_unicode_ci',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$mailinglists_individuals
|
||||||
|
->addColumn('id', 'integer', [
|
||||||
|
'autoIncrement' => true,
|
||||||
|
'limit' => 10,
|
||||||
|
'signed' => false,
|
||||||
|
])
|
||||||
|
->addPrimaryKey('id')
|
||||||
|
->addColumn('mailing_list_id', 'integer', [
|
||||||
|
'default' => null,
|
||||||
|
'null' => true,
|
||||||
|
'signed' => false,
|
||||||
|
'length' => 10,
|
||||||
|
])
|
||||||
|
->addColumn('individual_id', 'integer', [
|
||||||
|
'default' => null,
|
||||||
|
'null' => true,
|
||||||
|
'signed' => false,
|
||||||
|
'length' => 10,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$mailinglists_individuals->addIndex(['mailing_list_id', 'individual_id'], ['unique' => true]);
|
||||||
|
|
||||||
|
$mailinglists_individuals->create();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ class TagHelper extends Helper
|
||||||
'data-text-colour' => h($tag['text_colour']),
|
'data-text-colour' => h($tag['text_colour']),
|
||||||
];
|
];
|
||||||
}, $options['allTags']) : [];
|
}, $options['allTags']) : [];
|
||||||
$classes = ['tag-input', 'flex-grow-1'];
|
$classes = ['select2-input', 'flex-grow-1'];
|
||||||
$url = '';
|
$url = '';
|
||||||
if (!empty($this->getConfig('editable'))) {
|
if (!empty($this->getConfig('editable'))) {
|
||||||
$url = $this->Url->build([
|
$url = $this->Url->build([
|
||||||
|
|
|
@ -23,7 +23,7 @@ function createTagPicker(clicked) {
|
||||||
|
|
||||||
const $clicked = $(clicked)
|
const $clicked = $(clicked)
|
||||||
const $container = $clicked.closest('.tag-container')
|
const $container = $clicked.closest('.tag-container')
|
||||||
const $select = $container.parent().find('select.tag-input').removeClass('d-none')
|
const $select = $container.parent().find('select.select2-input').removeClass('d-none')
|
||||||
closePicker($select, $container)
|
closePicker($select, $container)
|
||||||
const $pickerContainer = $('<div></div>').addClass(['picker-container', 'd-flex'])
|
const $pickerContainer = $('<div></div>').addClass(['picker-container', 'd-flex'])
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ function refreshTagList(apiResult, $container) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function initSelect2Pickers() {
|
function initSelect2Pickers() {
|
||||||
$('select.tag-input').each(function() {
|
$('select.select2-input').each(function() {
|
||||||
if (!$(this).hasClass("select2-hidden-accessible")) {
|
if (!$(this).hasClass("select2-hidden-accessible")) {
|
||||||
initSelect2Picker($(this))
|
initSelect2Picker($(this))
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,11 @@ class Sidemenu {
|
||||||
'label' => __('Sharing Groups'),
|
'label' => __('Sharing Groups'),
|
||||||
'icon' => $this->iconTable['SharingGroups'],
|
'icon' => $this->iconTable['SharingGroups'],
|
||||||
'url' => '/sharingGroups/index',
|
'url' => '/sharingGroups/index',
|
||||||
|
],
|
||||||
|
'MailingLists' => [
|
||||||
|
'label' => __('Mailing Lists'),
|
||||||
|
'icon' => $this->iconTable['MailingLists'],
|
||||||
|
'url' => '/mailingLists/index',
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
__('Synchronisation') => [
|
__('Synchronisation') => [
|
||||||
|
|
|
@ -24,6 +24,7 @@ class NavigationComponent extends Component
|
||||||
'Organisations' => 'building',
|
'Organisations' => 'building',
|
||||||
'EncryptionKeys' => 'key',
|
'EncryptionKeys' => 'key',
|
||||||
'SharingGroups' => 'user-friends',
|
'SharingGroups' => 'user-friends',
|
||||||
|
'MailingLists' => 'mail-bulk',
|
||||||
'Broods' => 'network-wired',
|
'Broods' => 'network-wired',
|
||||||
'Roles' => 'id-badge',
|
'Roles' => 'id-badge',
|
||||||
'Users' => 'users',
|
'Users' => 'users',
|
||||||
|
@ -158,6 +159,7 @@ class NavigationComponent extends Component
|
||||||
'Tags',
|
'Tags',
|
||||||
'LocalTools',
|
'LocalTools',
|
||||||
'UserSettings',
|
'UserSettings',
|
||||||
|
'MailingLists',
|
||||||
];
|
];
|
||||||
foreach ($CRUDControllers as $controller) {
|
foreach ($CRUDControllers as $controller) {
|
||||||
$bcf->setDefaultCRUDForModel($controller);
|
$bcf->setDefaultCRUDForModel($controller);
|
||||||
|
|
|
@ -0,0 +1,196 @@
|
||||||
|
<?php
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Controller\AppController;
|
||||||
|
use Cake\Utility\Inflector;
|
||||||
|
use Cake\Utility\Hash;
|
||||||
|
use Cake\Utility\Text;
|
||||||
|
use \Cake\Database\Expression\QueryExpression;
|
||||||
|
use Cake\Error\Debugger;
|
||||||
|
|
||||||
|
class MailingListsController extends AppController
|
||||||
|
{
|
||||||
|
public $filterFields = ['MailingLists.uuid', 'MailingLists.name', 'description', 'releasability'];
|
||||||
|
public $quickFilterFields = ['MailingLists.uuid', ['MailingLists.name' => true], ['description' => true], ['releasability' => true]];
|
||||||
|
public $containFields = ['Users', 'Individuals'];
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$this->CRUD->index([
|
||||||
|
'contain' => $this->containFields,
|
||||||
|
'filters' => $this->filterFields,
|
||||||
|
'quickFilters' => $this->quickFilterFields
|
||||||
|
]);
|
||||||
|
$responsePayload = $this->CRUD->getResponsePayload();
|
||||||
|
if (!empty($responsePayload)) {
|
||||||
|
return $responsePayload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function add()
|
||||||
|
{
|
||||||
|
$this->CRUD->add([
|
||||||
|
'override' => [
|
||||||
|
'user_id' => $this->ACL->getUser()['id']
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
$responsePayload = $this->CRUD->getResponsePayload();
|
||||||
|
if (!empty($responsePayload)) {
|
||||||
|
return $responsePayload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view($id)
|
||||||
|
{
|
||||||
|
$this->CRUD->view($id, [
|
||||||
|
'contain' => $this->containFields
|
||||||
|
]);
|
||||||
|
$responsePayload = $this->CRUD->getResponsePayload();
|
||||||
|
if (!empty($responsePayload)) {
|
||||||
|
return $responsePayload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit($id = false)
|
||||||
|
{
|
||||||
|
$this->CRUD->edit($id);
|
||||||
|
$responsePayload = $this->CRUD->getResponsePayload();
|
||||||
|
if (!empty($responsePayload)) {
|
||||||
|
return $responsePayload;
|
||||||
|
}
|
||||||
|
$this->render('add');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete($id)
|
||||||
|
{
|
||||||
|
$this->CRUD->delete($id);
|
||||||
|
$responsePayload = $this->CRUD->getResponsePayload();
|
||||||
|
if (!empty($responsePayload)) {
|
||||||
|
return $responsePayload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function listIndividuals($mailinglist_id)
|
||||||
|
{
|
||||||
|
$individuals = $this->MailingLists->get($mailinglist_id, [
|
||||||
|
'contain' => 'Individuals'
|
||||||
|
])->individuals;
|
||||||
|
$params = $this->ParamHandler->harvestParams(['quickFilter']);
|
||||||
|
if (!empty($params['quickFilter'])) {
|
||||||
|
// foreach ($sharingGroup['sharing_group_orgs'] as $k => $org) {
|
||||||
|
// if (strpos($org['name'], $params['quickFilter']) === false) {
|
||||||
|
// unset($sharingGroup['sharing_group_orgs'][$k]);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// $sharingGroup['sharing_group_orgs'] = array_values($sharingGroup['sharing_group_orgs']);
|
||||||
|
}
|
||||||
|
if ($this->ParamHandler->isRest()) {
|
||||||
|
return $this->RestResponse->viewData($individuals, 'json');
|
||||||
|
}
|
||||||
|
$this->set('mailing_list_id', $mailinglist_id);
|
||||||
|
$this->set('individuals', $individuals);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addIndividual($mailinglist_id)
|
||||||
|
{
|
||||||
|
$mailingList = $this->MailingLists->get($mailinglist_id, [
|
||||||
|
'contain' => 'Individuals'
|
||||||
|
]);
|
||||||
|
$conditions = [];
|
||||||
|
$dropdownData = [
|
||||||
|
'individuals' => $this->MailingLists->Individuals->getTarget()->find('list', [
|
||||||
|
'sort' => ['name' => 'asc'],
|
||||||
|
'conditions' => $conditions
|
||||||
|
])
|
||||||
|
];
|
||||||
|
if ($this->request->is('post') || $this->request->is('put')) {
|
||||||
|
$memberIDs = $this->request->getData()['individuals'];
|
||||||
|
$members = $this->MailingLists->Individuals->getTarget()->find()->where([
|
||||||
|
'id IN' => $memberIDs
|
||||||
|
])->all()->toArray();
|
||||||
|
$success = (bool)$this->MailingLists->Individuals->link($mailingList, $members);
|
||||||
|
if ($success) {
|
||||||
|
$message = __n('%s individual added to the mailing list.', '%s Individuals added to the mailing list.', count($members), count($members));
|
||||||
|
$mailingList = $this->MailingLists->get($mailingList->id);
|
||||||
|
} else {
|
||||||
|
$message = __n('The individual could not be added to the mailing list.', 'The Individuals could not be added to the mailing list.', count($members));
|
||||||
|
}
|
||||||
|
$this->CRUD->setResponseForController('add_individuals', $success, $message, $mailingList, $mailingList->getErrors());
|
||||||
|
$responsePayload = $this->CRUD->getResponsePayload();
|
||||||
|
if (!empty($responsePayload)) {
|
||||||
|
return $responsePayload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->set(compact('dropdownData'));
|
||||||
|
$this->set('mailinglist_id', $mailinglist_id);
|
||||||
|
$this->set('mailingList', $mailingList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeIndividual($mailinglist_id, $individual_id=null)
|
||||||
|
{
|
||||||
|
$mailingList = $this->MailingLists->get($mailinglist_id, [
|
||||||
|
'contain' => 'Individuals'
|
||||||
|
]);
|
||||||
|
$individual = [];
|
||||||
|
if (!is_null($individual_id)) {
|
||||||
|
$individual = $this->MailingLists->Individuals->get($individual_id);
|
||||||
|
}
|
||||||
|
if ($this->request->is('post') || $this->request->is('delete')) {
|
||||||
|
$success = false;
|
||||||
|
if (!is_null($individual_id)) {
|
||||||
|
$individual = $this->MailingLists->Individuals->get($individual_id);
|
||||||
|
$success = (bool)$this->MailingLists->Individuals->unlink($mailingList, [$individual]);
|
||||||
|
if ($success) {
|
||||||
|
$message = __('Individual removed from the mailing list.');
|
||||||
|
$mailingList = $this->MailingLists->get($mailingList->id);
|
||||||
|
} else {
|
||||||
|
$message = __n('Individual could not be removed from the mailing list.');
|
||||||
|
}
|
||||||
|
$this->CRUD->setResponseForController('remove_individuals', $success, $message, $mailingList, $mailingList->getErrors());
|
||||||
|
} else {
|
||||||
|
$params = $this->ParamHandler->harvestParams(['ids']);
|
||||||
|
if (!empty($params['ids'])) {
|
||||||
|
$params['ids'] = json_decode($params['ids']);
|
||||||
|
}
|
||||||
|
if (empty($params['ids'])) {
|
||||||
|
throw new NotFoundException(__('Invalid {0}.', Inflector::singularize($this->MailingLists->Individuals->getAlias())));
|
||||||
|
}
|
||||||
|
$individuals = $this->MailingLists->Individuals->find()->where([
|
||||||
|
'id IN' => array_map('intval', $params['ids'])
|
||||||
|
])->all()->toArray();
|
||||||
|
$unlinkSuccesses = 0;
|
||||||
|
foreach ($individuals as $individual) {
|
||||||
|
$success = (bool)$this->MailingLists->Individuals->unlink($mailingList, [$individual]);
|
||||||
|
$results[] = $success;
|
||||||
|
if ($success) {
|
||||||
|
$unlinkSuccesses++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$mailingList = $this->MailingLists->get($mailingList->id);
|
||||||
|
$success = $unlinkSuccesses == count($individuals);
|
||||||
|
$message = __(
|
||||||
|
'{0} {1} have been removed.',
|
||||||
|
$unlinkSuccesses == count($individuals) ? __('All') : sprintf('%s / %s', $unlinkSuccesses, count($individuals)),
|
||||||
|
Inflector::singularize($this->MailingLists->Individuals->getAlias())
|
||||||
|
);
|
||||||
|
$this->CRUD->setResponseForController('remove_individuals', $success, $message, $mailingList, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
$responsePayload = $this->CRUD->getResponsePayload();
|
||||||
|
if (!empty($responsePayload)) {
|
||||||
|
return $responsePayload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->set('mailinglist_id', $mailinglist_id);
|
||||||
|
$this->set('mailingList', $mailingList);
|
||||||
|
if (!empty($individual)) {
|
||||||
|
$this->set('deletionText', __('Are you sure you want to remove `{0} ({1})` from the mailing list?', $individual->full_name, $individual->email));
|
||||||
|
} else {
|
||||||
|
$this->set('deletionText', __('Are you sure you want to remove multiples individuals from the mailing list?'));
|
||||||
|
}
|
||||||
|
$this->set('postLinkParameters', ['action' => 'removeIndividual', $mailinglist_id, $individual_id]);
|
||||||
|
$this->viewBuilder()->setLayout('ajax');
|
||||||
|
$this->render('/genericTemplates/delete');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Model\Entity;
|
||||||
|
|
||||||
|
use App\Model\Entity\AppModel;
|
||||||
|
use Cake\ORM\Entity;
|
||||||
|
|
||||||
|
class MailingList extends AppModel
|
||||||
|
{
|
||||||
|
protected $_accessible = [
|
||||||
|
'*' => true,
|
||||||
|
'id' => false,
|
||||||
|
'uuid' => false,
|
||||||
|
'user_id' => false,
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $_accessibleOnNew = [
|
||||||
|
'uuid' => true,
|
||||||
|
'user_id' => true,
|
||||||
|
];
|
||||||
|
}
|
|
@ -36,6 +36,7 @@ class IndividualsTable extends AppTable
|
||||||
$this->belongsToMany('Organisations', [
|
$this->belongsToMany('Organisations', [
|
||||||
'through' => 'Alignments',
|
'through' => 'Alignments',
|
||||||
]);
|
]);
|
||||||
|
$this->belongsToMany('MailingLists');
|
||||||
$this->setDisplayField('email');
|
$this->setDisplayField('email');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Model\Table;
|
||||||
|
|
||||||
|
use App\Model\Table\AppTable;
|
||||||
|
use Cake\ORM\Table;
|
||||||
|
use Cake\Validation\Validator;
|
||||||
|
use Cake\ORM\RulesChecker;
|
||||||
|
use Cake\ORM\TableRegistry;
|
||||||
|
|
||||||
|
class MailingListsTable extends AppTable
|
||||||
|
{
|
||||||
|
public function initialize(array $config): void
|
||||||
|
{
|
||||||
|
parent::initialize($config);
|
||||||
|
$this->addBehavior('UUID');
|
||||||
|
$this->addBehavior('Timestamp');
|
||||||
|
$this->belongsTo(
|
||||||
|
'Users'
|
||||||
|
);
|
||||||
|
// $this->belongsToMany(
|
||||||
|
// 'Individuals',
|
||||||
|
// [
|
||||||
|
// 'className' => 'Individuals',
|
||||||
|
// 'foreignKey' => 'individual_id',
|
||||||
|
// 'joinTable' => 'sgo',
|
||||||
|
// 'targetForeignKey' => 'organisation_id'
|
||||||
|
// ]
|
||||||
|
// );
|
||||||
|
|
||||||
|
$this->belongsToMany('Individuals', [
|
||||||
|
'joinTable' => 'mailing_lists_individuals',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->setDisplayField('name');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validationDefault(Validator $validator): Validator
|
||||||
|
{
|
||||||
|
$validator
|
||||||
|
->requirePresence(['name', 'releasability'], 'create');
|
||||||
|
return $validator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildRules(RulesChecker $rules): RulesChecker
|
||||||
|
{
|
||||||
|
return $rules;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
echo $this->element('genericElements/Form/genericForm', [
|
||||||
|
'data' => [
|
||||||
|
'description' => __('Mailing list are email distribution lists containing individuals.'),
|
||||||
|
'model' => 'MailingLists',
|
||||||
|
'fields' => [
|
||||||
|
[
|
||||||
|
'field' => 'name'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'field' => 'uuid',
|
||||||
|
'label' => 'UUID',
|
||||||
|
'type' => 'uuid'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'field' => 'releasability',
|
||||||
|
'type' => 'textarea'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'field' => 'description',
|
||||||
|
'type' => 'textarea'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'field' => 'active',
|
||||||
|
'type' => 'checkbox',
|
||||||
|
'default' => 1
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'submit' => [
|
||||||
|
'action' => $this->request->getParam('action')
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
?>
|
||||||
|
</div>
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
echo $this->element('genericElements/Form/genericForm', [
|
||||||
|
'data' => [
|
||||||
|
'title' => __('Add members to mailing list {0} [{1}]', h($mailingList->name), h($mailingList->id)),
|
||||||
|
// 'description' => __('Mailing list are email distribution lists containing individuals.'),
|
||||||
|
'model' => 'MailingLists',
|
||||||
|
'fields' => [
|
||||||
|
[
|
||||||
|
'field' => 'individuals',
|
||||||
|
'type' => 'dropdown',
|
||||||
|
'multiple' => true,
|
||||||
|
'select2' => true,
|
||||||
|
'label' => __('Members'),
|
||||||
|
'class' => 'select2-input',
|
||||||
|
'options' => $dropdownData['individuals']
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'submit' => [
|
||||||
|
'action' => $this->request->getParam('action')
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
?>
|
||||||
|
</div>
|
|
@ -0,0 +1,108 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
echo $this->element('genericElements/IndexTable/index_table', [
|
||||||
|
'data' => [
|
||||||
|
'data' => $data,
|
||||||
|
'top_bar' => [
|
||||||
|
'children' => [
|
||||||
|
[
|
||||||
|
'type' => 'simple',
|
||||||
|
'children' => [
|
||||||
|
'data' => [
|
||||||
|
'type' => 'simple',
|
||||||
|
'text' => __('Add mailing list'),
|
||||||
|
'popover_url' => '/MailingLists/add'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'type' => 'search',
|
||||||
|
'button' => __('Filter'),
|
||||||
|
'placeholder' => __('Enter value to search'),
|
||||||
|
'data' => '',
|
||||||
|
'searchKey' => 'value'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'type' => 'table_action',
|
||||||
|
'table_setting_id' => 'mailinglist_index',
|
||||||
|
]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'fields' => [
|
||||||
|
[
|
||||||
|
'name' => '#',
|
||||||
|
'sort' => 'id',
|
||||||
|
'class' => 'short',
|
||||||
|
'data_path' => 'id',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => __('Name'),
|
||||||
|
'sort' => 'name',
|
||||||
|
'data_path' => 'name',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => __('Owner'),
|
||||||
|
'data_path' => 'user_id',
|
||||||
|
'element' => 'user'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => __('UUID'),
|
||||||
|
'sort' => 'uuid',
|
||||||
|
'data_path' => 'uuid',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => __('Members'),
|
||||||
|
'data_path' => 'individuals',
|
||||||
|
'element' => 'count_summary',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => __('Intended recipients'),
|
||||||
|
'data_path' => 'recipients',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => __('Description'),
|
||||||
|
'data_path' => 'description',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => __('Active'),
|
||||||
|
'data_path' => 'active',
|
||||||
|
'sort' => 'active',
|
||||||
|
'element' => 'boolean',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => __('Deleted'),
|
||||||
|
'data_path' => 'deleted',
|
||||||
|
'sort' => 'deleted',
|
||||||
|
'element' => 'boolean',
|
||||||
|
],
|
||||||
|
|
||||||
|
// [
|
||||||
|
// 'name' => __('Members'),
|
||||||
|
// 'data_path' => 'alignments',
|
||||||
|
// 'element' => 'count_summary',
|
||||||
|
// 'url' => '/sharingGroups/view/{{id}}',
|
||||||
|
// 'url_data_path' => 'id'
|
||||||
|
// ]
|
||||||
|
],
|
||||||
|
'title' => __('Mailing Lists Index'),
|
||||||
|
'description' => __('Mailing list are email distribution lists containing individuals.'),
|
||||||
|
'actions' => [
|
||||||
|
[
|
||||||
|
'url' => '/mailingLists/view',
|
||||||
|
'url_params_data_paths' => ['id'],
|
||||||
|
'icon' => 'eye'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'open_modal' => '/mailingLists/edit/[onclick_params_data_path]',
|
||||||
|
'modal_params_data_path' => 'id',
|
||||||
|
'icon' => 'edit'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'open_modal' => '/mailingLists/delete/[onclick_params_data_path]',
|
||||||
|
'modal_params_data_path' => 'id',
|
||||||
|
'icon' => 'trash'
|
||||||
|
],
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
?>
|
|
@ -0,0 +1,140 @@
|
||||||
|
<?php
|
||||||
|
echo $this->element('genericElements/IndexTable/index_table', [
|
||||||
|
'data' => [
|
||||||
|
'data' => $individuals,
|
||||||
|
'skip_pagination' => 1,
|
||||||
|
'top_bar' => [
|
||||||
|
'children' => [
|
||||||
|
[
|
||||||
|
'type' => 'multi_select_actions',
|
||||||
|
'children' => [
|
||||||
|
[
|
||||||
|
'text' => __('Remove members'),
|
||||||
|
'variant' => 'danger',
|
||||||
|
'onclick' => 'removeMembers',
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'data' => [
|
||||||
|
'id' => [
|
||||||
|
'value_path' => 'id'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'type' => 'simple',
|
||||||
|
'children' => [
|
||||||
|
'data' => [
|
||||||
|
'type' => 'simple',
|
||||||
|
'text' => __('Add member'),
|
||||||
|
'popover_url' => '/mailingLists/addIndividual/' . h($mailing_list_id),
|
||||||
|
'reload_url' => '/mailingLists/listIndividuals/' . h($mailing_list_id)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'type' => 'search',
|
||||||
|
'button' => __('Filter'),
|
||||||
|
'placeholder' => __('Enter value to search'),
|
||||||
|
'data' => '',
|
||||||
|
'searchKey' => 'value'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'fields' => [
|
||||||
|
[
|
||||||
|
'name' => '#',
|
||||||
|
'sort' => 'id',
|
||||||
|
'data_path' => 'id',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => __('First name'),
|
||||||
|
'data_path' => 'first_name',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => __('Last name'),
|
||||||
|
'data_path' => 'last_name',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => __('Email'),
|
||||||
|
'data_path' => 'email',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => __('UUID'),
|
||||||
|
'sort' => 'uuid',
|
||||||
|
'data_path' => 'uuid',
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'actions' => [
|
||||||
|
[
|
||||||
|
'url' => '/individuals/view',
|
||||||
|
'url_params_data_paths' => ['id'],
|
||||||
|
'icon' => 'eye'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'open_modal' => '/mailingLists/removeIndividual/' . h($mailing_list_id) . '/[onclick_params_data_path]',
|
||||||
|
'modal_params_data_path' => 'id',
|
||||||
|
'reload_url' => '/mailingLists/listIndividuals/' . h($mailing_list_id),
|
||||||
|
'icon' => 'trash'
|
||||||
|
],
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function removeMembers(idList, selectedData, $table) {
|
||||||
|
const successCallback = function([data, modalObject]) {
|
||||||
|
UI.reload('/mailingLists/listIndividuals/<?= h($mailing_list_id) ?>', UI.getContainerForTable($table), $table)
|
||||||
|
}
|
||||||
|
const failCallback = ([data, modalObject]) => {
|
||||||
|
const tableData = selectedData.map(row => {
|
||||||
|
entryInError = data.filter(error => error.data.id == row.id)[0]
|
||||||
|
$faIcon = $('<i class="fa"></i>').addClass(entryInError.success ? 'fa-check text-success' : 'fa-times text-danger')
|
||||||
|
return [row.id, row.first_name, row.last_name, row.email, entryInError.message, JSON.stringify(entryInError.errors), $faIcon]
|
||||||
|
});
|
||||||
|
handleMessageTable(
|
||||||
|
modalObject.$modal,
|
||||||
|
['<?= __('ID') ?>', '<?= __('First name') ?>', '<?= __('Last name') ?>', '<?= __('email') ?>', '<?= __('Message') ?>', '<?= __('Error') ?>', '<?= __('State') ?>'],
|
||||||
|
tableData
|
||||||
|
)
|
||||||
|
const $footer = $(modalObject.ajaxApi.statusNode).parent()
|
||||||
|
modalObject.ajaxApi.statusNode.remove()
|
||||||
|
const $cancelButton = $footer.find('button[data-bs-dismiss="modal"]')
|
||||||
|
$cancelButton.text('<?= __('OK') ?>').removeClass('btn-secondary').addClass('btn-primary')
|
||||||
|
}
|
||||||
|
UI.submissionModal('/mailingLists/removeIndividual/<?= h($mailing_list_id) ?>', successCallback, failCallback).then(([modalObject, ajaxApi]) => {
|
||||||
|
const $idsInput = modalObject.$modal.find('form').find('input#ids-field')
|
||||||
|
$idsInput.val(JSON.stringify(idList))
|
||||||
|
const tableData = selectedData.map(row => {
|
||||||
|
return [row.id, row.first_name, row.last_name, row.email]
|
||||||
|
});
|
||||||
|
handleMessageTable(
|
||||||
|
modalObject.$modal,
|
||||||
|
['<?= __('ID') ?>', '<?= __('First name') ?>', '<?= __('Last name') ?>', '<?= __('email') ?>'],
|
||||||
|
tableData
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
function constructMessageTable(header, data) {
|
||||||
|
return HtmlHelper.table(
|
||||||
|
header,
|
||||||
|
data, {
|
||||||
|
small: true,
|
||||||
|
borderless: true,
|
||||||
|
tableClass: ['message-table', 'mt-4 mb-0'],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMessageTable($modal, header, data) {
|
||||||
|
const $modalBody = $modal.find('.modal-body')
|
||||||
|
const $messageTable = $modalBody.find('table.message-table')
|
||||||
|
const messageTableHTML = constructMessageTable(header, data)[0].outerHTML
|
||||||
|
if ($messageTable.length) {
|
||||||
|
$messageTable.html(messageTableHTML)
|
||||||
|
} else {
|
||||||
|
$modalBody.append(messageTableHTML)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
echo $this->element(
|
||||||
|
'/genericElements/SingleViews/single_view',
|
||||||
|
[
|
||||||
|
'data' => $entity,
|
||||||
|
'fields' => [
|
||||||
|
[
|
||||||
|
'key' => __('ID'),
|
||||||
|
'path' => 'id'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => __('UUID'),
|
||||||
|
'path' => 'uuid'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => __('Name'),
|
||||||
|
'path' => 'name'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => __('Owner'),
|
||||||
|
'path' => 'user_id',
|
||||||
|
'url' => '/users/view/{{0}}',
|
||||||
|
'url_vars' => 'user_id'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => __('Releasability'),
|
||||||
|
'path' => 'releasability'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => __('Description'),
|
||||||
|
'path' => 'description'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => __('Active'),
|
||||||
|
'path' => 'active',
|
||||||
|
'type' => 'boolean'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => __('Deleted'),
|
||||||
|
'path' => 'deleted',
|
||||||
|
'type' => 'boolean'
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'children' => [
|
||||||
|
[
|
||||||
|
'url' => '/mailingLists/listIndividuals/{{0}}',
|
||||||
|
'url_params' => ['id'],
|
||||||
|
'title' => __('Individuals')
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
);
|
|
@ -7,4 +7,7 @@
|
||||||
'disabled' => $fieldData['disabled'] ?? false,
|
'disabled' => $fieldData['disabled'] ?? false,
|
||||||
'class' => ($fieldData['class'] ?? '') . ' formDropdown form-select'
|
'class' => ($fieldData['class'] ?? '') . ' formDropdown form-select'
|
||||||
];
|
];
|
||||||
|
if (!empty($fieldData['label'])) {
|
||||||
|
$controlParams['label'] = $fieldData['label'];
|
||||||
|
}
|
||||||
echo $this->FormFieldMassage->prepareFormElement($this->Form, $controlParams, $fieldData);
|
echo $this->FormFieldMassage->prepareFormElement($this->Form, $controlParams, $fieldData);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
/*
|
/*
|
||||||
* Generic form builder
|
* Generic form builder
|
||||||
*
|
*
|
||||||
* Simply pass a JSON with the following keys set:
|
* Simply pass a JSON with the following keys set:
|
||||||
|
@ -26,6 +26,7 @@
|
||||||
$data['url'] = ["controller" => $this->request->getParam('controller'), "action" => $this->request->getParam('url')];
|
$data['url'] = ["controller" => $this->request->getParam('controller'), "action" => $this->request->getParam('url')];
|
||||||
}
|
}
|
||||||
$formRandomValue = Cake\Utility\Security::randomString(8);
|
$formRandomValue = Cake\Utility\Security::randomString(8);
|
||||||
|
$initSelect2 = false;
|
||||||
$formCreate = $this->Form->create($entity, ['id' => 'form-' . $formRandomValue]);
|
$formCreate = $this->Form->create($entity, ['id' => 'form-' . $formRandomValue]);
|
||||||
$default_template = [
|
$default_template = [
|
||||||
'inputContainer' => '<div class="row mb-3">{{content}}</div>',
|
'inputContainer' => '<div class="row mb-3">{{content}}</div>',
|
||||||
|
@ -52,6 +53,7 @@
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$initSelect2 = $initSelect2 || (!empty($fieldData['type']) && $fieldData['type'] == 'dropdown' && !empty($fieldData['select2']));
|
||||||
$formTemplate = $default_template;
|
$formTemplate = $default_template;
|
||||||
if (!empty($fieldData['floating-label'])) {
|
if (!empty($fieldData['floating-label'])) {
|
||||||
$formTemplate['inputContainer'] = '<div class="form-floating input {{type}}{{required}}">{{content}}</div>';
|
$formTemplate['inputContainer'] = '<div class="form-floating input {{type}}{{required}}">{{content}}</div>';
|
||||||
|
@ -66,7 +68,8 @@
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$fieldsString .= $this->element(
|
$fieldsString .= $this->element(
|
||||||
'genericElements/Form/fieldScaffold', [
|
'genericElements/Form/fieldScaffold',
|
||||||
|
[
|
||||||
'fieldData' => $fieldData,
|
'fieldData' => $fieldData,
|
||||||
'form' => $this->Form,
|
'form' => $this->Form,
|
||||||
'simpleFieldWhitelist' => $simpleFieldWhitelist
|
'simpleFieldWhitelist' => $simpleFieldWhitelist
|
||||||
|
@ -76,7 +79,8 @@
|
||||||
}
|
}
|
||||||
if (!empty($data['metaTemplates']) && $data['metaTemplates']->count() > 0) {
|
if (!empty($data['metaTemplates']) && $data['metaTemplates']->count() > 0) {
|
||||||
$metaTemplateString = $this->element(
|
$metaTemplateString = $this->element(
|
||||||
'genericElements/Form/metaTemplateScaffold', [
|
'genericElements/Form/metaTemplateScaffold',
|
||||||
|
[
|
||||||
'metaTemplatesData' => $data['metaTemplates'],
|
'metaTemplatesData' => $data['metaTemplates'],
|
||||||
'form' => $this->Form,
|
'form' => $this->Form,
|
||||||
]
|
]
|
||||||
|
@ -100,6 +104,7 @@
|
||||||
$actionName = h(\Cake\Utility\Inflector::humanize($this->request->getParam('action')));
|
$actionName = h(\Cake\Utility\Inflector::humanize($this->request->getParam('action')));
|
||||||
$modelName = h(\Cake\Utility\Inflector::humanize(\Cake\Utility\Inflector::singularize($this->request->getParam('controller'))));
|
$modelName = h(\Cake\Utility\Inflector::humanize(\Cake\Utility\Inflector::singularize($this->request->getParam('controller'))));
|
||||||
if (!empty($ajax)) {
|
if (!empty($ajax)) {
|
||||||
|
$seedModal = 'mseed-' . mt_rand();
|
||||||
echo $this->element('genericElements/genericModal', [
|
echo $this->element('genericElements/genericModal', [
|
||||||
'title' => empty($data['title']) ? sprintf('%s %s', $actionName, $modelName) : h($data['title']),
|
'title' => empty($data['title']) ? sprintf('%s %s', $actionName, $modelName) : h($data['title']),
|
||||||
'body' => sprintf(
|
'body' => sprintf(
|
||||||
|
@ -112,7 +117,8 @@
|
||||||
$formCreate,
|
$formCreate,
|
||||||
$fieldsString,
|
$fieldsString,
|
||||||
empty($metaTemplateString) ? '' : $this->element(
|
empty($metaTemplateString) ? '' : $this->element(
|
||||||
'genericElements/accordion_scaffold', [
|
'genericElements/accordion_scaffold',
|
||||||
|
[
|
||||||
'children' => [
|
'children' => [
|
||||||
[
|
[
|
||||||
'body' => $metaTemplateString,
|
'body' => $metaTemplateString,
|
||||||
|
@ -124,7 +130,7 @@
|
||||||
$formEnd
|
$formEnd
|
||||||
),
|
),
|
||||||
'actionButton' => $this->element('genericElements/Form/submitButton', $submitButtonData),
|
'actionButton' => $this->element('genericElements/Form/submitButton', $submitButtonData),
|
||||||
'class' => 'modal-lg'
|
'class' => "modal-lg {$seedModal}"
|
||||||
]);
|
]);
|
||||||
} else if (!empty($raw)) {
|
} else if (!empty($raw)) {
|
||||||
echo sprintf(
|
echo sprintf(
|
||||||
|
@ -137,7 +143,8 @@
|
||||||
$formCreate,
|
$formCreate,
|
||||||
$fieldsString,
|
$fieldsString,
|
||||||
empty($metaTemplateString) ? '' : $this->element(
|
empty($metaTemplateString) ? '' : $this->element(
|
||||||
'genericElements/accordion_scaffold', [
|
'genericElements/accordion_scaffold',
|
||||||
|
[
|
||||||
'children' => [
|
'children' => [
|
||||||
[
|
[
|
||||||
'body' => $metaTemplateString,
|
'body' => $metaTemplateString,
|
||||||
|
@ -161,7 +168,8 @@
|
||||||
),
|
),
|
||||||
sprintf('<div class="panel">%s</div>', $fieldsString),
|
sprintf('<div class="panel">%s</div>', $fieldsString),
|
||||||
empty($metaTemplateString) ? '' : $this->element(
|
empty($metaTemplateString) ? '' : $this->element(
|
||||||
'genericElements/accordion_scaffold', [
|
'genericElements/accordion_scaffold',
|
||||||
|
[
|
||||||
'children' => [
|
'children' => [
|
||||||
[
|
[
|
||||||
'body' => $metaTemplateString,
|
'body' => $metaTemplateString,
|
||||||
|
@ -184,5 +192,14 @@
|
||||||
$('.formDropdown').on('change', function() {
|
$('.formDropdown').on('change', function() {
|
||||||
executeStateDependencyChecks('#' + this.id);
|
executeStateDependencyChecks('#' + this.id);
|
||||||
})
|
})
|
||||||
|
<?php if (!empty($initSelect2)): ?>
|
||||||
|
<?php
|
||||||
|
$dropdownParent = !empty($seedModal) ? sprintf("$('.modal-dialog.%s .modal-body')", $seedModal) : "$(document.body)";
|
||||||
|
?>
|
||||||
|
$('select.select2-input').select2({
|
||||||
|
dropdownParent: <?= $dropdownParent ?>,
|
||||||
|
width: '100%',
|
||||||
|
})
|
||||||
|
<?php endif; ?>
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
|
@ -96,7 +96,7 @@ echo $this->Bootstrap->modal([
|
||||||
activeFilters[fullFilter] = rowData['value']
|
activeFilters[fullFilter] = rowData['value']
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
$select = modalObject.$modal.find('select.tag-input')
|
$select = modalObject.$modal.find('select.select2-input')
|
||||||
activeFilters['filteringTags'] = $select.select2('data').map(tag => tag.text)
|
activeFilters['filteringTags'] = $select.select2('data').map(tag => tag.text)
|
||||||
const searchParam = jQuery.param(activeFilters);
|
const searchParam = jQuery.param(activeFilters);
|
||||||
const url = `/${controller}/${action}?${searchParam}`
|
const url = `/${controller}/${action}?${searchParam}`
|
||||||
|
@ -125,7 +125,7 @@ echo $this->Bootstrap->modal([
|
||||||
}
|
}
|
||||||
setFilteringValues($filteringTable, field, value, operator)
|
setFilteringValues($filteringTable, field, value, operator)
|
||||||
}
|
}
|
||||||
$select = $filteringTable.closest('.modal-body').find('select.tag-input')
|
$select = $filteringTable.closest('.modal-body').find('select.select2-input')
|
||||||
let passedTags = []
|
let passedTags = []
|
||||||
tags.forEach(tagname => {
|
tags.forEach(tagname => {
|
||||||
const existingOption = $select.find('option').filter(function() {
|
const existingOption = $select.find('option').filter(function() {
|
||||||
|
|
Loading…
Reference in New Issue