Removed obsolete dir
Numeric conversions for all ids coming in as route req params. Infrastructure for proper error message when dependencies prevent entity deletion.
This commit is contained in:
parent
2b57396a5d
commit
0a08088893
636 changed files with 291 additions and 73346 deletions
|
@ -1,6 +1,7 @@
|
||||||
# Crowdfunding Backers
|
# Crowdfunding Backers of Mailtrain v1
|
||||||
|
|
||||||
Mailtrain received funding from a [crowdfunding campaign](https://www.indiegogo.com/at/mailtrain/8720095). This was to enable me to spend the time required to get automation support into Mailtrain. These are the people who contributed to this fund raiser.
|
Mailtrain v1 received funding from a [crowdfunding campaign](https://www.indiegogo.com/at/mailtrain/8720095).
|
||||||
|
This was to enable me to spend the time required to get automation support into Mailtrain v1. These are the people who contributed to this fund raiser.
|
||||||
|
|
||||||
* iRedMail - free, open source mail server solution <[www.iredmail.org](http://www.iredmail.org/)>
|
* iRedMail - free, open source mail server solution <[www.iredmail.org](http://www.iredmail.org/)>
|
||||||
* Richard Adleta
|
* Richard Adleta
|
||||||
|
|
3
TODO.md
3
TODO.md
|
@ -2,7 +2,6 @@
|
||||||
- Some dashboard
|
- Some dashboard
|
||||||
|
|
||||||
### Deletion
|
### Deletion
|
||||||
- Delete button in Lists
|
|
||||||
- Check/delete dependencies
|
- Check/delete dependencies
|
||||||
|
|
||||||
### Templates
|
### Templates
|
||||||
|
@ -13,7 +12,7 @@
|
||||||
|
|
||||||
### Campaigns
|
### Campaigns
|
||||||
- Statistics for a sent campaign
|
- Statistics for a sent campaign
|
||||||
- List of sent RSS campaigns
|
- List of sent RSS campaigns (?)
|
||||||
|
|
||||||
### Pull requests
|
### Pull requests
|
||||||
- Support ldaps:// - 5325f2ea7864ce5f42a9a6df3408af7ffbd32591
|
- Support ldaps:// - 5325f2ea7864ce5f42a9a6df3408af7ffbd32591
|
||||||
|
|
|
@ -407,7 +407,7 @@ export default class CUD extends Component {
|
||||||
} else if (sourceTypeKey === CampaignSource.CUSTOM || sourceTypeKey === CampaignSource.CUSTOM_FROM_TEMPLATE || sourceTypeKey === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
|
} else if (sourceTypeKey === CampaignSource.CUSTOM || sourceTypeKey === CampaignSource.CUSTOM_FROM_TEMPLATE || sourceTypeKey === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
|
||||||
this.navigateToWithFlashMessage(`/campaigns/${submitResponse}/content`, 'success', t('Campaign saved'));
|
this.navigateToWithFlashMessage(`/campaigns/${submitResponse}/content`, 'success', t('Campaign saved'));
|
||||||
} else {
|
} else {
|
||||||
this.navigateToWithFlashMessage(`/campaigns/${submitResponse}/edit`, 'success', t('Campaign saved'));
|
this.navigateToWithFlashMessage(`/campaigns/${submitResponse}/status`, 'success', t('Campaign saved'));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.enableForm();
|
this.enableForm();
|
||||||
|
@ -661,7 +661,7 @@ export default class CUD extends Component {
|
||||||
} else if (sourceTypeKey === CampaignSource.CUSTOM || sourceTypeKey === CampaignSource.CUSTOM_FROM_TEMPLATE || sourceTypeKey === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
|
} else if (sourceTypeKey === CampaignSource.CUSTOM || sourceTypeKey === CampaignSource.CUSTOM_FROM_TEMPLATE || sourceTypeKey === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
|
||||||
saveButtonLabel = t('Save and edit content');
|
saveButtonLabel = t('Save and edit content');
|
||||||
} else {
|
} else {
|
||||||
saveButtonLabel = t('Save and edit campaign');
|
saveButtonLabel = t('Save campaign and go to status');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,8 @@ import {
|
||||||
import {getUrl} from "./urls";
|
import {getUrl} from "./urls";
|
||||||
import {withPageHelpers} from "./page";
|
import {withPageHelpers} from "./page";
|
||||||
import styles from './styles.scss';
|
import styles from './styles.scss';
|
||||||
|
import interoperableErrors from '../../../shared/interoperable-errors';
|
||||||
|
import {Link} from "react-router-dom";
|
||||||
|
|
||||||
@translate()
|
@translate()
|
||||||
@withPageHelpers
|
@withPageHelpers
|
||||||
|
@ -22,13 +24,17 @@ export class RestActionModalDialog extends Component {
|
||||||
visible: PropTypes.bool.isRequired,
|
visible: PropTypes.bool.isRequired,
|
||||||
actionMethod: PropTypes.func.isRequired,
|
actionMethod: PropTypes.func.isRequired,
|
||||||
actionUrl: PropTypes.string.isRequired,
|
actionUrl: PropTypes.string.isRequired,
|
||||||
|
|
||||||
backUrl: PropTypes.string,
|
backUrl: PropTypes.string,
|
||||||
successUrl: PropTypes.string,
|
successUrl: PropTypes.string,
|
||||||
|
|
||||||
onBack: PropTypes.func,
|
onBack: PropTypes.func,
|
||||||
onSuccess: PropTypes.func,
|
|
||||||
onPerformingAction: PropTypes.func,
|
onPerformingAction: PropTypes.func,
|
||||||
|
onSuccess: PropTypes.func,
|
||||||
|
|
||||||
actionInProgressMsg: PropTypes.string.isRequired,
|
actionInProgressMsg: PropTypes.string.isRequired,
|
||||||
actionDoneMsg: PropTypes.string.isRequired,
|
actionDoneMsg: PropTypes.string.isRequired,
|
||||||
|
|
||||||
onErrorAsync: PropTypes.func
|
onErrorAsync: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,21 +96,71 @@ export class RestActionModalDialog extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@translate()
|
@translate()
|
||||||
|
@withPageHelpers
|
||||||
export class DeleteModalDialog extends Component {
|
export class DeleteModalDialog extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
const t = props.t;
|
||||||
|
|
||||||
|
this.entityTypeLabels = {
|
||||||
|
'campaign': t('Campaign'),
|
||||||
|
'template': t('Template')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
stateOwner: PropTypes.object,
|
|
||||||
visible: PropTypes.bool.isRequired,
|
visible: PropTypes.bool.isRequired,
|
||||||
|
|
||||||
|
stateOwner: PropTypes.object,
|
||||||
|
name: PropTypes.string,
|
||||||
deleteUrl: PropTypes.string.isRequired,
|
deleteUrl: PropTypes.string.isRequired,
|
||||||
|
|
||||||
backUrl: PropTypes.string,
|
backUrl: PropTypes.string,
|
||||||
successUrl: PropTypes.string,
|
successUrl: PropTypes.string,
|
||||||
name: PropTypes.string,
|
|
||||||
onBack: PropTypes.func,
|
onBack: PropTypes.func,
|
||||||
onSuccess: PropTypes.func,
|
|
||||||
onPerformingAction: PropTypes.func,
|
onPerformingAction: PropTypes.func,
|
||||||
|
onSuccess: PropTypes.func,
|
||||||
|
onFail: PropTypes.func,
|
||||||
|
|
||||||
deletingMsg: PropTypes.string.isRequired,
|
deletingMsg: PropTypes.string.isRequired,
|
||||||
deletedMsg: PropTypes.string.isRequired,
|
deletedMsg: PropTypes.string.isRequired
|
||||||
onErrorAsync: PropTypes.func
|
}
|
||||||
|
|
||||||
|
async onErrorAsync(err) {
|
||||||
|
const t = this.props.t;
|
||||||
|
|
||||||
|
if (err instanceof interoperableErrors.DependencyPresentError) {
|
||||||
|
const owner = this.props.stateOwner;
|
||||||
|
|
||||||
|
const name = this.props.name !== undefined ? this.props.name : (owner ? owner.getFormValue('name') : '');
|
||||||
|
this.setFlashMessage('danger',
|
||||||
|
<div>
|
||||||
|
<p>{t('Cannote delete "{{name}}" due to the following dependencies:', {name, nsSeparator: '|'})}</p>
|
||||||
|
<ul className={styles.dependenciesList}>
|
||||||
|
{err.data.dependencies.map(dep =>
|
||||||
|
<li key={dep.link}><Link to={dep.link}>{this.entityTypeLabels[dep.entityTypeId]}: {dep.name}</Link></li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
window.scrollTo(0, 0); // This is to scroll up because the flash message appears on top and it's quite misleading if the delete fails and the message is not in the viewport
|
||||||
|
|
||||||
|
if (this.props.onFail) {
|
||||||
|
this.props.onFail();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (owner) {
|
||||||
|
owner.enableForm();
|
||||||
|
owner.clearFormStatusMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -122,11 +178,11 @@ export class DeleteModalDialog extends Component {
|
||||||
backUrl={this.props.backUrl}
|
backUrl={this.props.backUrl}
|
||||||
successUrl={this.props.successUrl}
|
successUrl={this.props.successUrl}
|
||||||
onBack={this.props.onBack}
|
onBack={this.props.onBack}
|
||||||
onSuccess={this.props.onSuccess}
|
|
||||||
onPerformingAction={this.props.onPerformingAction}
|
onPerformingAction={this.props.onPerformingAction}
|
||||||
|
onSuccess={this.props.onSuccess}
|
||||||
actionInProgressMsg={this.props.deletingMsg}
|
actionInProgressMsg={this.props.deletingMsg}
|
||||||
actionDoneMsg={this.props.deletedMsg}
|
actionDoneMsg={this.props.deletedMsg}
|
||||||
onErrorAsync={this.props.onErrorAsync}
|
onErrorAsync={::this.onErrorAsync}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -174,6 +230,7 @@ export function tableDeleteDialogRender(owner, deleteUrlBase, deletingMsg, delet
|
||||||
onBack={hide}
|
onBack={hide}
|
||||||
onPerformingAction={() => owner.setState({ deleteDialogShown: false })}
|
onPerformingAction={() => owner.setState({ deleteDialogShown: false })}
|
||||||
onSuccess={hide}
|
onSuccess={hide}
|
||||||
|
onFail={hide}
|
||||||
deletingMsg={deletingMsg}
|
deletingMsg={deletingMsg}
|
||||||
deletedMsg={deletedMsg}
|
deletedMsg={deletedMsg}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -462,8 +462,8 @@ function requiresAuthenticatedUser(target) {
|
||||||
const comp1 = withPageHelpers(target);
|
const comp1 = withPageHelpers(target);
|
||||||
|
|
||||||
function comp2(props, context) {
|
function comp2(props, context) {
|
||||||
comp1.call(this, props, context);
|
|
||||||
context.sectionContent.ensureAuthenticated();
|
context.sectionContent.ensureAuthenticated();
|
||||||
|
comp1.call(this, props, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
comp2.prototype = comp1.prototype;
|
comp2.prototype = comp1.prototype;
|
||||||
|
|
|
@ -135,4 +135,8 @@
|
||||||
|
|
||||||
.iconDisabled {
|
.iconDisabled {
|
||||||
color: #888;
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dependenciesList {
|
||||||
|
margin-bottom: 0px;
|
||||||
}
|
}
|
|
@ -5,6 +5,7 @@ const files = require('../models/files');
|
||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const uploadedFilesDir = path.join(files.filesDir, 'uploaded');
|
const uploadedFilesDir = path.join(files.filesDir, 'uploaded');
|
||||||
|
const {castToInteger} = require('./helpers');
|
||||||
|
|
||||||
const multer = require('multer')({
|
const multer = require('multer')({
|
||||||
dest: uploadedFilesDir
|
dest: uploadedFilesDir
|
||||||
|
@ -12,7 +13,7 @@ const multer = require('multer')({
|
||||||
|
|
||||||
function installUploadHandler(router, url, replacementBehavior, type, subType) {
|
function installUploadHandler(router, url, replacementBehavior, type, subType) {
|
||||||
router.postAsync(url, passport.loggedIn, multer.array('files[]'), async (req, res) => {
|
router.postAsync(url, passport.loggedIn, multer.array('files[]'), async (req, res) => {
|
||||||
return res.json(await files.createFiles(req.context, type || req.params.type, subType || req.params.subType, req.params.entityId, req.files, replacementBehavior));
|
return res.json(await files.createFiles(req.context, type || req.params.type, subType || req.params.subType, castToInteger(req.params.entityId), req.files, replacementBehavior));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,8 @@ let _ = require('./translate')._;
|
||||||
module.exports = {
|
module.exports = {
|
||||||
enforce,
|
enforce,
|
||||||
cleanupFromPost,
|
cleanupFromPost,
|
||||||
filterObject
|
filterObject,
|
||||||
|
castToInteger
|
||||||
};
|
};
|
||||||
|
|
||||||
function enforce(condition, message) {
|
function enforce(condition, message) {
|
||||||
|
@ -27,4 +28,14 @@ function filterObject(obj, allowedKeys) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function castToInteger(id) {
|
||||||
|
const val = parseInt(id);
|
||||||
|
|
||||||
|
if (!Number.isInteger(val)) {
|
||||||
|
throw new Error('Invalid id');
|
||||||
|
}
|
||||||
|
|
||||||
|
return val;
|
||||||
}
|
}
|
|
@ -378,6 +378,13 @@ async function _createTx(tx, context, entity, content) {
|
||||||
|
|
||||||
await tx('campaign_lists').insert(entity.lists.map(x => ({campaign: id, ...x})));
|
await tx('campaign_lists').insert(entity.lists.map(x => ({campaign: id, ...x})));
|
||||||
|
|
||||||
|
if (entity.source === CampaignSource.TEMPLATE) {
|
||||||
|
await tx('template_dep_campaigns').insert({
|
||||||
|
campaign: id,
|
||||||
|
template: entity.data.sourceTemplate
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await shares.rebuildPermissionsTx(tx, { entityTypeId: 'campaign', entityId: id });
|
await shares.rebuildPermissionsTx(tx, { entityTypeId: 'campaign', entityId: id });
|
||||||
|
|
||||||
if (copyFilesFrom) {
|
if (copyFilesFrom) {
|
||||||
|
@ -436,6 +443,12 @@ async function updateWithConsistencyCheck(context, entity, content) {
|
||||||
if (content === Content.ALL || content === Content.WITHOUT_SOURCE_CUSTOM) {
|
if (content === Content.ALL || content === Content.WITHOUT_SOURCE_CUSTOM) {
|
||||||
await tx('campaign_lists').where('campaign', entity.id).del();
|
await tx('campaign_lists').where('campaign', entity.id).del();
|
||||||
await tx('campaign_lists').insert(entity.lists.map(x => ({campaign: entity.id, ...x})));
|
await tx('campaign_lists').insert(entity.lists.map(x => ({campaign: entity.id, ...x})));
|
||||||
|
|
||||||
|
if (existing.source === CampaignSource.TEMPLATE) {
|
||||||
|
await tx('template_dep_campaigns')
|
||||||
|
.where('campaign', entity.id)
|
||||||
|
.update('template', entity.data.sourceTemplate);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
filteredEntity.data = JSON.stringify(filteredEntity.data);
|
filteredEntity.data = JSON.stringify(filteredEntity.data);
|
||||||
|
@ -458,6 +471,10 @@ async function remove(context, id) {
|
||||||
|
|
||||||
await triggers.removeAllByCampaignIdTx(tx, context, id);
|
await triggers.removeAllByCampaignIdTx(tx, context, id);
|
||||||
|
|
||||||
|
await tx('template_dep_campaigns')
|
||||||
|
.where('campaign', entity.id)
|
||||||
|
.del();
|
||||||
|
|
||||||
await tx('campaigns').where('id', id).del();
|
await tx('campaigns').where('id', id).del();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -305,6 +305,19 @@ async function copyAllTx(tx, context, fromType, fromSubType, fromEntityId, toTyp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function removeAllTx(tx, context, type, subType, entityId) {
|
||||||
|
enforceTypePermitted(type, subType);
|
||||||
|
await shares.enforceEntityPermissionTx(tx, context, type, entityId, getFilesPermission(type, subType, 'manage'));
|
||||||
|
|
||||||
|
const rows = await tx(getFilesTable(type, subType)).where({entity: entityId});
|
||||||
|
for (const row of rows) {
|
||||||
|
const filePath = getFilePath(type, subType, entityId, row.filename);
|
||||||
|
await fs.removeAsync(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
await tx(getFilesTable(type, subType)).where('entity', entityId).del();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
module.exports.filesDir = filesDir;
|
module.exports.filesDir = filesDir;
|
||||||
module.exports.listDTAjax = listDTAjax;
|
module.exports.listDTAjax = listDTAjax;
|
||||||
|
@ -319,4 +332,5 @@ module.exports.removeFile = removeFile;
|
||||||
module.exports.getFileUrl = getFileUrl;
|
module.exports.getFileUrl = getFileUrl;
|
||||||
module.exports.getFilePath = getFilePath;
|
module.exports.getFilePath = getFilePath;
|
||||||
module.exports.copyAllTx = copyAllTx;
|
module.exports.copyAllTx = copyAllTx;
|
||||||
|
module.exports.removeAllTx = removeAllTx;
|
||||||
module.exports.ReplacementBehavior = ReplacementBehavior;
|
module.exports.ReplacementBehavior = ReplacementBehavior;
|
||||||
|
|
|
@ -83,14 +83,21 @@ async function updateWithConsistencyCheck(context, entity) {
|
||||||
|
|
||||||
async function remove(context, id) {
|
async function remove(context, id) {
|
||||||
await knex.transaction(async tx => {
|
await knex.transaction(async tx => {
|
||||||
const rows = await tx('templates').where('type', 'mosaico').select(['data']);
|
const deps = [];
|
||||||
for (const row of rows) {
|
const tmpls = await tx('templates').where('type', 'mosaico').select(['id', 'name', 'data']);
|
||||||
|
for (const row of tmpls) {
|
||||||
const data = JSON.parse(row.data);
|
const data = JSON.parse(row.data);
|
||||||
if (data.template === id) {
|
if (data.mosaicoTemplate === id) {
|
||||||
throw new interoperableErrors.DependencyPresentError();
|
deps.push({ entityTypeId: 'template', name: row.name, link: `templates/${row.id}` });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (deps.length > 0) {
|
||||||
|
throw new interoperableErrors.DependencyPresentError('', {
|
||||||
|
dependencies: deps
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await shares.enforceEntityPermissionTx(tx, context, 'mosaicoTemplate', id, 'delete');
|
await shares.enforceEntityPermissionTx(tx, context, 'mosaicoTemplate', id, 'delete');
|
||||||
|
|
||||||
await tx('mosaico_templates').where('id', id).del();
|
await tx('mosaico_templates').where('id', id).del();
|
||||||
|
|
|
@ -8,6 +8,7 @@ const interoperableErrors = require('../shared/interoperable-errors');
|
||||||
const namespaceHelpers = require('../lib/namespace-helpers');
|
const namespaceHelpers = require('../lib/namespace-helpers');
|
||||||
const shares = require('./shares');
|
const shares = require('./shares');
|
||||||
const reports = require('./reports');
|
const reports = require('./reports');
|
||||||
|
const files = require('./files');
|
||||||
|
|
||||||
const allowedKeys = new Set(['name', 'description', 'type', 'data', 'html', 'text', 'namespace']);
|
const allowedKeys = new Set(['name', 'description', 'type', 'data', 'html', 'text', 'namespace']);
|
||||||
|
|
||||||
|
@ -96,7 +97,19 @@ async function remove(context, id) {
|
||||||
await knex.transaction(async tx => {
|
await knex.transaction(async tx => {
|
||||||
await shares.enforceEntityPermissionTx(tx, context, 'template', id, 'delete');
|
await shares.enforceEntityPermissionTx(tx, context, 'template', id, 'delete');
|
||||||
|
|
||||||
// FIXME - deal with deletion of dependent entities (files, etc.)
|
const depCampaigns = await tx('template_dep_campaigns')
|
||||||
|
.where('template_dep_campaigns.template', id)
|
||||||
|
.innerJoin('campaigns', 'template_dep_campaigns.campaign', 'campaigns.id')
|
||||||
|
.limit(interoperableErrors.defaultNoOfDependenciesReported)
|
||||||
|
.select(['campaigns.id', 'campaigns.name']);
|
||||||
|
|
||||||
|
if (depCampaigns.length > 0) {
|
||||||
|
throw new interoperableErrors.DependencyPresentError('', {
|
||||||
|
dependencies: depCampaigns.map(row => ({ entityTypeId: 'campaign', name: row.name, link: `campaigns/${row.id}` }))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await files.removeAllTx(tx, context, 'template', 'file', id);
|
||||||
|
|
||||||
await tx('templates').where('id', id).del();
|
await tx('templates').where('id', id).del();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,117 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let config = require('config');
|
|
||||||
let mysql = require('mysql2');
|
|
||||||
let redis = require('redis');
|
|
||||||
let Lock = require('redfour');
|
|
||||||
let stringifyDate = require('json-stringify-date');
|
|
||||||
let senders = require('./senders');
|
|
||||||
|
|
||||||
const bluebird = require('bluebird');
|
|
||||||
|
|
||||||
module.exports = mysql.createPool(config.mysql);
|
|
||||||
if (config.redis && config.redis.enabled) {
|
|
||||||
|
|
||||||
module.exports.redis = redis.createClient(config.redis);
|
|
||||||
|
|
||||||
let queueLock = new Lock({
|
|
||||||
redis: config.redis,
|
|
||||||
namespace: 'mailtrain:lock'
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.getLock = bluebird.promisify((id, callback) => {
|
|
||||||
queueLock.waitAcquireLock(id, 60 * 1000 /* Lock expires after 60sec */ , 10 * 1000 /* Wait for lock for up to 10sec */ , (err, lock) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
if (!lock) {
|
|
||||||
return callback(null, false);
|
|
||||||
}
|
|
||||||
return callback(null, {
|
|
||||||
lock,
|
|
||||||
release(done) {
|
|
||||||
queueLock.releaseLock(lock, done);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.clearCache = bluebird.promisify((key, callback) => {
|
|
||||||
module.exports.redis.del('mailtrain:cache:' + key, err => callback(err));
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.addToCache = bluebird.promisify((key, value, callback) => {
|
|
||||||
if (!value) {
|
|
||||||
return setImmediate(() => callback());
|
|
||||||
}
|
|
||||||
module.exports.redis.multi().
|
|
||||||
lpush('mailtrain:cache:' + key, stringifyDate.stringify(value)).
|
|
||||||
expire('mailtrain:cache:' + key, 24 * 3600).
|
|
||||||
exec(err => callback(err));
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.getFromCache = bluebird.promisify((key, callback) => {
|
|
||||||
module.exports.redis.rpop('mailtrain:cache:' + key, (err, value) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
value = stringifyDate.parse(value);
|
|
||||||
} catch (E) {
|
|
||||||
return callback(E);
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback(null, value);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// fakelock. does not lock anything
|
|
||||||
module.exports.getLock = bluebird.promisify((id, callback) => {
|
|
||||||
setImmediate(() => callback(null, {
|
|
||||||
lock: false,
|
|
||||||
release(done) {
|
|
||||||
setImmediate(done);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
let caches = new Map();
|
|
||||||
|
|
||||||
module.exports.clearCache = bluebird.promisify((key, callback) => {
|
|
||||||
caches.delete(key);
|
|
||||||
// This notifies the callback below - i.e. process.on(...) - which is installed in the sender process.
|
|
||||||
senders.workers.forEach(child => {
|
|
||||||
child.send({
|
|
||||||
cmd: 'db.clearCache',
|
|
||||||
key
|
|
||||||
});
|
|
||||||
});
|
|
||||||
setImmediate(() => callback());
|
|
||||||
});
|
|
||||||
|
|
||||||
process.on('message', m => {
|
|
||||||
if (m && m.cmd === 'db.clearCache' && m.key) {
|
|
||||||
caches.delete(m.key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.addToCache = bluebird.promisify((key, value, callback) => {
|
|
||||||
if (!caches.has(key)) {
|
|
||||||
caches.set(key, []);
|
|
||||||
}
|
|
||||||
caches.get(key).push(value);
|
|
||||||
setImmediate(() => callback());
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.getFromCache = (key, callback) => {
|
|
||||||
let value;
|
|
||||||
if (caches.has(key)) {
|
|
||||||
value = caches.get(key).shift();
|
|
||||||
if (!caches.get(key).length) {
|
|
||||||
caches.delete(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setImmediate(() => callback(null, value));
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,81 +0,0 @@
|
||||||
// DEPRECATED
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let _ = require('../lib/translate')._;
|
|
||||||
let helpers = require('../lib/helpers');
|
|
||||||
let templates = require('../lib/models/templates');
|
|
||||||
let campaigns = require('../lib/models/campaigns');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
getResource,
|
|
||||||
getMergeTagsForResource
|
|
||||||
};
|
|
||||||
|
|
||||||
function getResource(type, id, callback) {
|
|
||||||
if (type === 'template') {
|
|
||||||
templates.get(id, (err, template) => {
|
|
||||||
if (err || !template) {
|
|
||||||
return callback(err && err.message || err || _('Could not find template with specified ID'));
|
|
||||||
}
|
|
||||||
|
|
||||||
getMergeTagsForResource(template, (err, mergeTags) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err.message || err);
|
|
||||||
}
|
|
||||||
|
|
||||||
template.mergeTags = mergeTags;
|
|
||||||
return callback(null, template);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
} else if (type === 'campaign') {
|
|
||||||
campaigns.get(id, false, (err, campaign) => {
|
|
||||||
if (err || !campaign) {
|
|
||||||
return callback(err && err.message || err || _('Could not find campaign with specified ID'));
|
|
||||||
}
|
|
||||||
|
|
||||||
getMergeTagsForResource(campaign, (err, mergeTags) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err.message || err);
|
|
||||||
}
|
|
||||||
|
|
||||||
campaign.mergeTags = mergeTags;
|
|
||||||
return callback(null, campaign);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return callback(_('Invalid resource type'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMergeTagsForResource(resource, callback) {
|
|
||||||
helpers.getDefaultMergeTags((err, defaultMergeTags) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err.message || err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Number(resource.list)) {
|
|
||||||
return callback(null, defaultMergeTags);
|
|
||||||
}
|
|
||||||
|
|
||||||
helpers.getListMergeTags(resource.list, (err, listMergeTags) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err.message || err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resource.type !== 2) {
|
|
||||||
return callback(null, defaultMergeTags.concat(listMergeTags));
|
|
||||||
}
|
|
||||||
|
|
||||||
helpers.getRSSMergeTags((err, rssMergeTags) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err.message || err);
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(null, defaultMergeTags.concat(listMergeTags, rssMergeTags));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,318 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const { nodeifyFunction } = require('./nodeify');
|
|
||||||
const getSettings = nodeifyFunction(require('../models/settings').get);
|
|
||||||
|
|
||||||
let log = require('npmlog');
|
|
||||||
let config = require('config');
|
|
||||||
let nodemailer = require('nodemailer');
|
|
||||||
let openpgpEncrypt = require('nodemailer-openpgp').openpgpEncrypt;
|
|
||||||
let tools = require('./tools');
|
|
||||||
let db = require('./db');
|
|
||||||
let Handlebars = require('handlebars');
|
|
||||||
let fs = require('fs');
|
|
||||||
let path = require('path');
|
|
||||||
let templates = new Map();
|
|
||||||
let htmlToText = require('html-to-text');
|
|
||||||
let aws = require('aws-sdk');
|
|
||||||
let objectHash = require('object-hash');
|
|
||||||
let mjml = require('mjml');
|
|
||||||
|
|
||||||
let _ = require('./translate')._;
|
|
||||||
let util = require('util');
|
|
||||||
|
|
||||||
Handlebars.registerHelper('translate', function (context, options) { // eslint-disable-line prefer-arrow-callback
|
|
||||||
if (typeof options === 'undefined' && context) {
|
|
||||||
options = context;
|
|
||||||
context = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = _(options.fn(this)); // eslint-disable-line no-invalid-this
|
|
||||||
|
|
||||||
if (Array.isArray(context)) {
|
|
||||||
result = util.format(result, ...context);
|
|
||||||
}
|
|
||||||
return new Handlebars.SafeString(result);
|
|
||||||
});
|
|
||||||
|
|
||||||
async function getTransport(sendConfiguration) {
|
|
||||||
if (!sendConfiguration) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
module.exports.transport = false;
|
|
||||||
|
|
||||||
module.exports.update = () => {
|
|
||||||
createMailer();
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.getMailer = callback => {
|
|
||||||
if (!module.exports.transport) {
|
|
||||||
return createMailer(callback);
|
|
||||||
}
|
|
||||||
callback(null, module.exports.transport);
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.sendMail = (mail, template, callback) => {
|
|
||||||
if (!callback && typeof template === 'function') {
|
|
||||||
callback = template;
|
|
||||||
template = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!module.exports.transport) {
|
|
||||||
return createMailer(err => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return module.exports.sendMail(mail, template, callback);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mail.headers) {
|
|
||||||
mail.headers = {};
|
|
||||||
}
|
|
||||||
mail.headers['X-Sending-Zone'] = 'transactional';
|
|
||||||
|
|
||||||
getTemplate(template.html, (err, htmlRenderer) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (htmlRenderer) {
|
|
||||||
mail.html = htmlRenderer(template.data || {});
|
|
||||||
}
|
|
||||||
|
|
||||||
tools.prepareHtml(mail.html, (err, prepareHtml) => {
|
|
||||||
if (err) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prepareHtml) {
|
|
||||||
mail.html = prepareHtml;
|
|
||||||
}
|
|
||||||
|
|
||||||
getTemplate(template.text, (err, textRenderer) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (textRenderer) {
|
|
||||||
mail.text = textRenderer(template.data || {});
|
|
||||||
} else if (mail.html) {
|
|
||||||
mail.text = htmlToText.fromString(mail.html, {
|
|
||||||
wordwrap: 130
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let tryCount = 0;
|
|
||||||
let trySend = () => {
|
|
||||||
tryCount++;
|
|
||||||
|
|
||||||
module.exports.transport.sendMail(mail, (err, info) => {
|
|
||||||
if (err) {
|
|
||||||
log.error('Mail', err);
|
|
||||||
if (err.responseCode && err.responseCode >= 400 && err.responseCode < 500 && tryCount <= 5) {
|
|
||||||
// temporary error, try again
|
|
||||||
log.verbose('Mail', 'Retrying after %s sec. ...', tryCount);
|
|
||||||
return setTimeout(trySend, tryCount * 1000);
|
|
||||||
}
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, info);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
setImmediate(trySend);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
function getTemplate(template, callback) {
|
|
||||||
if (!template) {
|
|
||||||
return callback(null, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let key = (typeof template === 'object') ? objectHash(template) : template;
|
|
||||||
|
|
||||||
if (templates.has(key)) {
|
|
||||||
return callback(null, templates.get(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
let done = (source, isMjml = false) => {
|
|
||||||
if (isMjml) {
|
|
||||||
let compiled;
|
|
||||||
try {
|
|
||||||
compiled = mjml.mjml2html(source);
|
|
||||||
} catch (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
if (compiled.errors.length) {
|
|
||||||
return callback(compiled.errors[0].message || compiled.errors[0]);
|
|
||||||
}
|
|
||||||
source = compiled.html;
|
|
||||||
}
|
|
||||||
let renderer = Handlebars.compile(source);
|
|
||||||
templates.set(key, renderer);
|
|
||||||
callback(null, renderer);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (typeof template === 'object') {
|
|
||||||
tools.mergeTemplateIntoLayout(template.template, template.layout, (err, source) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
let isMjml = template.type === 'mjml';
|
|
||||||
done(source, isMjml);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
fs.readFile(path.join(__dirname, '..', 'views', template), 'utf-8', (err, source) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
done(source);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMailer(callback) {
|
|
||||||
// FIXME
|
|
||||||
module.exports.transport = {
|
|
||||||
on: () => {},
|
|
||||||
isIdle: () => true,
|
|
||||||
checkThrottling: next => next()
|
|
||||||
};
|
|
||||||
return callback(null, module.exports.transport);
|
|
||||||
|
|
||||||
|
|
||||||
getSettings(['smtpHostname', 'smtpPort', 'smtpEncryption', 'smtpUser', 'smtpPass', 'smtpLog', 'smtpDisableAuth', 'smtpMaxConnections', 'smtpMaxMessages', 'smtpSelfSigned', 'pgpPrivateKey', 'pgpPassphrase', 'smtpThrottling', 'mailTransport', 'sesKey', 'sesSecret', 'sesRegion'], (err, configItems) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let oldListeners = [];
|
|
||||||
if (module.exports.transport) {
|
|
||||||
oldListeners = module.exports.transport.listeners('idle');
|
|
||||||
module.exports.transport.removeAllListeners('idle');
|
|
||||||
module.exports.transport.removeAllListeners('stream');
|
|
||||||
module.exports.transport.checkThrottling = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let sendingRate = Number(configItems.smtpThrottling) || 0;
|
|
||||||
if (sendingRate) {
|
|
||||||
// convert to messages/second
|
|
||||||
sendingRate = sendingRate / 3600;
|
|
||||||
}
|
|
||||||
|
|
||||||
let transportOptions;
|
|
||||||
|
|
||||||
let logfunc = function () {
|
|
||||||
let args = [].slice.call(arguments);
|
|
||||||
let level = args.shift();
|
|
||||||
args.shift();
|
|
||||||
args.unshift('Mail');
|
|
||||||
log[level](...args);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (configItems.mailTransport === 'smtp' || !configItems.mailTransport) {
|
|
||||||
transportOptions = {
|
|
||||||
pool: true,
|
|
||||||
host: configItems.smtpHostname,
|
|
||||||
port: Number(configItems.smtpPort) || false,
|
|
||||||
secure: configItems.smtpEncryption === 'TLS',
|
|
||||||
ignoreTLS: configItems.smtpEncryption === 'NONE',
|
|
||||||
auth: configItems.smtpDisableAuth ? false : {
|
|
||||||
user: configItems.smtpUser,
|
|
||||||
pass: configItems.smtpPass
|
|
||||||
},
|
|
||||||
debug: !!configItems.smtpLog,
|
|
||||||
logger: !configItems.smtpLog ? false : {
|
|
||||||
debug: logfunc.bind(null, 'verbose'),
|
|
||||||
info: logfunc.bind(null, 'info'),
|
|
||||||
error: logfunc.bind(null, 'error')
|
|
||||||
},
|
|
||||||
maxConnections: Number(configItems.smtpMaxConnections),
|
|
||||||
maxMessages: Number(configItems.smtpMaxMessages),
|
|
||||||
tls: {
|
|
||||||
rejectUnauthorized: !configItems.smtpSelfSigned
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else if (configItems.mailTransport === 'ses') {
|
|
||||||
transportOptions = {
|
|
||||||
SES: new aws.SES({
|
|
||||||
apiVersion: '2010-12-01',
|
|
||||||
accessKeyId: configItems.sesKey,
|
|
||||||
secretAccessKey: configItems.sesSecret,
|
|
||||||
region: configItems.sesRegion
|
|
||||||
}),
|
|
||||||
debug: !!configItems.smtpLog,
|
|
||||||
logger: !configItems.smtpLog ? false : {
|
|
||||||
debug: logfunc.bind(null, 'verbose'),
|
|
||||||
info: logfunc.bind(null, 'info'),
|
|
||||||
error: logfunc.bind(null, 'error')
|
|
||||||
},
|
|
||||||
maxConnections: Number(configItems.smtpMaxConnections),
|
|
||||||
sendingRate,
|
|
||||||
tls: {
|
|
||||||
rejectUnauthorized: !configItems.smtpSelfSigned
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return callback(new Error(_('Invalid mail transport')));
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports.transport = nodemailer.createTransport(transportOptions, config.nodemailer);
|
|
||||||
|
|
||||||
module.exports.transport.use('stream', openpgpEncrypt({
|
|
||||||
signingKey: configItems.pgpPrivateKey,
|
|
||||||
passphrase: configItems.pgpPassphrase
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (oldListeners.length) {
|
|
||||||
log.info('Mail', 'Reattaching %s idle listeners', oldListeners.length);
|
|
||||||
oldListeners.forEach(listener => module.exports.transport.on('idle', listener));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (configItems.mailTransport === 'smtp' || !configItems.mailTransport) {
|
|
||||||
|
|
||||||
let throttling = Number(configItems.smtpThrottling) || 0;
|
|
||||||
if (throttling) {
|
|
||||||
throttling = 1 / (throttling / (3600 * 1000));
|
|
||||||
}
|
|
||||||
|
|
||||||
let lastCheck = Date.now();
|
|
||||||
|
|
||||||
module.exports.transport.checkThrottling = function (next) {
|
|
||||||
if (!throttling) {
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
let nextCheck = Date.now();
|
|
||||||
let checkDiff = (nextCheck - lastCheck);
|
|
||||||
if (checkDiff < throttling) {
|
|
||||||
log.verbose('Mail', 'Throttling next message in %s sec.', (throttling - checkDiff) / 1000);
|
|
||||||
setTimeout(() => {
|
|
||||||
lastCheck = Date.now();
|
|
||||||
next();
|
|
||||||
}, throttling - checkDiff);
|
|
||||||
} else {
|
|
||||||
lastCheck = nextCheck;
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
module.exports.transport.checkThrottling = next => next();
|
|
||||||
}
|
|
||||||
|
|
||||||
db.clearCache('sender', () => {
|
|
||||||
callback(null, module.exports.transport);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports.getTemplate = getTemplate;
|
|
|
@ -1,86 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let db = require('../db');
|
|
||||||
|
|
||||||
module.exports.get = (start, limit, search, callback) => {
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
search = '%' + search + '%';
|
|
||||||
connection.query('SELECT SQL_CALC_FOUND_ROWS `email` FROM blacklist WHERE `email` LIKE ? ORDER BY `email` LIMIT ? OFFSET ?', [search, limit, start], (err, rows) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('SELECT FOUND_ROWS() AS total', (err, total) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
let emails = [];
|
|
||||||
rows.forEach(email => {
|
|
||||||
emails.push(email.email);
|
|
||||||
});
|
|
||||||
return callback(null, emails, total && total[0] && total[0].total);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.add = (email, callback) => {
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('INSERT IGNORE INTO `blacklist` (`email`) VALUES(?)', email, err => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.release();
|
|
||||||
return callback(null, null);
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.delete = (email, callback) => {
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('DELETE FROM `blacklist` WHERE `email`=?', email, err => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.release();
|
|
||||||
return callback(null, null);
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.isblacklisted = (email, callback) => {
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('SELECT `email` FROM blacklist WHERE `email`=?', email, (err, rows) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.release();
|
|
||||||
if (rows.length > 0) {
|
|
||||||
return callback(null, true);
|
|
||||||
} else {
|
|
||||||
return callback(null, false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,91 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let db = require('../db');
|
|
||||||
let shortid = require('shortid');
|
|
||||||
let helpers = require('../helpers');
|
|
||||||
let _ = require('../translate')._;
|
|
||||||
|
|
||||||
/*
|
|
||||||
Adds new entry to the confirmations tables. Generates confirmation cid, which it returns.
|
|
||||||
*/
|
|
||||||
module.exports.addConfirmation = (listId, action, ip, data, callback) => {
|
|
||||||
let cid = shortid.generate();
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = 'INSERT INTO confirmations (cid, list, action, ip, data) VALUES (?,?,?,?,?)';
|
|
||||||
connection.query(query, [cid, listId, action, ip, JSON.stringify(data || {})], (err, result) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result || !result.affectedRows) {
|
|
||||||
return callback(new Error(_('Could not store confirmation data')));
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback(null, cid);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Atomically retrieves confirmation from the database, removes it from the database and returns it.
|
|
||||||
*/
|
|
||||||
module.exports.takeConfirmation = (cid, callback) => {
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.beginTransaction(err => {
|
|
||||||
if (err) {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = 'SELECT cid, list, action, ip, data FROM confirmations WHERE cid=? LIMIT 1';
|
|
||||||
connection.query(query, [cid], (err, rows) => {
|
|
||||||
if (err) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rows || !rows.length) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(null, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('DELETE FROM confirmations WHERE `cid`=? LIMIT 1', [cid], () => {
|
|
||||||
if (err) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.commit(err => {
|
|
||||||
if (err) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
|
|
||||||
}
|
|
||||||
connection.release();
|
|
||||||
|
|
||||||
let data;
|
|
||||||
try {
|
|
||||||
data = JSON.parse(rows[0].data);
|
|
||||||
} catch (E) {
|
|
||||||
data = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = {
|
|
||||||
listId: rows[0].list,
|
|
||||||
action: rows[0].action,
|
|
||||||
ip: rows[0].ip,
|
|
||||||
data
|
|
||||||
};
|
|
||||||
|
|
||||||
return callback(null, result);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,617 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let db = require('../db');
|
|
||||||
let tools = require('../tools');
|
|
||||||
let slugify = require('slugify');
|
|
||||||
let lists = require('./lists');
|
|
||||||
let shortid = require('shortid');
|
|
||||||
let Handlebars = require('handlebars');
|
|
||||||
let _ = require('../translate')._;
|
|
||||||
let util = require('util');
|
|
||||||
|
|
||||||
let allowedKeys = ['name', 'key', 'default_value', 'group', 'group_template', 'visible'];
|
|
||||||
let allowedTypes;
|
|
||||||
|
|
||||||
module.exports.grouped = ['radio', 'checkbox', 'dropdown'];
|
|
||||||
module.exports.types = {
|
|
||||||
text: _('Text'),
|
|
||||||
website: _('Website'),
|
|
||||||
longtext: _('Multi-line text'),
|
|
||||||
gpg: _('GPG Public Key'),
|
|
||||||
number: _('Number'),
|
|
||||||
radio: _('Radio Buttons'),
|
|
||||||
checkbox: _('Checkboxes'),
|
|
||||||
dropdown: _('Drop Down'),
|
|
||||||
'date-us': _('Date (MM/DD/YYY)'),
|
|
||||||
'date-eur': _('Date (DD/MM/YYYY)'),
|
|
||||||
'birthday-us': _('Birthday (MM/DD)'),
|
|
||||||
'birthday-eur': _('Birthday (DD/MM)'),
|
|
||||||
json: _('JSON value for custom rendering'),
|
|
||||||
option: _('Option')
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.allowedTypes = allowedTypes = Object.keys(module.exports.types);
|
|
||||||
|
|
||||||
module.exports.genericTypes = {
|
|
||||||
text: 'string',
|
|
||||||
website: 'string',
|
|
||||||
longtext: 'textarea',
|
|
||||||
gpg: 'textarea',
|
|
||||||
json: 'textarea',
|
|
||||||
number: 'number',
|
|
||||||
'date-us': 'date',
|
|
||||||
'date-eur': 'date',
|
|
||||||
'birthday-us': 'birthday',
|
|
||||||
'birthday-eur': 'birthday',
|
|
||||||
option: 'boolean'
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.list = (listId, callback) => {
|
|
||||||
listId = Number(listId) || 0;
|
|
||||||
|
|
||||||
if (listId < 1) {
|
|
||||||
return callback(new Error(_('Missing List ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = 'SELECT * FROM custom_fields WHERE list=? ORDER BY id';
|
|
||||||
connection.query(query, [listId], (err, rows) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let fieldList = rows && rows.map(row => tools.convertKeys(row)) || [];
|
|
||||||
let groups = new Map();
|
|
||||||
|
|
||||||
// remove grouped rows
|
|
||||||
for (let i = fieldList.length - 1; i >= 0; i--) {
|
|
||||||
let field = fieldList[i];
|
|
||||||
if (module.exports.grouped.indexOf(field.type) >= 0) {
|
|
||||||
if (!groups.has(field.id)) {
|
|
||||||
groups.set(field.id, []);
|
|
||||||
}
|
|
||||||
field.options = groups.get(field.id);
|
|
||||||
} else if (field.group && field.type === 'option') {
|
|
||||||
if (!groups.has(field.group)) {
|
|
||||||
groups.set(field.group, [field]);
|
|
||||||
} else {
|
|
||||||
groups.get(field.group).unshift(field);
|
|
||||||
}
|
|
||||||
fieldList.splice(i, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback(null, fieldList);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.get = (id, callback) => {
|
|
||||||
id = Number(id) || 0;
|
|
||||||
|
|
||||||
if (id < 1) {
|
|
||||||
return callback(new Error(_('Missing List ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = 'SELECT * FROM custom_fields WHERE id=? LIMIT 1';
|
|
||||||
connection.query(query, [id], (err, rows) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
let field = rows && rows[0] && tools.convertKeys(rows[0]) || false;
|
|
||||||
field.isGroup = module.exports.grouped.indexOf(field.type) >= 0 || field.type === 'json';
|
|
||||||
return callback(null, field);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.create = (listId, field, callback) => {
|
|
||||||
listId = Number(listId) || 0;
|
|
||||||
|
|
||||||
if (listId < 1) {
|
|
||||||
return callback(new Error(_('Missing List ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
field = tools.convertKeys(field);
|
|
||||||
|
|
||||||
if (field.type === 'option' && !field.group) {
|
|
||||||
return callback(new Error(_('Option field requires a group to be selected')));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (field.type !== 'option') {
|
|
||||||
field.group = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
field.defaultValue = (field.defaultValue || '').toString().trim() || null;
|
|
||||||
field.groupTemplate = (field.groupTemplate || '').toString().trim() || null;
|
|
||||||
|
|
||||||
addCustomField(listId, field.name, field.defaultValue, field.type, field.group, field.groupTemplate, field.visible, callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.update = (id, updates, callback) => {
|
|
||||||
updates = updates || {};
|
|
||||||
id = Number(id) || 0;
|
|
||||||
|
|
||||||
updates = tools.convertKeys(updates);
|
|
||||||
|
|
||||||
if (id < 1) {
|
|
||||||
return callback(new Error(_('Missing Field ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(updates.name || '').toString().trim()) {
|
|
||||||
return callback(new Error(_('Field Name must be set')));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updates.key) {
|
|
||||||
updates.key = slugify(updates.key, '_').toUpperCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
updates.defaultValue = (updates.defaultValue || '').toString().trim() || null;
|
|
||||||
updates.groupTemplate = (updates.groupTemplate || '').toString().trim() || null;
|
|
||||||
|
|
||||||
updates.visible = updates.visible ? 1 : 0;
|
|
||||||
|
|
||||||
let name = (updates.name || '').toString().trim();
|
|
||||||
let keys = ['name'];
|
|
||||||
let values = [name];
|
|
||||||
|
|
||||||
Object.keys(updates).forEach(key => {
|
|
||||||
let value = typeof updates[key] === 'string' ? updates[key].trim() : updates[key];
|
|
||||||
key = tools.toDbKey(key);
|
|
||||||
if (allowedKeys.indexOf(key) >= 0) {
|
|
||||||
keys.push(key);
|
|
||||||
values.push(value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
values.push(id);
|
|
||||||
|
|
||||||
connection.query('UPDATE custom_fields SET ' + keys.map(key => '`' + key + '`=?').join(', ') + ' WHERE id=? LIMIT 1', values, (err, result) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, result && result.affectedRows || false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.delete = (fieldId, callback) => {
|
|
||||||
fieldId = Number(fieldId) || 0;
|
|
||||||
|
|
||||||
if (fieldId < 1) {
|
|
||||||
return callback(new Error(_('Missing Field ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = 'SELECT * FROM custom_fields WHERE id=? LIMIT 1';
|
|
||||||
connection.query(query, [fieldId], (err, rows) => {
|
|
||||||
if (err) {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rows || !rows.length) {
|
|
||||||
connection.release();
|
|
||||||
return callback(new Error(_('Custom field not found')));
|
|
||||||
}
|
|
||||||
|
|
||||||
let field = tools.convertKeys(rows[0]);
|
|
||||||
if (field.column) {
|
|
||||||
connection.query('ALTER TABLE `subscription__' + field.list + '` DROP COLUMN `' + field.column + '`', err => {
|
|
||||||
if (err && err.code !== 'ER_CANT_DROP_FIELD_OR_KEY') {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
connection.query('DELETE FROM custom_fields WHERE id=? LIMIT 1', [fieldId], err => {
|
|
||||||
if (err) {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
connection.query('DELETE FROM segment_rules WHERE column=? LIMIT 1', [field.column], err => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
return callback(null, true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// delete all subfields in this group
|
|
||||||
let query = 'SELECT id FROM custom_fields WHERE `group`=?';
|
|
||||||
connection.query(query, [fieldId], (err, rows) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rows || !rows.length) {
|
|
||||||
rows = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
let pos = 0;
|
|
||||||
let deleteNext = () => {
|
|
||||||
if (pos >= rows.length) {
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
connection.query('DELETE FROM custom_fields WHERE id=? LIMIT 1', [fieldId], err => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
module.exports.delete(rows[pos++].id, deleteNext);
|
|
||||||
};
|
|
||||||
|
|
||||||
deleteNext();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function addCustomField(listId, name, defaultValue, type, group, groupTemplate, visible, callback) {
|
|
||||||
type = (type || '').toString().trim().toLowerCase();
|
|
||||||
group = Number(group) || null;
|
|
||||||
listId = Number(listId) || 0;
|
|
||||||
|
|
||||||
let column = null;
|
|
||||||
let key = slugify('merge ' + name, '_').toUpperCase();
|
|
||||||
|
|
||||||
if (allowedTypes.indexOf(type) < 0) {
|
|
||||||
return callback(new Error(util.format(_('Unknown column type %s'), type)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!name) {
|
|
||||||
return callback(new Error(_('Missing column name')));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (listId <= 0) {
|
|
||||||
return callback(new Error(_('Missing list ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
lists.get(listId, (err, list) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
if (!list) {
|
|
||||||
return callback(_('Provided List ID not found'));
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (module.exports.grouped.indexOf(type) < 0) {
|
|
||||||
column = ('custom_' + slugify(name, '_') + '_' + shortid.generate()).toLowerCase().replace(/[^a-z0-9_]/g, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = 'INSERT INTO custom_fields (`list`, `name`, `key`,`default_value`, `type`, `group`, `group_template`, `column`, `visible`) VALUES(?,?,?,?,?,?,?,?,?)';
|
|
||||||
connection.query(query, [listId, name, key, defaultValue, type, group, groupTemplate, column, visible ? 1 : 0], (err, result) => {
|
|
||||||
|
|
||||||
if (err) {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let fieldId = result && result.insertId;
|
|
||||||
let indexQuery;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case 'text':
|
|
||||||
case 'website':
|
|
||||||
query = 'ALTER TABLE `subscription__' + listId + '` ADD COLUMN `' + column + '` VARCHAR(255) DEFAULT NULL';
|
|
||||||
indexQuery = 'CREATE INDEX ' + column + '_index ON `subscription__' + listId + '` (`column`);';
|
|
||||||
break;
|
|
||||||
case 'gpg':
|
|
||||||
case 'longtext':
|
|
||||||
case 'json':
|
|
||||||
query = 'ALTER TABLE `subscription__' + listId + '` ADD COLUMN `' + column + '` TEXT DEFAULT NULL';
|
|
||||||
break;
|
|
||||||
case 'number':
|
|
||||||
query = 'ALTER TABLE `subscription__' + listId + '` ADD COLUMN `' + column + '` INT(11) DEFAULT NULL';
|
|
||||||
indexQuery = 'CREATE INDEX ' + column + '_index ON `subscription__' + listId + '` (`column`);';
|
|
||||||
break;
|
|
||||||
case 'option':
|
|
||||||
query = 'ALTER TABLE `subscription__' + listId + '` ADD COLUMN `' + column + '` TINYINT(4) UNSIGNED NOT NULL DEFAULT \'0\'';
|
|
||||||
indexQuery = 'CREATE INDEX ' + column + '_index ON `subscription__' + listId + '` (`column`);';
|
|
||||||
break;
|
|
||||||
case 'date-us':
|
|
||||||
case 'date-eur':
|
|
||||||
case 'birthday-us':
|
|
||||||
case 'birthday-eur':
|
|
||||||
query = 'ALTER TABLE `subscription__' + listId + '` ADD COLUMN `' + column + '` DATETIME NULL DEFAULT NULL';
|
|
||||||
indexQuery = 'CREATE INDEX ' + column + '_index ON `subscription__' + listId + '` (`column`);';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
connection.release();
|
|
||||||
return callback(null, fieldId, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query(query, err => {
|
|
||||||
if (err) {
|
|
||||||
connection.query('DELETE FROM custom_fields WHERE id=? LIMIT 1', [fieldId], () => connection.release());
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
if (!indexQuery) {
|
|
||||||
connection.release();
|
|
||||||
return callback(null, fieldId, key);
|
|
||||||
} else {
|
|
||||||
connection.query(query, err => {
|
|
||||||
if (err) {
|
|
||||||
// ignore index errors
|
|
||||||
}
|
|
||||||
connection.release();
|
|
||||||
return callback(null, fieldId, key);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports.getRow = (fieldList, values, useDate, showAll, onlyExisting) => {
|
|
||||||
let valueList = {};
|
|
||||||
let row = [];
|
|
||||||
|
|
||||||
Object.keys(values || {}).forEach(key => {
|
|
||||||
let value = values[key];
|
|
||||||
key = tools.toDbKey(key);
|
|
||||||
if (key.indexOf('custom_') === 0) {
|
|
||||||
valueList[key] = value;
|
|
||||||
} else if (key.indexOf('group_g') === 0 && value.indexOf('custom_') === 0) {
|
|
||||||
valueList[tools.toDbKey(value)] = 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fieldList.filter(field => showAll || field.visible).forEach(field => {
|
|
||||||
if (onlyExisting && field.column && !valueList.hasOwnProperty(field.column)) {
|
|
||||||
// ignore missing values
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/* eslint-disable indent */
|
|
||||||
switch (field.type) {
|
|
||||||
case 'text':
|
|
||||||
case 'website':
|
|
||||||
case 'gpg':
|
|
||||||
case 'longtext':
|
|
||||||
{
|
|
||||||
let item = {
|
|
||||||
id: field.id,
|
|
||||||
type: field.type,
|
|
||||||
name: field.name,
|
|
||||||
column: field.column,
|
|
||||||
value: (valueList[field.column] || '').toString().trim(),
|
|
||||||
visible: !!field.visible,
|
|
||||||
mergeTag: field.key,
|
|
||||||
mergeValue: (valueList[field.column] || '').toString().trim() || field.defaultValue,
|
|
||||||
['type' + (field.type || '').toString().trim().replace(/(?:^|-)([a-z])/g, (m, c) => c.toUpperCase())]: true
|
|
||||||
};
|
|
||||||
row.push(item);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'json':
|
|
||||||
{
|
|
||||||
let value;
|
|
||||||
let json = (valueList[field.column] || '').toString().trim();
|
|
||||||
try {
|
|
||||||
let parsed = JSON.parse(json);
|
|
||||||
if (Array.isArray(parsed)) {
|
|
||||||
parsed = {
|
|
||||||
values: parsed
|
|
||||||
};
|
|
||||||
}
|
|
||||||
value = json ? render(field.groupTemplate, parsed) : '';
|
|
||||||
} catch (E) {
|
|
||||||
value = E.message;
|
|
||||||
}
|
|
||||||
|
|
||||||
let item = {
|
|
||||||
id: field.id,
|
|
||||||
type: field.type,
|
|
||||||
name: field.name,
|
|
||||||
column: field.column,
|
|
||||||
value: (valueList[field.column] || '').toString().trim(),
|
|
||||||
visible: !!field.visible,
|
|
||||||
mergeTag: field.key,
|
|
||||||
mergeValue: value || field.defaultValue,
|
|
||||||
['type' + (field.type || '').toString().trim().replace(/(?:^|-)([a-z])/g, (m, c) => c.toUpperCase())]: true
|
|
||||||
};
|
|
||||||
row.push(item);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'number':
|
|
||||||
{
|
|
||||||
let item = {
|
|
||||||
id: field.id,
|
|
||||||
type: field.type,
|
|
||||||
name: field.name,
|
|
||||||
column: field.column,
|
|
||||||
value: Number(valueList[field.column]) || 0,
|
|
||||||
visible: !!field.visible,
|
|
||||||
mergeTag: field.key,
|
|
||||||
mergeValue: (Number(valueList[field.column]) || Number(field.defaultValue) || 0).toString(),
|
|
||||||
['type' + (field.type || '').toString().trim().replace(/(?:^|-)([a-z])/g, (m, c) => c.toUpperCase())]: true
|
|
||||||
};
|
|
||||||
row.push(item);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'dropdown':
|
|
||||||
case 'radio':
|
|
||||||
case 'checkbox':
|
|
||||||
{
|
|
||||||
let hasSelectedOption = (field.options || []).some(subField => subField.column && valueList[subField.column]);
|
|
||||||
let item = {
|
|
||||||
id: field.id,
|
|
||||||
type: field.type,
|
|
||||||
name: field.name,
|
|
||||||
visible: !!field.visible,
|
|
||||||
key: 'group-g' + field.id,
|
|
||||||
mergeTag: field.key,
|
|
||||||
mergeValue: field.defaultValue,
|
|
||||||
['type' + (field.type || '').toString().trim().replace(/(?:^|-)([a-z])/g, (m, c) => c.toUpperCase())]: true,
|
|
||||||
groupTemplate: field.groupTemplate,
|
|
||||||
options: (field.options || []).map(subField => {
|
|
||||||
if (onlyExisting && subField.column && !valueList.hasOwnProperty(subField.column)) {
|
|
||||||
if (hasSelectedOption && field.type !== 'checkbox') {
|
|
||||||
// Set all radio or dropdown options if a selection for the group is present
|
|
||||||
} else if (field.type === 'checkbox' && values['originGroupG' + field.id] === 'webform') {
|
|
||||||
// Set all checkbox options if origin is webform (subscribe, manage, or admin edit) #333
|
|
||||||
// Atomic updates via API call or CSV import still possible
|
|
||||||
} else {
|
|
||||||
// ignore missing values
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
type: subField.type,
|
|
||||||
name: subField.name,
|
|
||||||
column: subField.column,
|
|
||||||
value: valueList[subField.column] ? 1 : 0,
|
|
||||||
visible: !!subField.visible,
|
|
||||||
mergeTag: subField.key,
|
|
||||||
mergeValue: valueList[subField.column] ? subField.name : subField.defaultValue
|
|
||||||
};
|
|
||||||
}).filter(subField => subField)
|
|
||||||
};
|
|
||||||
let subItems = item.options.filter(subField => (showAll || subField.visible) && subField.value).map(subField => subField.name);
|
|
||||||
item.value = field.groupTemplate ? render(field.groupTemplate, {
|
|
||||||
values: subItems
|
|
||||||
}) : subItems.join(', ');
|
|
||||||
item.mergeValue = item.value || field.defaultValue;
|
|
||||||
row.push(item);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'date-eur':
|
|
||||||
case 'birthday-eur':
|
|
||||||
case 'date-us':
|
|
||||||
case 'birthday-us':
|
|
||||||
{
|
|
||||||
let isUs = /-us$/.test(field.type);
|
|
||||||
let isYear = field.type.indexOf('date-') === 0;
|
|
||||||
let value = valueList[field.column];
|
|
||||||
let day, month, year;
|
|
||||||
let formatted;
|
|
||||||
|
|
||||||
if (value && typeof value.getUTCFullYear === 'function') {
|
|
||||||
day = value.getUTCDate();
|
|
||||||
month = value.getUTCMonth() + 1;
|
|
||||||
year = value.getUTCFullYear();
|
|
||||||
} else {
|
|
||||||
value = (value || '').toString().trim();
|
|
||||||
|
|
||||||
// try international format first YYYY-MM-DD
|
|
||||||
let parts = value.match(/(\d{4})\D+(\d{2})(?:\D+(\d{2})\b)?/);
|
|
||||||
if (parts) {
|
|
||||||
year = Number(parts[1]) || 2000;
|
|
||||||
month = Number(parts[2]) || 0;
|
|
||||||
day = Number(parts[3]) || 0;
|
|
||||||
value = new Date(Date.UTC(year, month - 1, day));
|
|
||||||
} else {
|
|
||||||
parts = value.match(/(\d+)\D+(\d+)(?:\D+(\d+)\b)?/);
|
|
||||||
if (!parts) {
|
|
||||||
value = null;
|
|
||||||
} else {
|
|
||||||
day = Number(parts[isUs ? 2 : 1]) || 0;
|
|
||||||
month = Number(parts[isUs ? 1 : 2]) || 0;
|
|
||||||
year = Number(parts[3]) || 2000;
|
|
||||||
|
|
||||||
if (!day || !month) {
|
|
||||||
value = null;
|
|
||||||
} else {
|
|
||||||
value = new Date(Date.UTC(year, month - 1, day));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (day && month) {
|
|
||||||
if (isUs) {
|
|
||||||
formatted = (month < 10 ? '0' : '') + month + '/' + (day < 10 ? '0' : '') + day;
|
|
||||||
} else {
|
|
||||||
formatted = (day < 10 ? '0' : '') + day + '/' + (month < 10 ? '0' : '') + month;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isYear) {
|
|
||||||
formatted += '/' + year;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
formatted = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let item = {
|
|
||||||
id: field.id,
|
|
||||||
type: field.type,
|
|
||||||
name: field.name,
|
|
||||||
column: field.column,
|
|
||||||
value: useDate ? value : formatted,
|
|
||||||
visible: !!field.visible,
|
|
||||||
mergeTag: field.key,
|
|
||||||
mergeValue: (useDate ? value : formatted) || field.defaultValue,
|
|
||||||
['type' + (field.type || '').toString().trim().replace(/(?:^|-)([a-z])/g, (m, c) => c.toUpperCase())]: true
|
|
||||||
};
|
|
||||||
row.push(item);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* eslint-enable indent */
|
|
||||||
});
|
|
||||||
|
|
||||||
return row;
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.getValues = (row, showAll) => {
|
|
||||||
let result = [];
|
|
||||||
row.filter(field => showAll || field.visible).forEach(field => {
|
|
||||||
if (field.column) {
|
|
||||||
result.push({
|
|
||||||
key: field.column,
|
|
||||||
value: field.value
|
|
||||||
});
|
|
||||||
} else if (field.options) {
|
|
||||||
field.options.filter(field => showAll || field.visible).forEach(subField => {
|
|
||||||
result.push({
|
|
||||||
key: subField.column,
|
|
||||||
value: subField.value
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
function render(template, options) {
|
|
||||||
let renderer = Handlebars.compile(template);
|
|
||||||
return renderer(options);
|
|
||||||
}
|
|
|
@ -1,418 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let db = require('../db');
|
|
||||||
let fs = require('fs');
|
|
||||||
let path = require('path');
|
|
||||||
let tools = require('../tools');
|
|
||||||
let mjml = require('mjml');
|
|
||||||
let _ = require('../translate')._;
|
|
||||||
|
|
||||||
let allowedKeys = [
|
|
||||||
'name',
|
|
||||||
'description',
|
|
||||||
'fields_shown_on_subscribe',
|
|
||||||
'fields_shown_on_manage',
|
|
||||||
'layout',
|
|
||||||
'form_input_style',
|
|
||||||
'web_subscribe',
|
|
||||||
'web_confirm_subscription_notice',
|
|
||||||
'mail_confirm_subscription_html',
|
|
||||||
'mail_confirm_subscription_text',
|
|
||||||
'mail_already_subscribed_html',
|
|
||||||
'mail_already_subscribed_text',
|
|
||||||
'web_subscribed_notice',
|
|
||||||
'mail_subscription_confirmed_html',
|
|
||||||
'mail_subscription_confirmed_text',
|
|
||||||
'web_manage',
|
|
||||||
'web_manage_address',
|
|
||||||
'web_updated_notice',
|
|
||||||
'web_unsubscribe',
|
|
||||||
'web_confirm_unsubscription_notice',
|
|
||||||
'mail_confirm_unsubscription_html',
|
|
||||||
'mail_confirm_unsubscription_text',
|
|
||||||
'mail_confirm_address_change_html',
|
|
||||||
'mail_confirm_address_change_text',
|
|
||||||
'web_unsubscribed_notice',
|
|
||||||
'mail_unsubscription_confirmed_html',
|
|
||||||
'mail_unsubscription_confirmed_text',
|
|
||||||
'web_manual_unsubscribe_notice'
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
module.exports.list = (listId, callback) => {
|
|
||||||
listId = Number(listId) || 0;
|
|
||||||
|
|
||||||
if (listId < 1) {
|
|
||||||
return callback(new Error(_('Missing List ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('SELECT * FROM custom_forms WHERE list=? ORDER BY id', [listId], (err, rows) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let formList = rows && rows.map(row => tools.convertKeys(row)) || [];
|
|
||||||
return callback(null, formList);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.get = (id, callback) => {
|
|
||||||
id = Number(id) || 0;
|
|
||||||
|
|
||||||
if (id < 1) {
|
|
||||||
return callback(new Error(_('Missing Form ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('SELECT * FROM custom_forms WHERE id=? LIMIT 1', [id], (err, rows) => {
|
|
||||||
if (err) {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let form = rows && rows[0] && tools.convertKeys(rows[0]) || false;
|
|
||||||
|
|
||||||
if (!form) {
|
|
||||||
connection.release();
|
|
||||||
return callback(new Error('Selected form not found'));
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('SELECT * FROM custom_forms_data WHERE form=?', [id], (err, data_rows = []) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
data_rows.forEach(data_row => {
|
|
||||||
let modelKey = tools.fromDbKey(data_row.data_key);
|
|
||||||
form[modelKey] = data_row.data_value;
|
|
||||||
});
|
|
||||||
|
|
||||||
return callback(null, form);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
module.exports.create = (listId, form, callback) => {
|
|
||||||
listId = Number(listId) || 0;
|
|
||||||
|
|
||||||
if (listId < 1) {
|
|
||||||
return callback(new Error(_('Missing Form ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
form = tools.convertKeys(form);
|
|
||||||
form = setDefaultValues(form);
|
|
||||||
form.name = (form.name || '').toString().trim();
|
|
||||||
|
|
||||||
if (!form.name) {
|
|
||||||
return callback(new Error(_('Form Name must be set')));
|
|
||||||
}
|
|
||||||
|
|
||||||
let keys = ['list'];
|
|
||||||
let values = [listId];
|
|
||||||
|
|
||||||
Object.keys(form).forEach(key => {
|
|
||||||
let value = form[key].trim();
|
|
||||||
key = tools.toDbKey(key);
|
|
||||||
if (key === 'description') {
|
|
||||||
value = tools.purifyHTML(value);
|
|
||||||
}
|
|
||||||
if (allowedKeys.indexOf(key) >= 0) {
|
|
||||||
keys.push(key);
|
|
||||||
values.push(value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let filtered = filterKeysAndValues(keys, values, 'exclude', ['mail_', 'web_']);
|
|
||||||
let query = 'INSERT INTO custom_forms (' + filtered.keys.join(', ') + ') VALUES (' + filtered.values.map(() => '?').join(',') + ')';
|
|
||||||
|
|
||||||
connection.query(query, filtered.values, (err, result) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let formId = result && result.insertId;
|
|
||||||
|
|
||||||
if (!formId) {
|
|
||||||
return callback(new Error('Invalid custom_forms insertId'));
|
|
||||||
}
|
|
||||||
|
|
||||||
let jobs = 1;
|
|
||||||
let error = null;
|
|
||||||
|
|
||||||
let done = err => {
|
|
||||||
jobs--;
|
|
||||||
error = err ? err : error; // One's enough
|
|
||||||
jobs === 0 && callback(error, formId);
|
|
||||||
};
|
|
||||||
|
|
||||||
filtered = filterKeysAndValues(keys, values, 'include', ['mail_', 'web_']);
|
|
||||||
|
|
||||||
filtered.keys.forEach((key, index) => {
|
|
||||||
jobs++;
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('INSERT INTO custom_forms_data (form, data_key, data_value) VALUES (?, ?, ?)', [formId, key, filtered.values[index]], err => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
return done(null);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
done(null);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.update = (id, updates, callback) => {
|
|
||||||
updates = updates || {};
|
|
||||||
id = Number(id) || 0;
|
|
||||||
|
|
||||||
updates = tools.convertKeys(updates);
|
|
||||||
|
|
||||||
if (id < 1) {
|
|
||||||
return callback(new Error(_('Missing Form ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(updates.name || '').toString().trim()) {
|
|
||||||
return callback(new Error(_('Form Name must be set')));
|
|
||||||
}
|
|
||||||
|
|
||||||
let keys = [];
|
|
||||||
let values = [];
|
|
||||||
|
|
||||||
Object.keys(updates).forEach(key => {
|
|
||||||
let value = typeof updates[key] === 'string' ? updates[key].trim() : updates[key];
|
|
||||||
key = tools.toDbKey(key);
|
|
||||||
if (key === 'description') {
|
|
||||||
value = tools.purifyHTML(value);
|
|
||||||
}
|
|
||||||
if (allowedKeys.indexOf(key) >= 0) {
|
|
||||||
keys.push(key);
|
|
||||||
values.push(value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let filtered = filterKeysAndValues(keys, values, 'exclude', ['mail_', 'web_']);
|
|
||||||
let query = 'UPDATE custom_forms SET ' + filtered.keys.map(key => '`' + key + '`=?').join(', ') + ' WHERE id=? LIMIT 1';
|
|
||||||
|
|
||||||
connection.query(query, filtered.values.concat(id), (err, result) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let affectedRows = result && result.affectedRows;
|
|
||||||
|
|
||||||
let jobs = 1;
|
|
||||||
let error = null;
|
|
||||||
|
|
||||||
let done = err => {
|
|
||||||
jobs--;
|
|
||||||
error = err ? err : error; // One's enough
|
|
||||||
|
|
||||||
if (jobs === 0) {
|
|
||||||
if (error) {
|
|
||||||
return callback(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save then validate, as otherwise their work get's lost ...
|
|
||||||
err = testForMjmlErrors(keys, values);
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback(null, affectedRows);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
filtered = filterKeysAndValues(keys, values, 'include', ['mail_', 'web_']);
|
|
||||||
|
|
||||||
filtered.keys.forEach((key, index) => {
|
|
||||||
jobs++;
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('UPDATE custom_forms_data SET data_value=? WHERE data_key=? AND form=?', [filtered.values[index], key, id], err => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
return done(null);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
done(null);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.delete = (formId, callback) => {
|
|
||||||
formId = Number(formId) || 0;
|
|
||||||
|
|
||||||
if (formId < 1) {
|
|
||||||
return callback(new Error(_('Missing Form ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('SELECT * FROM custom_forms WHERE id=? LIMIT 1', [formId], (err, rows) => {
|
|
||||||
if (err) {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rows || !rows.length) {
|
|
||||||
connection.release();
|
|
||||||
return callback(new Error(_('Custom form not found')));
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('DELETE FROM custom_forms WHERE id=? LIMIT 1', [formId], err => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function setDefaultValues(form) {
|
|
||||||
let getContents = fileName => {
|
|
||||||
try {
|
|
||||||
let basePath = path.join(__dirname, '..', '..');
|
|
||||||
let template = fs.readFileSync(path.join(basePath, fileName), 'utf8');
|
|
||||||
return template.replace(/\{\{#translate\}\}(.*?)\{\{\/translate\}\}/g, (m, s) => _(s));
|
|
||||||
} catch (err) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
allowedKeys.forEach(key => {
|
|
||||||
let modelKey = tools.fromDbKey(key);
|
|
||||||
let base = 'views/subscription/' + key.replace(/_/g, '-');
|
|
||||||
|
|
||||||
if (key.startsWith('mail') || key.startsWith('web')) {
|
|
||||||
form[modelKey] = getContents(base + '.mjml.hbs') || getContents(base + '.hbs') || '';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
form.layout = getContents('views/subscription/layout.mjml.hbs') || '';
|
|
||||||
form.formInputStyle = getContents('static/subscription/form-input-style.css') || '@import url(/subscription/form-input-style.css);';
|
|
||||||
|
|
||||||
return form;
|
|
||||||
}
|
|
||||||
|
|
||||||
function filterKeysAndValues(keysIn, valuesIn, method = 'include', prefixes = []) {
|
|
||||||
let values = [];
|
|
||||||
|
|
||||||
let prefixMatch = key => (
|
|
||||||
prefixes.some(prefix => key.startsWith(prefix))
|
|
||||||
);
|
|
||||||
|
|
||||||
let keys = keysIn.filter((key, index) => {
|
|
||||||
if ((method === 'include' && prefixMatch(key)) || (method === 'exclude' && !prefixMatch(key))) {
|
|
||||||
values.push(valuesIn[index]);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
keys,
|
|
||||||
values
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function testForMjmlErrors(keys, values) {
|
|
||||||
|
|
||||||
let errors = [];
|
|
||||||
let testLayout = '<mjml><mj-body><mj-container>{{{body}}}</mj-container></mj-body></mjml>';
|
|
||||||
|
|
||||||
let hasMjmlError = (template, layout = testLayout) => {
|
|
||||||
let source = layout.replace(/\{\{\{body\}\}\}/g, template);
|
|
||||||
let compiled;
|
|
||||||
|
|
||||||
try {
|
|
||||||
compiled = mjml.mjml2html(source);
|
|
||||||
} catch (err) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (compiled.errors.length) {
|
|
||||||
return compiled.errors[0].message || compiled.errors[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
keys.forEach((key, index) => {
|
|
||||||
if (key.startsWith('mail_') || key.startsWith('web_')) {
|
|
||||||
|
|
||||||
let template = values[index];
|
|
||||||
let err = hasMjmlError(template);
|
|
||||||
|
|
||||||
err && errors.push(key + ': ' + (err.message || err));
|
|
||||||
key === 'mail_confirm_html' && !template.includes('{{confirmUrl}}') && errors.push(key + ': Missing {{confirmUrl}}');
|
|
||||||
|
|
||||||
} else if (key === 'layout') {
|
|
||||||
|
|
||||||
let layout = values[index];
|
|
||||||
let err = hasMjmlError('', layout);
|
|
||||||
|
|
||||||
err && errors.push('layout: ' + (err.message || err));
|
|
||||||
!layout.includes('{{{body}}}') && errors.push('layout: {{{body}}} not found');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
if (errors.length) {
|
|
||||||
errors.forEach((err, index) => {
|
|
||||||
errors[index] = (index + 1) + ') ' + err;
|
|
||||||
});
|
|
||||||
return 'Please fix these MJML errors:\n\n' + errors.join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
|
@ -1,364 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let db = require('../db');
|
|
||||||
let shortid = require('shortid');
|
|
||||||
let util = require('util');
|
|
||||||
let _ = require('../translate')._;
|
|
||||||
|
|
||||||
let geoip = require('geoip-ultralight');
|
|
||||||
let campaigns = require('./campaigns');
|
|
||||||
let subscriptions = require('./subscriptions');
|
|
||||||
let lists = require('./lists');
|
|
||||||
|
|
||||||
let log = require('npmlog');
|
|
||||||
let urllib = require('url');
|
|
||||||
let he = require('he');
|
|
||||||
let ua_parser = require('device');
|
|
||||||
|
|
||||||
module.exports.resolve = (linkCid, callback) => {
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
let query = 'SELECT id, url FROM links WHERE `cid`=? LIMIT 1';
|
|
||||||
connection.query(query, [linkCid], (err, rows) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rows && rows.length) {
|
|
||||||
return callback(null, rows[0].id, rows[0].url);
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback(null, false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.countClick = (remoteIp, useragent, campaignCid, listCid, subscriptionCid, linkId, callback) => {
|
|
||||||
getSubscriptionData(campaignCid, listCid, subscriptionCid, (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data || data.campaign.clickTrackingDisabled) {
|
|
||||||
return callback(null, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.beginTransaction(err => {
|
|
||||||
if (err) {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let country = geoip.lookupCountry(remoteIp) || null;
|
|
||||||
let device = ua_parser(useragent, { unknownUserAgentDeviceType: 'desktop', emptyUserAgentDeviceType: 'desktop' });
|
|
||||||
let query = 'INSERT INTO `campaign_tracker__' + data.campaign.id + '` (`list`, `subscriber`, `link`, `ip`, `device_type`, `country`) VALUES (?,?,?,?,?,?) ON DUPLICATE KEY UPDATE `count`=`count`+1';
|
|
||||||
connection.query(query, [data.list.id, data.subscription.id, linkId, remoteIp, device.type, country], (err, result) => {
|
|
||||||
if (err && err.code !== 'ER_DUP_ENTRY') {
|
|
||||||
return connection.rollback(() => {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err && err.code === 'ER_DUP_ENTRY' || result.affectedRows > 1) {
|
|
||||||
return connection.commit(err => {
|
|
||||||
if (err) {
|
|
||||||
return connection.rollback(() => {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
connection.release();
|
|
||||||
return callback(null, false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = 'UPDATE `subscription__' + data.list.id + '` SET `latest_click`=NOW(), `latest_open`=NOW() WHERE id=?';
|
|
||||||
connection.query(query, [data.subscription.id], err => {
|
|
||||||
if (err) {
|
|
||||||
return connection.rollback(() => {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = 'UPDATE links SET clicks = clicks + 1 WHERE id=?';
|
|
||||||
connection.query(query, [linkId], err => {
|
|
||||||
if (err) {
|
|
||||||
return connection.rollback(() => {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = 'INSERT INTO `campaign_tracker__' + data.campaign.id + '` (`list`, `subscriber`, `link`, `ip`, `device_type`, `country`) VALUES (?,?,?,?,?,?)';
|
|
||||||
connection.query(query, [data.list.id, data.subscription.id, 0, remoteIp, device.type, country], err => {
|
|
||||||
if (err && err.code !== 'ER_DUP_ENTRY') {
|
|
||||||
return connection.rollback(() => {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err && err.code === 'ER_DUP_ENTRY') {
|
|
||||||
return connection.commit(err => {
|
|
||||||
if (err) {
|
|
||||||
return connection.rollback(() => {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
connection.release();
|
|
||||||
return callback(null, false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = 'UPDATE campaigns SET clicks = clicks + 1 WHERE id=?';
|
|
||||||
connection.query(query, [data.campaign.id], err => {
|
|
||||||
if (err) {
|
|
||||||
return connection.rollback(() => {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
connection.commit(err => {
|
|
||||||
if (err) {
|
|
||||||
return connection.rollback(() => {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
connection.release();
|
|
||||||
return callback(null, false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// also count clicks as open events in case beacon image was blocked
|
|
||||||
module.exports.countOpen(remoteIp, useragent, campaignCid, listCid, subscriptionCid, () => false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.countOpen = (remoteIp, useragent, campaignCid, listCid, subscriptionCid, callback) => {
|
|
||||||
getSubscriptionData(campaignCid, listCid, subscriptionCid, (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data || data.campaign.openTrackingDisabled) {
|
|
||||||
return callback(null, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.beginTransaction(err => {
|
|
||||||
if (err) {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let country = geoip.lookupCountry(remoteIp) || null;
|
|
||||||
let device = ua_parser(useragent, { unknownUserAgentDeviceType: 'desktop', emptyUserAgentDeviceType: 'desktop' });
|
|
||||||
let query = 'INSERT INTO `campaign_tracker__' + data.campaign.id + '` (`list`, `subscriber`, `link`, `ip`, `device_type`, `country`) VALUES (?,?,?,?,?,?) ON DUPLICATE KEY UPDATE `count`=`count`+1';
|
|
||||||
connection.query(query, [data.list.id, data.subscription.id, -1, remoteIp, device.type, country], (err, result) => {
|
|
||||||
if (err && err.code !== 'ER_DUP_ENTRY') {
|
|
||||||
return connection.rollback(() => {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (err && err.code === 'ER_DUP_ENTRY' || result.affectedRows > 1) {
|
|
||||||
return connection.commit(err => {
|
|
||||||
if (err) {
|
|
||||||
return connection.rollback(() => {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
connection.release();
|
|
||||||
return callback(null, false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = 'UPDATE `subscription__' + data.list.id + '` SET `latest_open`=NOW() WHERE id=?';
|
|
||||||
connection.query(query, [data.subscription.id], err => {
|
|
||||||
if (err) {
|
|
||||||
return connection.rollback(() => {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = 'UPDATE campaigns SET opened = opened + 1 WHERE id=?';
|
|
||||||
connection.query(query, [data.campaign.id], err => {
|
|
||||||
if (err) {
|
|
||||||
return connection.rollback(() => {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
connection.commit(err => {
|
|
||||||
if (err) {
|
|
||||||
return connection.rollback(() => {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
connection.release();
|
|
||||||
return callback(null, false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.add = (url, campaignId, callback) => {
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let cid = shortid.generate();
|
|
||||||
let query = 'INSERT INTO links (`cid`, `campaign`, `url`) VALUES (?,?,?)';
|
|
||||||
connection.query(query, [cid, campaignId, url], (err, result) => {
|
|
||||||
if (err && err.code !== 'ER_DUP_ENTRY') {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!err && result && result.insertId) {
|
|
||||||
connection.release();
|
|
||||||
return callback(null, result.insertId, cid);
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = 'SELECT id, cid FROM links WHERE `campaign`=? AND `url`=? LIMIT 1';
|
|
||||||
connection.query(query, [campaignId, url], (err, rows) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rows && rows.length) {
|
|
||||||
return callback(null, rows[0].id, rows[0].cid);
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback(null, false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.updateLinks = (campaign, list, subscription, serviceUrl, message, callback) => {
|
|
||||||
if ((campaign.openTrackingDisabled && campaign.clickTrackingDisabled) || !message || !message.trim()) {
|
|
||||||
// tracking is disabled, do not modify the message
|
|
||||||
return setImmediate(() => callback(null, message));
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert tracking image
|
|
||||||
if (!campaign.openTrackingDisabled) {
|
|
||||||
let inserted = false;
|
|
||||||
let imgUrl = urllib.resolve(serviceUrl, util.format('/links/%s/%s/%s', campaign.cid, list.cid, encodeURIComponent(subscription.cid)));
|
|
||||||
let img = '<img src="' + imgUrl + '" width="1" height="1" alt="mt">';
|
|
||||||
message = message.replace(/<\/body\b/i, match => {
|
|
||||||
inserted = true;
|
|
||||||
return img + match;
|
|
||||||
});
|
|
||||||
if (!inserted) {
|
|
||||||
message = message + img;
|
|
||||||
}
|
|
||||||
if (campaign.clickTrackingDisabled) {
|
|
||||||
return callback(null, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!campaign.clickTrackingDisabled) {
|
|
||||||
let re = /(<a[^>]* href\s*=[\s"']*)(http[^"'>\s]+)/gi;
|
|
||||||
let urls = new Set();
|
|
||||||
(message || '').replace(re, (match, prefix, url) => {
|
|
||||||
urls.add(url);
|
|
||||||
});
|
|
||||||
|
|
||||||
let map = new Map();
|
|
||||||
let vals = urls.values();
|
|
||||||
|
|
||||||
let replaceUrls = () => {
|
|
||||||
callback(null,
|
|
||||||
message.replace(re, (match, prefix, url) =>
|
|
||||||
prefix + (map.has(url) ? urllib.resolve(serviceUrl, util.format('/links/%s/%s/%s/%s', campaign.cid, list.cid, encodeURIComponent(subscription.cid), encodeURIComponent(map.get(url)))) : url)));
|
|
||||||
};
|
|
||||||
|
|
||||||
let storeNext = () => {
|
|
||||||
let urlItem = vals.next();
|
|
||||||
if (urlItem.done) {
|
|
||||||
return replaceUrls();
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports.add(he.decode(urlItem.value, {
|
|
||||||
isAttributeValue: true
|
|
||||||
}), campaign.id, (err, linkId, cid) => {
|
|
||||||
if (err) {
|
|
||||||
log.error('Link', err);
|
|
||||||
return storeNext();
|
|
||||||
}
|
|
||||||
map.set(urlItem.value, cid);
|
|
||||||
return storeNext();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
storeNext();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function getSubscriptionData(campaignCid, listCid, subscriptionCid, callback) {
|
|
||||||
campaigns.getByCid(campaignCid, (err, campaign) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
if (!campaign) {
|
|
||||||
return callback(new Error(_('Campaign not found')));
|
|
||||||
}
|
|
||||||
|
|
||||||
lists.getByCid(listCid, (err, list) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
if (!list) {
|
|
||||||
return callback(new Error(_('List not found')));
|
|
||||||
}
|
|
||||||
|
|
||||||
subscriptions.get(list.id, subscriptionCid, (err, subscription) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
if (!subscription) {
|
|
||||||
return callback(new Error(_('Subscription not found')));
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback(null, {
|
|
||||||
campaign,
|
|
||||||
list,
|
|
||||||
subscription
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,333 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let db = require('../db');
|
|
||||||
let tools = require('../tools');
|
|
||||||
let shortid = require('shortid');
|
|
||||||
let segments = require('./segments');
|
|
||||||
let subscriptions = require('./subscriptions');
|
|
||||||
let _ = require('../translate')._;
|
|
||||||
let tableHelpers = require('../table-helpers');
|
|
||||||
const UnsubscriptionMode = require('../../shared/lists').UnsubscriptionMode;
|
|
||||||
|
|
||||||
module.exports.UnsubscriptionMode = UnsubscriptionMode;
|
|
||||||
|
|
||||||
let allowedKeys = ['description', 'default_form', 'public_subscribe', 'unsubscription_mode', 'listunsubscribe_disabled'];
|
|
||||||
|
|
||||||
module.exports.list = (start, limit, callback) => {
|
|
||||||
tableHelpers.list('lists', ['*'], 'name', null, start, limit, callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.filter = (request, parent, callback) => {
|
|
||||||
tableHelpers.filter('lists', ['*'], request, ['#', 'name', 'cid', 'subscribers', 'description'], ['name'], 'name ASC', null, callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.filterQuicklist = (request, callback) => {
|
|
||||||
tableHelpers.filter('lists', ['id', 'name', 'subscribers'], request, ['#', 'name', 'subscribers'], ['name'], 'name ASC', null, callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.quicklist = callback => {
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('SELECT id, name, subscribers FROM lists ORDER BY name LIMIT 1000', (err, rows) => {
|
|
||||||
if (err) {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let lists = (rows || []).map(tools.convertKeys);
|
|
||||||
|
|
||||||
connection.query('SELECT id, list, name FROM segments ORDER BY list, name LIMIT 1000', (err, rows) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let segments = (rows || []).map(tools.convertKeys);
|
|
||||||
|
|
||||||
lists.forEach(list => {
|
|
||||||
list.segments = segments.filter(segment => segment.list === list.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
return callback(null, lists);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.getListsWithEmail = (email, callback) => {
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('SELECT id, name FROM lists', (err, rows) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let lists = (rows || []).map(tools.convertKeys);
|
|
||||||
const results = [];
|
|
||||||
lists.forEach((list, index, arr) => {
|
|
||||||
subscriptions.getByEmail(list.id, email, (err, sub) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
if (sub) {
|
|
||||||
results.push(list.id);
|
|
||||||
}
|
|
||||||
if (index === arr.length - 1) {
|
|
||||||
return callback(null, lists.filter(list => results.includes(list.id)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.getByCid = (cid, callback) => {
|
|
||||||
resolveCid(cid, (err, id) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
if (!id) {
|
|
||||||
return callback(null, false);
|
|
||||||
}
|
|
||||||
module.exports.get(id, callback);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.get = (id, callback) => {
|
|
||||||
id = Number(id) || 0;
|
|
||||||
|
|
||||||
if (id < 1) {
|
|
||||||
return callback(new Error(_('Missing List ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('SELECT * FROM lists WHERE id=?', [id], (err, rows) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rows || !rows.length) {
|
|
||||||
return callback(null, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let list = tools.convertKeys(rows[0]);
|
|
||||||
segments.list(list.id, (err, segmentList) => {
|
|
||||||
if (err || !segmentList) {
|
|
||||||
segmentList = [];
|
|
||||||
}
|
|
||||||
list.segments = segmentList;
|
|
||||||
return callback(null, list);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.update = (id, updates, callback) => {
|
|
||||||
updates = updates || {};
|
|
||||||
id = Number(id) || 0;
|
|
||||||
|
|
||||||
if (id < 1) {
|
|
||||||
return callback(new Error(_('Missing List ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = tools.convertKeys(updates);
|
|
||||||
|
|
||||||
const keys = [];
|
|
||||||
const values = [];
|
|
||||||
|
|
||||||
// The update can be only partial when executed from forms/:list
|
|
||||||
if (!data.customFormChangeOnly) {
|
|
||||||
data.publicSubscribe = data.publicSubscribe ? 1 : 0;
|
|
||||||
data.listunsubscribeDisabled = data.listunsubscribeDisabled ? 1 : 0;
|
|
||||||
data.unsubscriptionMode = Number(data.unsubscriptionMode);
|
|
||||||
|
|
||||||
let name = (data.name || '').toString().trim();
|
|
||||||
|
|
||||||
if (!name) {
|
|
||||||
return callback(new Error(_('List Name must be set')));
|
|
||||||
}
|
|
||||||
|
|
||||||
keys.push('name');
|
|
||||||
values.push(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.keys(data).forEach(key => {
|
|
||||||
let value = data[key].toString().trim();
|
|
||||||
key = tools.toDbKey(key);
|
|
||||||
if (key === 'description') {
|
|
||||||
value = tools.purifyHTML(value);
|
|
||||||
}
|
|
||||||
if (allowedKeys.indexOf(key) >= 0) {
|
|
||||||
keys.push(key);
|
|
||||||
values.push(value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
values.push(id);
|
|
||||||
|
|
||||||
connection.query('UPDATE lists SET ' + keys.map(key => key + '=?').join(', ') + ' WHERE id=? LIMIT 1', values, (err, result) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, result && result.affectedRows || false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.create = (list, callback) => {
|
|
||||||
|
|
||||||
let data = tools.convertKeys(list);
|
|
||||||
data.publicSubscribe = data.publicSubscribe ? 1 : 0;
|
|
||||||
|
|
||||||
let name = (data.name || '').toString().trim();
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
return callback(new Error(_('List Name must be set')));
|
|
||||||
}
|
|
||||||
|
|
||||||
let keys = ['name'];
|
|
||||||
let values = [name];
|
|
||||||
|
|
||||||
Object.keys(data).forEach(key => {
|
|
||||||
let value = data[key].toString().trim();
|
|
||||||
key = tools.toDbKey(key);
|
|
||||||
if (key === 'description') {
|
|
||||||
value = tools.purifyHTML(value);
|
|
||||||
}
|
|
||||||
if (allowedKeys.indexOf(key) >= 0) {
|
|
||||||
keys.push(key);
|
|
||||||
values.push(value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let cid = shortid.generate();
|
|
||||||
keys.push('cid');
|
|
||||||
values.push(cid);
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = 'INSERT INTO lists (`' + keys.join('`, `') + '`) VALUES (' + values.map(() => '?').join(',') + ')';
|
|
||||||
connection.query(query, values, (err, result) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let listId = result && result.insertId || false;
|
|
||||||
if (!listId) {
|
|
||||||
return callback(null, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
createSubscriptionTable(listId, err => {
|
|
||||||
if (err) {
|
|
||||||
// FIXME: rollback
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, listId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.delete = (id, callback) => {
|
|
||||||
id = Number(id) || 0;
|
|
||||||
|
|
||||||
if (id < 1) {
|
|
||||||
return callback(new Error(_('Missing List ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('DELETE FROM lists WHERE id=? LIMIT 1', id, (err, result) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let affected = result && result.affectedRows || 0;
|
|
||||||
|
|
||||||
removeSubscriptionTable(id, err => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, affected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function resolveCid(cid, callback) {
|
|
||||||
cid = (cid || '').toString().trim();
|
|
||||||
if (!cid) {
|
|
||||||
return callback(new Error(_('Missing List CID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
connection.query('SELECT id FROM lists WHERE cid=?', [cid], (err, rows) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, rows && rows[0] && rows[0].id || false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSubscriptionTable(id, callback) {
|
|
||||||
let query = 'CREATE TABLE `subscription__' + id + '` LIKE subscription';
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
connection.query(query, err => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeSubscriptionTable(id, callback) {
|
|
||||||
let query = 'DROP TABLE IF EXISTS `subscription__' + id + '`';
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
connection.query(query, err => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,691 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let tools = require('../tools');
|
|
||||||
let db = require('../db');
|
|
||||||
let fields = require('./fields');
|
|
||||||
let util = require('util');
|
|
||||||
let _ = require('../translate')._;
|
|
||||||
|
|
||||||
module.exports.defaultColumns = [{
|
|
||||||
column: 'email',
|
|
||||||
name: _('Email address'),
|
|
||||||
type: 'string'
|
|
||||||
}, {
|
|
||||||
column: 'opt_in_country',
|
|
||||||
name: _('Signup country'),
|
|
||||||
type: 'string'
|
|
||||||
}, {
|
|
||||||
column: 'created',
|
|
||||||
name: _('Sign up date'),
|
|
||||||
type: 'date'
|
|
||||||
}, {
|
|
||||||
column: 'latest_open',
|
|
||||||
name: _('Latest open'),
|
|
||||||
type: 'date'
|
|
||||||
}, {
|
|
||||||
column: 'latest_click',
|
|
||||||
name: _('Latest click'),
|
|
||||||
type: 'date'
|
|
||||||
}, {
|
|
||||||
column: 'first_name',
|
|
||||||
name: _('First name'),
|
|
||||||
type: 'string'
|
|
||||||
}, {
|
|
||||||
column: 'last_name',
|
|
||||||
name: _('Last name'),
|
|
||||||
type: 'string'
|
|
||||||
}];
|
|
||||||
|
|
||||||
module.exports.list = (listId, callback) => {
|
|
||||||
listId = Number(listId) || 0;
|
|
||||||
|
|
||||||
if (listId < 1) {
|
|
||||||
return callback(new Error(_('Missing List ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = 'SELECT * FROM segments WHERE list=? ORDER BY name';
|
|
||||||
connection.query(query, [listId], (err, rows) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let segments = (rows || []).map(tools.convertKeys);
|
|
||||||
return callback(null, segments);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.get = (id, callback) => {
|
|
||||||
id = Number(id) || 0;
|
|
||||||
|
|
||||||
if (id < 1) {
|
|
||||||
return callback(new Error(_('Missing Segment ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = 'SELECT * FROM segments WHERE id=? LIMIT 1';
|
|
||||||
connection.query(query, [id], (err, rows) => {
|
|
||||||
if (err) {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
if (!rows || !rows.length) {
|
|
||||||
connection.release();
|
|
||||||
return callback(new Error(_('Segment not found')));
|
|
||||||
}
|
|
||||||
|
|
||||||
let segment = tools.convertKeys(rows[0]);
|
|
||||||
|
|
||||||
let query = 'SELECT * FROM segment_rules WHERE segment=? ORDER BY id ASC';
|
|
||||||
connection.query(query, [id], (err, rows) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
fields.list(segment.list, (err, fieldList) => {
|
|
||||||
if (err || !fieldList) {
|
|
||||||
fieldList = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
segment.columns = [].concat(module.exports.defaultColumns);
|
|
||||||
fieldList.forEach(field => {
|
|
||||||
if (fields.genericTypes[field.type] === 'textarea') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (field.column) {
|
|
||||||
segment.columns.push({
|
|
||||||
column: field.column,
|
|
||||||
name: field.name,
|
|
||||||
type: fields.genericTypes[field.type] || 'string'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (field.options) {
|
|
||||||
field.options.forEach(subField => {
|
|
||||||
if (subField.column) {
|
|
||||||
segment.columns.push({
|
|
||||||
column: subField.column,
|
|
||||||
name: field.name + ': ' + subField.name,
|
|
||||||
type: fields.genericTypes[subField.type] || 'string'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
segment.rules = (rows || []).map(rule => {
|
|
||||||
rule = tools.convertKeys(rule);
|
|
||||||
if (rule.value) {
|
|
||||||
try {
|
|
||||||
rule.value = JSON.parse(rule.value);
|
|
||||||
} catch (E) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!rule.value) {
|
|
||||||
rule.value = {};
|
|
||||||
}
|
|
||||||
rule.columnType = segment.columns.filter(column => rule.column === column.column).pop() || {};
|
|
||||||
rule.name = rule.columnType.name || '';
|
|
||||||
switch (rule.columnType.type) {
|
|
||||||
case 'number':
|
|
||||||
case 'date':
|
|
||||||
case 'birthday':
|
|
||||||
if (rule.value.relativeRange) {
|
|
||||||
let startString = rule.value.startDirection ? util.format(_('%s days after today'), rule.value.start) : util.format(_('%s days before today'), rule.value.start);
|
|
||||||
let endString = rule.value.endDirection ? util.format(_('%s days after today'), rule.value.end) : util.format(_('%s days before today'), rule.value.end);
|
|
||||||
rule.formatted = (rule.value.start ? startString : _('today')) + ' … ' + (rule.value.end ? endString : _('today'));
|
|
||||||
} else if (rule.value.range) {
|
|
||||||
rule.formatted = (rule.value.start || '') + ' … ' + (rule.value.end || '');
|
|
||||||
} else {
|
|
||||||
rule.formatted = rule.value.value || '';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'boolean':
|
|
||||||
rule.formatted = rule.value.value ? _('Selected') : _('Not selected');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
rule.formatted = (rule.value.negate ? '!= ' : '') + (rule.value.value || '');
|
|
||||||
}
|
|
||||||
|
|
||||||
return rule;
|
|
||||||
});
|
|
||||||
|
|
||||||
return callback(null, segment);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.create = (listId, segment, callback) => {
|
|
||||||
listId = Number(listId) || 0;
|
|
||||||
|
|
||||||
if (listId < 1) {
|
|
||||||
return callback(new Error(_('Missing List ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
segment = tools.convertKeys(segment);
|
|
||||||
|
|
||||||
segment.name = (segment.name || '').toString().trim();
|
|
||||||
segment.type = Number(segment.type) || 0;
|
|
||||||
|
|
||||||
if (!segment.name) {
|
|
||||||
return callback(new Error(_('Field Name must be set')));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (segment.type <= 0) {
|
|
||||||
return callback(new Error(_('Invalid segment rule type')));
|
|
||||||
}
|
|
||||||
|
|
||||||
let keys = ['list', 'name', 'type'];
|
|
||||||
let values = [listId, segment.name, segment.type];
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = 'INSERT INTO segments (`' + keys.join('`, `') + '`) VALUES (' + values.map(() => '?').join(',') + ')';
|
|
||||||
connection.query(query, values, (err, result) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, result && result.insertId || false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.update = (id, updates, callback) => {
|
|
||||||
updates = updates || {};
|
|
||||||
id = Number(id) || 0;
|
|
||||||
|
|
||||||
if (id < 1) {
|
|
||||||
return callback(new Error(_('Missing Segment ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
let segment = tools.convertKeys(updates);
|
|
||||||
|
|
||||||
segment.name = (segment.name || '').toString().trim();
|
|
||||||
segment.type = Number(segment.type) || 0;
|
|
||||||
|
|
||||||
if (!segment.name) {
|
|
||||||
return callback(new Error(_('Field Name must be set')));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (segment.type <= 0) {
|
|
||||||
return callback(new Error(_('Invalid segment rule type')));
|
|
||||||
}
|
|
||||||
|
|
||||||
let keys = ['name', 'type'];
|
|
||||||
let values = [segment.name, segment.type];
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
values.push(id);
|
|
||||||
|
|
||||||
connection.query('UPDATE segments SET ' + keys.map(key => '`' + key + '`=?').join(', ') + ' WHERE id=? LIMIT 1', values, (err, result) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, result && result.affectedRows || false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.delete = (id, callback) => {
|
|
||||||
id = Number(id) || 0;
|
|
||||||
|
|
||||||
if (id < 1) {
|
|
||||||
return callback(new Error(_('Missing Segment ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('DELETE FROM segments WHERE id=? LIMIT 1', [id], err => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.createRule = (segmentId, rule, callback) => {
|
|
||||||
segmentId = Number(segmentId) || 0;
|
|
||||||
|
|
||||||
if (segmentId < 1) {
|
|
||||||
return callback(new Error(_('Missing Segment ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
rule = tools.convertKeys(rule);
|
|
||||||
|
|
||||||
module.exports.get(segmentId, (err, segment) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!segment) {
|
|
||||||
return callback(new Error(_('Selected segment not found')));
|
|
||||||
}
|
|
||||||
|
|
||||||
let column = segment.columns.filter(column => column.column === rule.column).pop();
|
|
||||||
if (!column) {
|
|
||||||
return callback(new Error(_('Invalid rule type')));
|
|
||||||
}
|
|
||||||
|
|
||||||
let value;
|
|
||||||
|
|
||||||
switch (column.type) {
|
|
||||||
case 'date':
|
|
||||||
case 'birthday':
|
|
||||||
case 'number':
|
|
||||||
if (column.type === 'date' && rule.range === 'relative') {
|
|
||||||
value = {
|
|
||||||
relativeRange: true,
|
|
||||||
start: Number(rule.startRelative) || 0,
|
|
||||||
startDirection: Number(rule.startDirection) ? 1 : 0,
|
|
||||||
end: Number(rule.endRelative) || 0,
|
|
||||||
endDirection: Number(rule.endDirection) ? 1 : 0
|
|
||||||
};
|
|
||||||
} else if (rule.range === 'yes') {
|
|
||||||
value = {
|
|
||||||
range: true,
|
|
||||||
start: rule.start,
|
|
||||||
end: rule.end
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
value = {
|
|
||||||
value: rule.value
|
|
||||||
};
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'boolean':
|
|
||||||
value = {
|
|
||||||
value: rule.value ? 1 : 0
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
value = {
|
|
||||||
negate: Number(rule.negate) ? 1 : 0,
|
|
||||||
value: rule.value
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let keys = ['segment', 'column', 'value'];
|
|
||||||
let values = [segment.id, rule.column, JSON.stringify(value)];
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = 'INSERT INTO segment_rules (`' + keys.join('`, `') + '`) VALUES (' + values.map(() => '?').join(',') + ')';
|
|
||||||
connection.query(query, values, (err, result) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, result && result.insertId || false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.getRule = (id, callback) => {
|
|
||||||
id = Number(id) || 0;
|
|
||||||
|
|
||||||
if (id < 1) {
|
|
||||||
return callback(new Error(_('Missing Rule ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = 'SELECT * FROM segment_rules WHERE id=? LIMIT 1';
|
|
||||||
connection.query(query, [id], (err, rows) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rows || !rows.length) {
|
|
||||||
return callback(new Error(_('Specified rule not found')));
|
|
||||||
}
|
|
||||||
|
|
||||||
let rule = tools.convertKeys(rows[0]);
|
|
||||||
|
|
||||||
module.exports.get(rule.segment, (err, segment) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!segment) {
|
|
||||||
return callback(new Error(_('Specified segment not found')));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rule.value) {
|
|
||||||
try {
|
|
||||||
rule.value = JSON.parse(rule.value);
|
|
||||||
} catch (E) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!rule.value) {
|
|
||||||
rule.value = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
rule.columnType = segment.columns.filter(column => rule.column === column.column).pop() || {};
|
|
||||||
|
|
||||||
rule.name = rule.columnType.name || '';
|
|
||||||
switch (rule.columnType.type) {
|
|
||||||
case 'number':
|
|
||||||
case 'date':
|
|
||||||
case 'birthday':
|
|
||||||
if (rule.value.relativeRange) {
|
|
||||||
|
|
||||||
let startString = rule.value.startDirection ? util.format(_('%s days after today'), rule.value.start) : util.format(_('%s days before today'), rule.value.start);
|
|
||||||
let endString = rule.value.endDirection ? util.format(_('%s days after today'), rule.value.end) : util.format(_('%s days before today'), rule.value.end);
|
|
||||||
rule.formatted = (rule.value.start ? startString : _('today')) + ' … ' + (rule.value.end ? endString : _('today'));
|
|
||||||
} else if (rule.value.range) {
|
|
||||||
rule.formatted = (rule.value.start || '') + ' … ' + (rule.value.end || '');
|
|
||||||
} else {
|
|
||||||
rule.formatted = rule.value.value || '';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'boolean':
|
|
||||||
rule.formatted = rule.value.value ? _('Selected') : _('Not selected');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
rule.formatted = (rule.value.negate ? '!= ' : '') + (rule.value.value || '');
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback(null, rule);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.updateRule = (id, rule, callback) => {
|
|
||||||
id = Number(id) || 0;
|
|
||||||
|
|
||||||
if (id < 1) {
|
|
||||||
return callback(new Error(_('Missing Rule ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
rule = tools.convertKeys(rule);
|
|
||||||
|
|
||||||
module.exports.getRule(id, (err, existingRule) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!existingRule) {
|
|
||||||
return callback(new Error(_('Selected rule not found')));
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports.get(existingRule.segment, (err, segment) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!segment) {
|
|
||||||
return callback(new Error(_('Selected segment not found')));
|
|
||||||
}
|
|
||||||
|
|
||||||
let column = segment.columns.filter(column => column.column === existingRule.column).pop();
|
|
||||||
if (!column) {
|
|
||||||
return callback(new Error(_('Invalid rule type')));
|
|
||||||
}
|
|
||||||
|
|
||||||
let value;
|
|
||||||
switch (column.type) {
|
|
||||||
case 'date':
|
|
||||||
case 'birthday':
|
|
||||||
case 'number':
|
|
||||||
if (column.type === 'date' && rule.range === 'relative') {
|
|
||||||
value = {
|
|
||||||
relativeRange: true,
|
|
||||||
start: Number(rule.startRelative) || 0,
|
|
||||||
startDirection: Number(rule.startDirection) ? 1 : 0,
|
|
||||||
end: Number(rule.endRelative) || 0,
|
|
||||||
endDirection: Number(rule.endDirection) ? 1 : 0
|
|
||||||
};
|
|
||||||
} else if (rule.range === 'yes') {
|
|
||||||
value = {
|
|
||||||
range: true,
|
|
||||||
start: rule.start,
|
|
||||||
end: rule.end
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
value = {
|
|
||||||
value: rule.value
|
|
||||||
};
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'boolean':
|
|
||||||
value = {
|
|
||||||
value: rule.value ? 1 : 0
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
value = {
|
|
||||||
negate: Number(rule.negate) ? 1 : 0,
|
|
||||||
value: rule.value
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let keys = ['value'];
|
|
||||||
let values = [JSON.stringify(value)];
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
values.push(id);
|
|
||||||
|
|
||||||
connection.query('UPDATE segment_rules SET ' + keys.map(key => '`' + key + '`=?').join(', ') + ' WHERE id=? LIMIT 1', values, (err, result) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, result && result.affectedRows || false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.deleteRule = (id, callback) => {
|
|
||||||
id = Number(id) || 0;
|
|
||||||
|
|
||||||
if (id < 1) {
|
|
||||||
return callback(new Error(_('Missing Rule ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('DELETE FROM segment_rules WHERE id=? LIMIT 1', [id], err => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.getQuery = (id, prefix, callback) => {
|
|
||||||
module.exports.get(id, (err, segment) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!segment) {
|
|
||||||
return callback(new Error(_('Segment not found')));
|
|
||||||
}
|
|
||||||
|
|
||||||
prefix = prefix ? prefix + '.' : '';
|
|
||||||
|
|
||||||
let query = [];
|
|
||||||
let values = [];
|
|
||||||
|
|
||||||
let getRelativeDate = (days, direction) => {
|
|
||||||
let date = new Date(Date.now() + (direction ? 1 : -1) * days * 24 * 3600 * 1000);
|
|
||||||
return date.toISOString().substr(0, 10);
|
|
||||||
};
|
|
||||||
|
|
||||||
let getDate = (value, nextDay) => {
|
|
||||||
let parts = value.trim().split(/\D/);
|
|
||||||
let year = Number(parts.shift()) || 0;
|
|
||||||
let month = Number(parts.shift()) || 0;
|
|
||||||
let day = Number(parts.shift()) || 0;
|
|
||||||
if (!year || !month || !day) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return new Date(Date.UTC(year, month - 1, day + (nextDay ? 1 : 0)));
|
|
||||||
};
|
|
||||||
|
|
||||||
segment.rules.forEach(rule => {
|
|
||||||
switch (rule.columnType.type) {
|
|
||||||
case 'string':
|
|
||||||
let condition = rule.value.negate ? 'NOT LIKE' : 'LIKE';
|
|
||||||
query.push(prefix + '`' + rule.columnType.column + '` ' + condition + ' ?');
|
|
||||||
values.push(rule.value.value);
|
|
||||||
break;
|
|
||||||
case 'boolean':
|
|
||||||
query.push(prefix + '`' + rule.columnType.column + '` = ?');
|
|
||||||
values.push(rule.value.value);
|
|
||||||
break;
|
|
||||||
case 'number':
|
|
||||||
if (rule.value.range) {
|
|
||||||
let ruleval = '';
|
|
||||||
if (rule.value.start) {
|
|
||||||
ruleval = prefix + '`' + rule.columnType.column + '` >= ?';
|
|
||||||
values.push(rule.value.start);
|
|
||||||
}
|
|
||||||
if (rule.value.end) {
|
|
||||||
ruleval = (ruleval ? '(' + ruleval + ' AND ' : '') + prefix + '`' + rule.columnType.column + '` < ?' + (ruleval ? ')' : '');
|
|
||||||
values.push(rule.value.end);
|
|
||||||
}
|
|
||||||
if (ruleval) {
|
|
||||||
query.push(ruleval);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
query.push(prefix + '`' + rule.columnType.column + '` = ?');
|
|
||||||
values.push(rule.value.value);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'birthday':
|
|
||||||
if (rule.value.range) {
|
|
||||||
let start = rule.value.start || '01-01';
|
|
||||||
let end = rule.value.end || '12-31';
|
|
||||||
query.push('(' + prefix + '`' + rule.columnType.column + '` >= ? AND ' + prefix + '`' + rule.columnType.column + '` < ?)');
|
|
||||||
values.push(getDate('2000-' + start));
|
|
||||||
values.push(getDate('2000-' + end, true));
|
|
||||||
} else {
|
|
||||||
query.push('(' + prefix + '`' + rule.columnType.column + '` >= ? AND ' + prefix + '`' + rule.columnType.column + '` < ?)');
|
|
||||||
values.push(getDate('2000-' + rule.value.value));
|
|
||||||
values.push(getDate('2000-' + rule.value.value, true));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'date':
|
|
||||||
if (rule.value.relativeRange) {
|
|
||||||
query.push('(' + prefix + '`' + rule.columnType.column + '` >= ? AND ' + prefix + '`' + rule.columnType.column + '` < ?)');
|
|
||||||
// start
|
|
||||||
values.push(getDate(getRelativeDate(rule.value.start, rule.value.startDirection)));
|
|
||||||
// end
|
|
||||||
values.push(getDate(getRelativeDate(rule.value.end, rule.value.endDirection), true));
|
|
||||||
} else if (rule.value.range) {
|
|
||||||
let ruleval = '';
|
|
||||||
if (rule.value.start) {
|
|
||||||
ruleval = prefix + '`' + rule.columnType.column + '` >= ?';
|
|
||||||
values.push(getDate(rule.value.start));
|
|
||||||
}
|
|
||||||
if (rule.value.end) {
|
|
||||||
ruleval = (ruleval ? '(' + ruleval + ' AND ' : '') + prefix + '`' + rule.columnType.column + '` < ?' + (ruleval ? ')' : '');
|
|
||||||
values.push(getDate(rule.value.end, true));
|
|
||||||
}
|
|
||||||
if (ruleval) {
|
|
||||||
query.push(ruleval);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
query.push('(' + prefix + '`' + rule.columnType.column + '` >= ? AND ' + prefix + '`' + rule.columnType.column + '` < ?)');
|
|
||||||
values.push(getDate(rule.value.value));
|
|
||||||
values.push(getDate(rule.value.value, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return callback(null, {
|
|
||||||
where: query.join(segment.type === 1 ? ' AND ' : ' OR ') || '1',
|
|
||||||
values
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.subscribers = (id, onlySubscribed, callback) => {
|
|
||||||
module.exports.get(id, (err, segment) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
if (!segment) {
|
|
||||||
return callback(new Error(_('Segment not found')));
|
|
||||||
}
|
|
||||||
module.exports.getQuery(id, false, (err, queryData) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let query;
|
|
||||||
if (!onlySubscribed) {
|
|
||||||
query = 'SELECT COUNT(id) AS `count` FROM `subscription__' + segment.list + '` WHERE ' + queryData.where + ' LIMIT 1';
|
|
||||||
} else {
|
|
||||||
query = 'SELECT COUNT(id) AS `count` FROM `subscription__' + segment.list + '` WHERE `status`=1 ' + (queryData.where ? ' AND (' + queryData.where + ')' : '') + ' LIMIT 1';
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query(query, queryData.values, (err, rows) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
let count = rows && rows[0] && rows[0].count || 0;
|
|
||||||
return callback(null, count);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,926 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let db = require('../db');
|
|
||||||
let shortid = require('shortid');
|
|
||||||
let striptags = require('striptags');
|
|
||||||
let tools = require('../tools');
|
|
||||||
let helpers = require('../helpers');
|
|
||||||
let fields = require('./fields');
|
|
||||||
let segments = require('./segments');
|
|
||||||
let _ = require('../translate')._;
|
|
||||||
let tableHelpers = require('../table-helpers');
|
|
||||||
|
|
||||||
const Status = require('../../shared/lists').SubscriptionStatus;
|
|
||||||
module.exports.Status = Status;
|
|
||||||
|
|
||||||
module.exports.list = (listId, start, limit, callback) => {
|
|
||||||
listId = Number(listId) || 0;
|
|
||||||
if (!listId) {
|
|
||||||
return callback(new Error('Missing List ID'));
|
|
||||||
}
|
|
||||||
|
|
||||||
tableHelpers.list('subscription__' + listId, ['*'], 'email', null, start, limit, (err, rows, total) => {
|
|
||||||
if (!err) {
|
|
||||||
rows = rows.map(row => tools.convertKeys(row));
|
|
||||||
}
|
|
||||||
return callback(err, rows, total);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.listTestUsers = (listId, callback) => {
|
|
||||||
listId = Number(listId) || 0;
|
|
||||||
|
|
||||||
if (listId < 1) {
|
|
||||||
return callback(new Error('Missing List ID'));
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('SELECT id, cid, email, first_name, last_name FROM `subscription__' + listId + '` WHERE is_test=1 LIMIT 100', (err, rows) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rows || !rows.length) {
|
|
||||||
return callback(null, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
let subscribers = rows.map(subscriber => {
|
|
||||||
subscriber = tools.convertKeys(subscriber);
|
|
||||||
let fullName = [].concat(subscriber.firstName || []).concat(subscriber.lastName || []).join(' ');
|
|
||||||
if (fullName) {
|
|
||||||
subscriber.displayName = fullName + ' <' + subscriber.email + '>';
|
|
||||||
} else {
|
|
||||||
subscriber.displayName = subscriber.email;
|
|
||||||
}
|
|
||||||
return subscriber;
|
|
||||||
});
|
|
||||||
return callback(null, subscribers);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.filter = (listId, request, columns, segmentId, callback) => {
|
|
||||||
listId = Number(listId) || 0;
|
|
||||||
segmentId = Number(segmentId) || 0;
|
|
||||||
|
|
||||||
if (!listId) {
|
|
||||||
return callback(new Error(_('Missing List ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (segmentId) {
|
|
||||||
segments.getQuery(segmentId, false, (err, queryData) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
tableHelpers.filter('subscription__' + listId, ['*'], request, columns, ['email', 'first_name', 'last_name'], 'email ASC', queryData, callback);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
tableHelpers.filter('subscription__' + listId, ['*'], request, columns, ['email', 'first_name', 'last_name'], 'email ASC', null, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
Adds a new subscription. Returns error if a subscription with the same email address is already present and is not unsubscribed.
|
|
||||||
If it is unsubscribed, the existing subscription is changed based on the provided data.
|
|
||||||
If meta.partial is true, it updates even an active subscription.
|
|
||||||
*/
|
|
||||||
module.exports.insert = (listId, meta, subscriptionData, callback) => {
|
|
||||||
meta = tools.convertKeys(meta);
|
|
||||||
subscriptionData = tools.convertKeys(subscriptionData);
|
|
||||||
|
|
||||||
meta.email = meta.email || subscriptionData.email;
|
|
||||||
meta.cid = meta.cid || shortid.generate();
|
|
||||||
|
|
||||||
fields.list(listId, (err, fieldList) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let insertKeys = ['email', 'cid', 'opt_in_ip', 'opt_in_country', 'imported'];
|
|
||||||
let insertValues = [meta.email, meta.cid, meta.optInIp || null, meta.optInCountry || null, meta.imported || null];
|
|
||||||
let keys = [];
|
|
||||||
let values = [];
|
|
||||||
|
|
||||||
let allowedKeys = ['first_name', 'last_name', 'tz', 'is_test'];
|
|
||||||
Object.keys(subscriptionData).forEach(key => {
|
|
||||||
let value = subscriptionData[key];
|
|
||||||
key = tools.toDbKey(key);
|
|
||||||
if (key === 'tz') {
|
|
||||||
value = (value || '').toString().toLowerCase().trim();
|
|
||||||
}
|
|
||||||
if (key === 'is_test') {
|
|
||||||
value = value ? '1' : '0';
|
|
||||||
}
|
|
||||||
if (allowedKeys.indexOf(key) >= 0) {
|
|
||||||
keys.push(key);
|
|
||||||
values.push(value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fields.getValues(fields.getRow(fieldList, subscriptionData, true, true, !!meta.partial), true).forEach(field => {
|
|
||||||
keys.push(field.key);
|
|
||||||
values.push(field.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
values = values.map(v => typeof v === 'string' ? striptags(v) : v);
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.beginTransaction(err => {
|
|
||||||
if (err) {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = 'SELECT `id`, `status`, `cid` FROM `subscription__' + listId + '` WHERE `email`=? OR `cid`=? LIMIT 1';
|
|
||||||
connection.query(query, [meta.email, meta.cid], (err, rows) => {
|
|
||||||
if (err) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
let query;
|
|
||||||
let queryArgs;
|
|
||||||
let existing = rows && rows[0] || false;
|
|
||||||
let entryId = existing ? existing.id : false;
|
|
||||||
|
|
||||||
meta.cid = existing ? rows[0].cid : meta.cid;
|
|
||||||
|
|
||||||
// meta.status may be 'undefined' or '0' when adding a subscription via API call or CSV import. In both cases meta.partial is 'true'.
|
|
||||||
// This must either update an existing subscription without changing its status or insert a new subscription with status SUBSCRIBED.
|
|
||||||
meta.status = meta.status || (existing ? existing.status : Status.SUBSCRIBED);
|
|
||||||
|
|
||||||
let statusChange = !existing || existing.status !== meta.status;
|
|
||||||
let statusDirection;
|
|
||||||
|
|
||||||
if (existing && existing.status === Status.SUBSCRIBED && !meta.partial) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(new Error(_('Email address already registered'))));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (statusChange) {
|
|
||||||
keys.push('status', 'status_change');
|
|
||||||
values.push(meta.status, new Date());
|
|
||||||
statusDirection = !existing ? (meta.status === Status.SUBSCRIBED ? '+' : false) : (existing.status === Status.SUBSCRIBED ? '-' : '+');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!keys.length) {
|
|
||||||
// nothing to update
|
|
||||||
return connection.commit(err => {
|
|
||||||
if (err) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
|
|
||||||
}
|
|
||||||
connection.release();
|
|
||||||
return callback(null, {
|
|
||||||
entryId,
|
|
||||||
cid: meta.cid,
|
|
||||||
inserted: !existing
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!existing) {
|
|
||||||
// insert as new
|
|
||||||
keys = insertKeys.concat(keys);
|
|
||||||
queryArgs = values = insertValues.concat(values);
|
|
||||||
query = 'INSERT INTO `subscription__' + listId + '` (`' + keys.join('`, `') + '`) VALUES (' + keys.map(() => '?').join(',') + ')';
|
|
||||||
} else {
|
|
||||||
// update existing
|
|
||||||
queryArgs = values.concat(existing.id);
|
|
||||||
query = 'UPDATE `subscription__' + listId + '` SET ' + keys.map(key => '`' + key + '`=?') + ' WHERE id=? LIMIT 1';
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query(query, queryArgs, (err, result) => {
|
|
||||||
if (err) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
entryId = result.insertId || entryId;
|
|
||||||
|
|
||||||
if (statusChange && statusDirection) {
|
|
||||||
connection.query('UPDATE lists SET `subscribers`=`subscribers`' + statusDirection + '1 WHERE id=?', [listId], err => {
|
|
||||||
if (err) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
|
|
||||||
}
|
|
||||||
connection.commit(err => {
|
|
||||||
if (err) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
|
|
||||||
}
|
|
||||||
connection.release();
|
|
||||||
return callback(null, {
|
|
||||||
entryId,
|
|
||||||
cid: meta.cid,
|
|
||||||
inserted: !existing
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
connection.commit(err => {
|
|
||||||
if (err) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
|
|
||||||
}
|
|
||||||
connection.release();
|
|
||||||
return callback(null, {
|
|
||||||
entryId,
|
|
||||||
cid: meta.cid,
|
|
||||||
inserted: !existing
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.get = (listId, cid, callback) => {
|
|
||||||
cid = (cid || '').toString().trim();
|
|
||||||
|
|
||||||
if (!cid) {
|
|
||||||
return callback(new Error(_('Missing Subscription ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('SELECT * FROM `subscription__' + listId + '` WHERE cid=?', [cid], (err, rows) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rows || !rows.length) {
|
|
||||||
return callback(null, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let subscription = tools.convertKeys(rows[0]);
|
|
||||||
// ensure list id in response
|
|
||||||
subscription.list = subscription.list || listId;
|
|
||||||
return callback(null, subscription);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.getById = (listId, id, callback) => {
|
|
||||||
id = Number(id) || 0;
|
|
||||||
|
|
||||||
if (!id) {
|
|
||||||
return callback(new Error(_('Missing Subscription ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('SELECT * FROM `subscription__' + listId + '` WHERE id=?', [id], (err, rows) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rows || !rows.length) {
|
|
||||||
return callback(null, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let subscription = tools.convertKeys(rows[0]);
|
|
||||||
// ensure list id in response
|
|
||||||
subscription.list = subscription.list || listId;
|
|
||||||
return callback(null, subscription);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.getByEmail = (listId, email, callback) => {
|
|
||||||
if (!email) {
|
|
||||||
return callback(new Error(_('Missing Subscription email address')));
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('SELECT * FROM `subscription__' + listId + '` WHERE email=?', [email], (err, rows) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rows || !rows.length) {
|
|
||||||
return callback(null, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let subscription = tools.convertKeys(rows[0]);
|
|
||||||
// ensure list id in response
|
|
||||||
subscription.list = subscription.list || listId;
|
|
||||||
return callback(null, subscription);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.getWithMergeTags = (listId, cid, callback) => {
|
|
||||||
module.exports.get(listId, cid, (err, subscription) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!subscription) {
|
|
||||||
return callback(null, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
fields.list(listId, (err, fieldList) => {
|
|
||||||
if (err || !fieldList) {
|
|
||||||
return fieldList = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
subscription.mergeTags = {
|
|
||||||
EMAIL: subscription.email,
|
|
||||||
FIRST_NAME: subscription.firstName,
|
|
||||||
LAST_NAME: subscription.lastName,
|
|
||||||
FULL_NAME: [].concat(subscription.firstName || []).concat(subscription.lastName || []).join(' '),
|
|
||||||
TIMEZONE: subscription.tz || ''
|
|
||||||
};
|
|
||||||
|
|
||||||
fields.getRow(fieldList, subscription, false, true).forEach(field => {
|
|
||||||
if (field.mergeTag) {
|
|
||||||
subscription.mergeTags[field.mergeTag] = field.mergeValue || '';
|
|
||||||
}
|
|
||||||
if (field.options) {
|
|
||||||
field.options.forEach(subField => {
|
|
||||||
if (subField.mergeTag) {
|
|
||||||
subscription.mergeTags[subField.mergeTag] = subField.mergeValue || '';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return callback(null, subscription);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.update = (listId, cid, updates, allowEmail, callback) => {
|
|
||||||
updates = tools.convertKeys(updates);
|
|
||||||
listId = Number(listId) || 0;
|
|
||||||
cid = (cid || '').toString().trim();
|
|
||||||
|
|
||||||
let keys = [];
|
|
||||||
let values = [];
|
|
||||||
|
|
||||||
if (listId < 1) {
|
|
||||||
return callback(new Error(_('Missing List ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!cid) {
|
|
||||||
return callback(new Error(_('Missing Subscription ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
fields.list(listId, (err, fieldList) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let allowedKeys = ['first_name', 'last_name', 'tz', 'is_test'];
|
|
||||||
|
|
||||||
if (allowEmail) {
|
|
||||||
allowedKeys.unshift('email');
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.keys(updates).forEach(key => {
|
|
||||||
let value = updates[key];
|
|
||||||
key = tools.toDbKey(key);
|
|
||||||
if (key === 'tz') {
|
|
||||||
value = (value || '').toString().toLowerCase().trim();
|
|
||||||
}
|
|
||||||
if (allowedKeys.indexOf(key) >= 0) {
|
|
||||||
keys.push(key);
|
|
||||||
values.push(value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fields.getValues(fields.getRow(fieldList, updates, true, true, true), true).forEach(field => {
|
|
||||||
keys.push(field.key);
|
|
||||||
values.push(field.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!values.length) {
|
|
||||||
return callback(null, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
values = values.map(v => typeof v === 'string' ? striptags(v) : v);
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
values.push(cid);
|
|
||||||
connection.query('UPDATE `subscription__' + listId + '` SET ' + keys.map(key => '`' + key + '`=?').join(', ') + ' WHERE `cid`=? LIMIT 1', values, (err, result) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, result && result.affectedRows || false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.changeStatus = (listId, id, campaignId, status, callback) => {
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
connection.beginTransaction(err => {
|
|
||||||
if (err) {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('SELECT `status` FROM `subscription__' + listId + '` WHERE id=? LIMIT 1', [id], (err, rows) => {
|
|
||||||
if (err) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rows || !rows.length) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(null, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
let oldStatus = rows[0].status;
|
|
||||||
let statusChange = oldStatus !== status;
|
|
||||||
let statusDirection;
|
|
||||||
|
|
||||||
if (!statusChange) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(null, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (statusChange && oldStatus === Status.SUBSCRIBED || status === Status.SUBSCRIBED) {
|
|
||||||
statusDirection = status === Status.SUBSCRIBED ? '+' : '-';
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('UPDATE `subscription__' + listId + '` SET `status`=?, `status_change`=NOW() WHERE id=? LIMIT 1', [status, id], err => {
|
|
||||||
if (err) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!statusDirection) {
|
|
||||||
return connection.commit(err => {
|
|
||||||
if (err) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
|
|
||||||
}
|
|
||||||
connection.release();
|
|
||||||
return callback(null, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('UPDATE `lists` SET `subscribers`=`subscribers`' + statusDirection + '1 WHERE id=? LIMIT 1', [listId], err => {
|
|
||||||
if (err) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
// status change is not related to a campaign or it marks message as bounced etc.
|
|
||||||
if (!campaignId || status !== Status.SUBSCRIBED && status !== Status.UNSUBSCRIBED) {
|
|
||||||
return connection.commit(err => {
|
|
||||||
if (err) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
|
|
||||||
}
|
|
||||||
connection.release();
|
|
||||||
return callback(null, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('SELECT `id` FROM `campaigns` WHERE `cid`=? LIMIT 1', [campaignId], (err, rows) => {
|
|
||||||
if (err) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
let campaign = rows && rows[0] || false;
|
|
||||||
|
|
||||||
if (!campaign) {
|
|
||||||
// should not happend
|
|
||||||
return connection.commit(err => {
|
|
||||||
if (err) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
|
|
||||||
}
|
|
||||||
connection.release();
|
|
||||||
return callback(null, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// we should see only unsubscribe events here but you never know
|
|
||||||
connection.query('UPDATE `campaigns` SET `unsubscribed`=`unsubscribed`' + (status === Status.UNSUBSCRIBED ? '+' : '-') + '1 WHERE `cid`=? LIMIT 1', [campaignId], err => {
|
|
||||||
if (err) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = 'UPDATE `campaign__' + campaign.id + '` SET `status`=? WHERE `list`=? AND `subscription`=? LIMIT 1';
|
|
||||||
let values = [status, listId, id];
|
|
||||||
|
|
||||||
// Updated tracker status
|
|
||||||
connection.query(query, values, err => {
|
|
||||||
if (err) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
return connection.commit(err => {
|
|
||||||
if (err) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
|
|
||||||
}
|
|
||||||
connection.release();
|
|
||||||
return callback(null, true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.delete = (listId, cid, callback) => {
|
|
||||||
listId = Number(listId) || 0;
|
|
||||||
cid = (cid || '').toString().trim();
|
|
||||||
|
|
||||||
if (listId < 1) {
|
|
||||||
return callback(new Error(_('Missing List ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!cid) {
|
|
||||||
return callback(new Error(_('Missing subscription ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('SELECT id, email, status FROM `subscription__' + listId + '` WHERE cid=? LIMIT 1', [cid], (err, rows) => {
|
|
||||||
if (err) {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let subscription = rows && rows[0];
|
|
||||||
if (!subscription) {
|
|
||||||
connection.release();
|
|
||||||
return callback(null, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.beginTransaction(err => {
|
|
||||||
if (err) {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('DELETE FROM `subscription__' + listId + '` WHERE cid=? LIMIT 1', [cid], err => {
|
|
||||||
if (err) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (subscription.status !== Status.SUBSCRIBED) {
|
|
||||||
return connection.commit(err => {
|
|
||||||
if (err) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
|
|
||||||
}
|
|
||||||
connection.release();
|
|
||||||
return callback(null, subscription.email);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('UPDATE lists SET subscribers=subscribers-1 WHERE id=? LIMIT 1', [listId], err => {
|
|
||||||
if (err) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
|
|
||||||
}
|
|
||||||
connection.commit(err => {
|
|
||||||
if (err) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
|
|
||||||
}
|
|
||||||
connection.release();
|
|
||||||
return callback(null, subscription.email);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.createImport = (listId, type, path, size, delimiter, emailcheck, mapping, callback) => {
|
|
||||||
listId = Number(listId) || 0;
|
|
||||||
type = Number(type) || 0;
|
|
||||||
|
|
||||||
if (listId < 1) {
|
|
||||||
return callback(new Error('Missing List ID'));
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
let query = 'INSERT INTO importer (`list`, `type`, `path`, `size`, `delimiter`, `emailcheck`, `mapping`) VALUES(?,?,?,?,?,?,?)';
|
|
||||||
connection.query(query, [listId, type, path, size, delimiter, emailcheck, JSON.stringify(mapping)], (err, result) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, result && result.insertId || false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.updateImport = (listId, importId, data, callback) => {
|
|
||||||
listId = Number(listId) || 0;
|
|
||||||
importId = Number(importId) || 0;
|
|
||||||
|
|
||||||
if (listId < 1) {
|
|
||||||
return callback(new Error(_('Missing List ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (importId < 1) {
|
|
||||||
return callback(new Error(_('Missing Import ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
let keys = [];
|
|
||||||
let values = [];
|
|
||||||
|
|
||||||
let allowedKeys = ['type', 'path', 'size', 'delimiter', 'status', 'error', 'processed', 'new', 'failed', 'mapping', 'finished'];
|
|
||||||
Object.keys(data).forEach(key => {
|
|
||||||
let value = data[key];
|
|
||||||
key = tools.toDbKey(key);
|
|
||||||
if (allowedKeys.indexOf(key) >= 0) {
|
|
||||||
keys.push(key);
|
|
||||||
values.push(value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
let query = 'UPDATE importer SET ' + keys.map(key => '`' + key + '`=?') + ' WHERE id=? AND list=? LIMIT 1';
|
|
||||||
connection.query(query, values.concat([importId, listId]), (err, result) => {
|
|
||||||
if (err) {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let affected = result && result.affectedRows || false;
|
|
||||||
|
|
||||||
if (data.failed === 0) {
|
|
||||||
// remove entries from import_failed table
|
|
||||||
let query = 'DELETE FROM `import_failed` WHERE `import`=?';
|
|
||||||
connection.query(query, [importId], () => {
|
|
||||||
connection.release();
|
|
||||||
return callback(null, affected);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
connection.release();
|
|
||||||
return callback(null, affected);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.getImport = (listId, importId, callback) => {
|
|
||||||
listId = Number(listId) || 0;
|
|
||||||
importId = Number(importId) || 0;
|
|
||||||
|
|
||||||
if (listId < 1) {
|
|
||||||
return callback(new Error(_('Missing List ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (importId < 1) {
|
|
||||||
return callback(new Error(_('Missing Import ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
let query = 'SELECT * FROM importer WHERE id=? AND list=? LIMIT 1';
|
|
||||||
connection.query(query, [importId, listId], (err, rows) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rows || !rows.length) {
|
|
||||||
return callback(null, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let importer = tools.convertKeys(rows[0]);
|
|
||||||
try {
|
|
||||||
importer.mapping = JSON.parse(importer.mapping);
|
|
||||||
} catch (E) {
|
|
||||||
importer.mapping = {
|
|
||||||
columns: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback(null, importer);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.getFailedImports = (importId, callback) => {
|
|
||||||
importId = Number(importId) || 0;
|
|
||||||
|
|
||||||
if (importId < 1) {
|
|
||||||
return callback(new Error(_('Missing Import ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
let query = 'SELECT * FROM import_failed WHERE import=? LIMIT 1000';
|
|
||||||
connection.query(query, [importId], (err, rows) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback(null, (rows || []).map(tools.convertKeys));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.listImports = (listId, callback) => {
|
|
||||||
listId = Number(listId) || 0;
|
|
||||||
|
|
||||||
if (listId < 1) {
|
|
||||||
return callback(new Error(_('Missing List ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
let query = 'SELECT * FROM importer WHERE list=? AND status > 0 ORDER BY id DESC';
|
|
||||||
connection.query(query, [listId], (err, rows) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rows || !rows.length) {
|
|
||||||
return callback(null, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
let imports = rows.map(row => {
|
|
||||||
let importer = tools.convertKeys(row);
|
|
||||||
try {
|
|
||||||
importer.mapping = JSON.parse(importer.mapping);
|
|
||||||
} catch (E) {
|
|
||||||
importer.mapping = {
|
|
||||||
columns: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return importer;
|
|
||||||
});
|
|
||||||
|
|
||||||
return callback(null, imports);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Performs checks before update of an address. This includes finding the existing subscriber, validating the new email
|
|
||||||
and checking whether the new email does not conflict with other subscribers.
|
|
||||||
*/
|
|
||||||
module.exports.updateAddressCheck = (list, cid, emailNew, ip, callback) => {
|
|
||||||
cid = (cid || '').toString().trim();
|
|
||||||
|
|
||||||
if (!list || !list.id) {
|
|
||||||
return callback(new Error(_('Missing List ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!cid) {
|
|
||||||
return callback(new Error(_('Missing subscription ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
tools.validateEmail(emailNew, false, err => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = 'SELECT * FROM `subscription__' + list.id + '` WHERE `cid`=? AND `status`=' + Status.SUBSCRIBED + ' LIMIT 1';
|
|
||||||
let args = [cid];
|
|
||||||
connection.query(query, args, (err, rows) => {
|
|
||||||
if (err) {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
if (!rows || !rows.length) {
|
|
||||||
connection.release();
|
|
||||||
return callback(new Error(_('Unknown subscription ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rows[0].email === emailNew) {
|
|
||||||
connection.release();
|
|
||||||
return callback(new Error(_('Nothing seems to be changed')));
|
|
||||||
}
|
|
||||||
|
|
||||||
let old = rows[0];
|
|
||||||
|
|
||||||
let query = 'SELECT `id` FROM `subscription__' + list.id + '` WHERE `email`=? AND `cid`<>? AND `status`=' + Status.SUBSCRIBED + ' LIMIT 1';
|
|
||||||
let args = [emailNew, cid];
|
|
||||||
connection.query(query, args, (err, rows) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rows && rows.length > 0) {
|
|
||||||
return callback(null, old, false);
|
|
||||||
} else {
|
|
||||||
return callback(null, old, true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
Updates address in subscription__xxx
|
|
||||||
*/
|
|
||||||
module.exports.updateAddress = (listId, subscriptionId, emailNew, callback) => {
|
|
||||||
// update email address instead of adding new
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
connection.beginTransaction(err => {
|
|
||||||
if (err) {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = 'SELECT `id` FROM `subscription__' + listId + '` WHERE `email`=? AND `id`<>? AND `status`=' + Status.SUBSCRIBED + ' LIMIT 1';
|
|
||||||
let args = [emailNew, subscriptionId];
|
|
||||||
connection.query(query, args, (err, rows) => {
|
|
||||||
if (err) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rows && rows.length > 0) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(new Error(_('Email address already registered'))));
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = 'DELETE FROM `subscription__' + listId + '` WHERE `email`=? AND `id`<>?';
|
|
||||||
let args = [emailNew, subscriptionId];
|
|
||||||
connection.query(query, args, err => {
|
|
||||||
if (err) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = 'UPDATE `subscription__' + listId + '` SET `email`=? WHERE `id`=? AND `status`=' + Status.SUBSCRIBED + ' LIMIT 1';
|
|
||||||
let args = [emailNew, subscriptionId];
|
|
||||||
connection.query(query, args, (err, result) => {
|
|
||||||
if (err) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result || !result.affectedRows) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(new Error(_('Subscription not found in this list'))));
|
|
||||||
}
|
|
||||||
|
|
||||||
return connection.commit(err => {
|
|
||||||
if (err) {
|
|
||||||
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
|
|
||||||
}
|
|
||||||
connection.release();
|
|
||||||
|
|
||||||
return callback();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.getUnsubscriptionMode = (list, subscriptionId) => list.unsubscriptionMode; // eslint-disable-line no-unused-vars
|
|
||||||
// TODO: Once the unsubscription mode is customizable per segment, then this will be a good place to process it.
|
|
|
@ -1,176 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let db = require('../db');
|
|
||||||
let tools = require('../tools');
|
|
||||||
let _ = require('../translate')._;
|
|
||||||
let tableHelpers = require('../table-helpers');
|
|
||||||
|
|
||||||
let allowedKeys = ['description', 'editor_name', 'editor_data', 'html', 'text'];
|
|
||||||
|
|
||||||
module.exports.list = (start, limit, callback) => {
|
|
||||||
tableHelpers.list('templates', ['*'], 'name', null, start, limit, callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.filter = (request, parent, callback) => {
|
|
||||||
tableHelpers.filter('templates', ['*'], request, ['#', 'name', 'description'], ['name'], 'name ASC', null, callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.quicklist = callback => {
|
|
||||||
tableHelpers.quicklist('templates', ['id', 'name'], 'name', callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.get = (id, callback) => {
|
|
||||||
id = Number(id) || 0;
|
|
||||||
|
|
||||||
if (id < 1) {
|
|
||||||
return callback(new Error(_('Missing Template ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('SELECT * FROM templates WHERE id=?', [id], (err, rows) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rows || !rows.length) {
|
|
||||||
return callback(null, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let template = tools.convertKeys(rows[0]);
|
|
||||||
return callback(null, template);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.create = (template, callback) => {
|
|
||||||
|
|
||||||
let data = tools.convertKeys(template);
|
|
||||||
|
|
||||||
if (!(data.name || '').toString().trim()) {
|
|
||||||
return callback(new Error(_('Template Name must be set')));
|
|
||||||
}
|
|
||||||
|
|
||||||
let name = (template.name || '').toString().trim();
|
|
||||||
|
|
||||||
let keys = ['name'];
|
|
||||||
let values = [name];
|
|
||||||
|
|
||||||
Object.keys(template).forEach(key => {
|
|
||||||
let value = template[key];
|
|
||||||
key = tools.toDbKey(key);
|
|
||||||
if (!allowedKeys.includes(key)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
value = value.trim();
|
|
||||||
if (key === 'description') {
|
|
||||||
value = tools.purifyHTML(value);
|
|
||||||
}
|
|
||||||
keys.push(key);
|
|
||||||
values.push(value);
|
|
||||||
});
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = 'INSERT INTO templates (' + keys.join(', ') + ') VALUES (' + values.map(() => '?').join(',') + ')';
|
|
||||||
connection.query(query, values, (err, result) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let templateId = result && result.insertId || false;
|
|
||||||
return callback(null, templateId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.update = (id, updates, callback) => {
|
|
||||||
updates = updates || {};
|
|
||||||
id = Number(id) || 0;
|
|
||||||
|
|
||||||
let data = tools.convertKeys(updates);
|
|
||||||
|
|
||||||
if (id < 1) {
|
|
||||||
return callback(new Error(_('Missing Template ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(data.name || '').toString().trim()) {
|
|
||||||
return callback(new Error(_('Template Name must be set')));
|
|
||||||
}
|
|
||||||
|
|
||||||
let name = (updates.name || '').toString().trim();
|
|
||||||
let keys = ['name'];
|
|
||||||
let values = [name];
|
|
||||||
|
|
||||||
Object.keys(updates).forEach(key => {
|
|
||||||
let value = updates[key].trim();
|
|
||||||
key = tools.toDbKey(key);
|
|
||||||
if (key === 'description') {
|
|
||||||
value = tools.purifyHTML(value);
|
|
||||||
}
|
|
||||||
if (allowedKeys.indexOf(key) >= 0) {
|
|
||||||
keys.push(key);
|
|
||||||
values.push(value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
values.push(id);
|
|
||||||
|
|
||||||
connection.query('UPDATE templates SET ' + keys.map(key => key + '=?').join(', ') + ' WHERE id=? LIMIT 1', values, (err, result) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, result && result.affectedRows || false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.duplicate = (id, callback) => module.exports.get(id, (err, template) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
if (!template) {
|
|
||||||
return callback(new Error(_('Template does not exist')));
|
|
||||||
}
|
|
||||||
template.name = template.name + ' Copy';
|
|
||||||
return module.exports.create(template, callback);
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.delete = (id, callback) => {
|
|
||||||
id = Number(id) || 0;
|
|
||||||
|
|
||||||
if (id < 1) {
|
|
||||||
return callback(new Error(_('Missing Template ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('DELETE FROM templates WHERE id=? LIMIT 1', id, (err, result) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let affected = result && result.affectedRows || 0;
|
|
||||||
|
|
||||||
return callback(null, affected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,427 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let log = require('npmlog');
|
|
||||||
let tools = require('../tools');
|
|
||||||
let db = require('../db');
|
|
||||||
let lists = require('./lists');
|
|
||||||
let segments = require('./segments');
|
|
||||||
let util = require('util');
|
|
||||||
let _ = require('../translate')._;
|
|
||||||
let tableHelpers = require('../table-helpers');
|
|
||||||
|
|
||||||
module.exports.defaultColumns = [{
|
|
||||||
column: 'created',
|
|
||||||
name: _('Sign up date'),
|
|
||||||
type: 'date'
|
|
||||||
}, {
|
|
||||||
column: 'latest_open',
|
|
||||||
name: _('Latest open'),
|
|
||||||
type: 'date'
|
|
||||||
}, {
|
|
||||||
column: 'latest_click',
|
|
||||||
name: _('Latest click'),
|
|
||||||
type: 'date'
|
|
||||||
}];
|
|
||||||
|
|
||||||
module.exports.defaultCampaignEvents = [{
|
|
||||||
option: 'delivered',
|
|
||||||
name: _('Delivered')
|
|
||||||
}, {
|
|
||||||
option: 'opened',
|
|
||||||
name: _('Has Opened')
|
|
||||||
}, {
|
|
||||||
option: 'clicked',
|
|
||||||
name: _('Has Clicked')
|
|
||||||
}, {
|
|
||||||
option: 'not_opened',
|
|
||||||
name: _('Not Opened')
|
|
||||||
}, {
|
|
||||||
option: 'not_clicked',
|
|
||||||
name: _('Not Clicked')
|
|
||||||
}];
|
|
||||||
|
|
||||||
let defaultColumnMap = {};
|
|
||||||
let defaultEventMap = {};
|
|
||||||
module.exports.defaultColumns.forEach(col => defaultColumnMap[col.column] = col.name);
|
|
||||||
module.exports.defaultCampaignEvents.forEach(evt => defaultEventMap[evt.option] = evt.name);
|
|
||||||
|
|
||||||
module.exports.list = callback => {
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let tableFields = [
|
|
||||||
'`triggers`.`id` AS `id`',
|
|
||||||
'`triggers`.`name` AS `name`',
|
|
||||||
'`triggers`.`description` AS `description`',
|
|
||||||
'`triggers`.`enabled` AS `enabled`',
|
|
||||||
'`triggers`.`list` AS `list`',
|
|
||||||
'`triggers`.`segment` AS `segment`',
|
|
||||||
'`lists`.`name` AS `list_name`',
|
|
||||||
'`segments`.`name` AS `segment_name`',
|
|
||||||
'`source`.`id` AS `source_campaign`',
|
|
||||||
'`source`.`name` AS `source_campaign_name`',
|
|
||||||
'`dest`.`id` AS `dest_campaign`',
|
|
||||||
'`dest`.`name` AS `dest_campaign_name`',
|
|
||||||
'`triggers`.`count` AS `count`',
|
|
||||||
'`custom_fields`.`id` AS `column_id`',
|
|
||||||
'`triggers`.`column` AS `column`',
|
|
||||||
'`custom_fields`.`name` AS `column_name`',
|
|
||||||
'`triggers`.`rule` AS `rule`',
|
|
||||||
'`triggers`.`seconds` AS `seconds`',
|
|
||||||
'`triggers`.`created` AS `created`'
|
|
||||||
];
|
|
||||||
|
|
||||||
let query = 'SELECT ' + tableFields.join(', ') + ' FROM `triggers` LEFT JOIN `campaigns` `source` ON `source`.`id`=`triggers`.`source_campaign` LEFT JOIN `campaigns` `dest` ON `dest`.`id`=`triggers`.`dest_campaign` LEFT JOIN `lists` ON `lists`.`id`=`triggers`.`list` LEFT JOIN `segments` ON `segments`.`id`=`triggers`.`segment` LEFT JOIN `custom_fields` ON `custom_fields`.`list` = `triggers`.`list` AND `custom_fields`.`column`=`triggers`.`column` ORDER BY `triggers`.`name`';
|
|
||||||
connection.query(query, (err, rows) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let triggers = (rows || []).map(tools.convertKeys).map(row => {
|
|
||||||
if (row.rule === 'subscription' && row.column && !row.columnName) {
|
|
||||||
row.columnName = defaultColumnMap[row.column];
|
|
||||||
}
|
|
||||||
|
|
||||||
let days = Math.round(row.seconds / (24 * 3600));
|
|
||||||
row.formatted = util.format('%s days after %s', days, row.rule === 'subscription' ? row.columnName : (util.format('%s <a href="/campaigns/view/%s">%s</a>', defaultEventMap[row.column], row.sourceCampaign, row.sourceCampaignName)));
|
|
||||||
|
|
||||||
return row;
|
|
||||||
});
|
|
||||||
return callback(null, triggers);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.getQuery = (id, callback) => {
|
|
||||||
module.exports.get(id, (err, trigger) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let limit = 300;
|
|
||||||
|
|
||||||
// time..NOW..time + 24h, 24 hour window after trigger target to detect it
|
|
||||||
//We need a 24 hour window for triggers as the format for dates added via the API are stored as 00:00:00
|
|
||||||
let treshold = 3600 * 24;
|
|
||||||
|
|
||||||
let intervalQuery = (column, seconds, treshold) => column + ' <= NOW() - INTERVAL ' + seconds + ' SECOND AND ' + column + ' >= NOW() - INTERVAL ' + (treshold + seconds) + ' SECOND';
|
|
||||||
|
|
||||||
|
|
||||||
let getSegmentQuery = (segmentId, next) => {
|
|
||||||
segmentId = Number(segmentId);
|
|
||||||
if (!segmentId) {
|
|
||||||
return next(null, {
|
|
||||||
where: '',
|
|
||||||
values: []
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
segments.getQuery(segmentId, 'subscription', next);
|
|
||||||
};
|
|
||||||
|
|
||||||
getSegmentQuery(trigger.segment, (err, queryData) => {
|
|
||||||
if (err) {
|
|
||||||
log.err('Triggers', err);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = false;
|
|
||||||
let querySegmentSubscription = '';
|
|
||||||
let querySegmentTriggertable = '';
|
|
||||||
if (trigger.segment > 0)
|
|
||||||
{
|
|
||||||
querySegmentSubscription = (queryData.where ? ' AND (' + queryData.where + ')' : '');
|
|
||||||
querySegmentTriggertable = ' AND triggertable.`segment` = ' + trigger.segment;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (trigger.rule) {
|
|
||||||
case 'subscription':
|
|
||||||
query = 'SELECT id FROM `subscription__' + trigger.list + '` subscription WHERE status=1 AND ' + intervalQuery('`' + trigger.column + '`', trigger.seconds, treshold) + ' ' + querySegmentSubscription + ' AND id NOT IN (SELECT subscription FROM `trigger__' + id + '` triggertable WHERE triggertable.`list` = ' + trigger.list + ' ' + querySegmentTriggertable + ' AND triggertable.`subscription` = subscription.`id`) LIMIT ' + limit;
|
|
||||||
break;
|
|
||||||
case 'campaign':
|
|
||||||
switch (trigger.column) {
|
|
||||||
case 'delivered':
|
|
||||||
query = 'SELECT subscription.id AS id FROM `subscription__' + trigger.list + '` subscription LEFT JOIN `campaign__' + trigger.sourceCampaign + '` campaign ON campaign.list=' + trigger.list + ' AND subscription.id=campaign.subscription WHERE campaign.status=1 AND ' + intervalQuery('`campaign`.`created`', trigger.seconds, treshold) + ' ' + querySegmentSubscription + ' AND subscription.id NOT IN (SELECT subscription FROM `trigger__' + id + '` triggertable WHERE triggertable.`list` = ' + trigger.list + ' ' + querySegmentTriggertable + ' AND triggertable.`subscription` = subscription.`id`) LIMIT ' + limit;
|
|
||||||
break;
|
|
||||||
case 'not_clicked':
|
|
||||||
query = 'SELECT subscription.id AS id FROM `subscription__' + trigger.list + '` subscription LEFT JOIN `campaign__' + trigger.sourceCampaign + '` campaign ON campaign.list=' + trigger.list + ' AND subscription.id=campaign.subscription LEFT JOIN `campaign_tracker__' + trigger.sourceCampaign + '` tracker ON tracker.list=campaign.list AND tracker.subscriber=subscription.id AND tracker.link=0 WHERE campaign.status=1 AND ' + intervalQuery('`campaign`.`created`', trigger.seconds, treshold) + ' AND tracker.created IS NULL ' + querySegmentSubscription + ' AND subscription.id NOT IN (SELECT subscription FROM `trigger__' + id + '` triggertable WHERE triggertable.`list` = ' + trigger.list + ' ' + querySegmentTriggertable + ' AND triggertable.`subscription` = subscription.`id`) LIMIT ' + limit;
|
|
||||||
break;
|
|
||||||
case 'not_opened':
|
|
||||||
query = 'SELECT subscription.id AS id FROM `subscription__' + trigger.list + '` subscription LEFT JOIN `campaign__' + trigger.sourceCampaign + '` campaign ON campaign.list=' + trigger.list + ' AND subscription.id=campaign.subscription LEFT JOIN `campaign_tracker__' + trigger.sourceCampaign + '` tracker ON tracker.list=campaign.list AND tracker.subscriber=subscription.id AND tracker.link=-1 WHERE campaign.status=1 AND ' + intervalQuery('`campaign`.`created`', trigger.seconds, treshold) + ' AND tracker.created IS NULL ' + querySegmentSubscription + ' AND subscription.id NOT IN (SELECT subscription FROM `trigger__' + id + '` triggertable WHERE triggertable.`list` = ' + trigger.list + ' ' + querySegmentTriggertable + ' AND triggertable.`subscription` = subscription.`id`) LIMIT ' + limit;
|
|
||||||
break;
|
|
||||||
case 'clicked':
|
|
||||||
query = 'SELECT subscription.id AS id FROM `subscription__' + trigger.list + '` subscription LEFT JOIN `campaign__' + trigger.sourceCampaign + '` campaign ON campaign.list=' + trigger.list + ' AND subscription.id=campaign.subscription LEFT JOIN `campaign_tracker__' + trigger.sourceCampaign + '` tracker ON tracker.list=campaign.list AND tracker.subscriber=subscription.id AND tracker.link=0 WHERE campaign.status=1 AND ' + intervalQuery('`tracker`.`created`', trigger.seconds, treshold) + ' ' + querySegmentSubscription + ' AND subscription.id NOT IN (SELECT subscription FROM `trigger__' + id + '` triggertable WHERE triggertable.`list` = ' + trigger.list + ' ' + querySegmentTriggertable + ' AND triggertable.`subscription` = subscription.`id`) LIMIT ' + limit;
|
|
||||||
break;
|
|
||||||
case 'opened':
|
|
||||||
query = 'SELECT subscription.id AS id FROM `subscription__' + trigger.list + '` subscription LEFT JOIN `campaign__' + trigger.sourceCampaign + '` campaign ON campaign.list=' + trigger.list + ' AND subscription.id=campaign.subscription LEFT JOIN `campaign_tracker__' + trigger.sourceCampaign + '` tracker ON tracker.list=campaign.list AND tracker.subscriber=subscription.id AND tracker.link=-1 WHERE campaign.status=1 AND ' + intervalQuery('`tracker`.`created`', trigger.seconds, treshold) + ' ' + querySegmentSubscription + ' AND subscription.id NOT IN (SELECT subscription FROM `trigger__' + id + '` triggertable WHERE triggertable.`list` = ' + trigger.list + ' ' + querySegmentTriggertable + ' AND triggertable.`subscription` = subscription.`id`) LIMIT ' + limit;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trigger.segment > 0) {
|
|
||||||
let values = queryData.values.concat([trigger.list, trigger.segment]);
|
|
||||||
for (let i = 0; i < values.length; i++) {
|
|
||||||
query = query.replace('?', db.escape(values[i]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(null, query);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.get = (id, callback) => {
|
|
||||||
id = Number(id) || 0;
|
|
||||||
|
|
||||||
if (id < 1) {
|
|
||||||
return callback(new Error('Missing Trigger ID'));
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('SELECT * FROM triggers WHERE id=?', [id], (err, rows) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rows || !rows.length) {
|
|
||||||
return callback(null, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let trigger = tools.convertKeys(rows[0]);
|
|
||||||
return callback(null, trigger);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.create = (trigger, callback) => {
|
|
||||||
|
|
||||||
trigger = tools.convertKeys(trigger);
|
|
||||||
let name = (trigger.name || '').toString().trim();
|
|
||||||
let description = (trigger.description || '').toString().trim();
|
|
||||||
let listId = Number(trigger.list) || 0;
|
|
||||||
let segmentId = Number(trigger.segmentId) || 0;
|
|
||||||
let seconds = (Number(trigger.days) || 0) * 24 * 3600;
|
|
||||||
let rule = (trigger.rule || '').toString().toLowerCase().trim();
|
|
||||||
let destCampaign = Number(trigger.destCampaign) || 0;
|
|
||||||
let sourceCampaign = null;
|
|
||||||
let column;
|
|
||||||
|
|
||||||
if (!listId) {
|
|
||||||
return callback(new Error(_('Missing or invalid list ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (seconds < 0) {
|
|
||||||
return callback(new Error(_('Days in the past are not allowed')));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rule || ['campaign', 'subscription'].indexOf(rule) < 0) {
|
|
||||||
return callback(new Error(_('Missing or invalid trigger rule')));
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (rule) {
|
|
||||||
case 'subscription':
|
|
||||||
column = (trigger.column || '').toString().toLowerCase().trim();
|
|
||||||
if (!column) {
|
|
||||||
return callback(new Error(_('Invalid subscription configuration')));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'campaign':
|
|
||||||
column = (trigger.campaignOption || '').toString().toLowerCase().trim();
|
|
||||||
sourceCampaign = Number(trigger.sourceCampaign) || 0;
|
|
||||||
if (!column || !sourceCampaign) {
|
|
||||||
return callback(new Error(_('Invalid campaign configuration')));
|
|
||||||
}
|
|
||||||
if (sourceCampaign === destCampaign) {
|
|
||||||
return callback(new Error(_('A campaing can not be a target for itself')));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return callback(new Error(_('Missing or invalid trigger rule')));
|
|
||||||
}
|
|
||||||
|
|
||||||
lists.get(listId, (err, list) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
if (!list) {
|
|
||||||
return callback(new Error(_('Missing or invalid list ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let keys = ['name', 'description', 'list', 'segment', 'source_campaign', 'rule', 'column', 'seconds', 'dest_campaign', 'last_check'];
|
|
||||||
let values = [name, description, list.id, segmentId, sourceCampaign, rule, column, seconds, destCampaign];
|
|
||||||
|
|
||||||
let query = 'INSERT INTO `triggers` (`' + keys.join('`, `') + '`) VALUES (' + values.map(() => '?').join(', ') + ', NOW())';
|
|
||||||
|
|
||||||
connection.query(query, values, (err, result) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let id = result && result.insertId;
|
|
||||||
if (!id) {
|
|
||||||
return callback(new Error(_('Could not store trigger row')));
|
|
||||||
}
|
|
||||||
|
|
||||||
createTriggerTable(id, err => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, id);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.update = (id, trigger, callback) => {
|
|
||||||
id = Number(id) || 0;
|
|
||||||
if (id < 1) {
|
|
||||||
return callback(new Error(_('Missing or invalid Trigger ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
trigger = tools.convertKeys(trigger);
|
|
||||||
let name = (trigger.name || '').toString().trim();
|
|
||||||
let description = (trigger.description || '').toString().trim();
|
|
||||||
let enabled = trigger.enabled ? 1 : 0;
|
|
||||||
let seconds = (Number(trigger.days) || 0) * 24 * 3600;
|
|
||||||
let rule = (trigger.rule || '').toString().toLowerCase().trim();
|
|
||||||
let destCampaign = Number(trigger.destCampaign) || 0;
|
|
||||||
let sourceCampaign = null;
|
|
||||||
let column;
|
|
||||||
|
|
||||||
if (seconds < 0) {
|
|
||||||
return callback(new Error(_('Days in the past are not allowed')));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rule || ['campaign', 'subscription'].indexOf(rule) < 0) {
|
|
||||||
return callback(new Error(_('Missing or invalid trigger rule')));
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (rule) {
|
|
||||||
case 'subscription':
|
|
||||||
column = (trigger.column || '').toString().toLowerCase().trim();
|
|
||||||
if (!column) {
|
|
||||||
return callback(new Error(_('Invalid subscription configuration')));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'campaign':
|
|
||||||
column = (trigger.campaignOption || '').toString().toLowerCase().trim();
|
|
||||||
sourceCampaign = Number(trigger.sourceCampaign) || 0;
|
|
||||||
if (!column || !sourceCampaign) {
|
|
||||||
return callback(new Error(_('Invalid campaign configuration')));
|
|
||||||
}
|
|
||||||
if (sourceCampaign === destCampaign) {
|
|
||||||
return callback(new Error(_('A campaing can not be a target for itself')));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return callback(new Error(_('Missing or invalid trigger rule')));
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let keys = ['name', 'description', 'enabled', 'source_campaign', 'rule', 'column', 'seconds', 'dest_campaign'];
|
|
||||||
let values = [name, description, enabled, sourceCampaign, rule, column, seconds, destCampaign];
|
|
||||||
|
|
||||||
let query = 'UPDATE `triggers` SET ' + keys.map(key => '`' + key + '`=?').join(', ') + ' WHERE `id`=? LIMIT 1';
|
|
||||||
|
|
||||||
connection.query(query, values.concat(id), (err, result) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback(null, result && result.affectedRows);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.delete = (id, callback) => {
|
|
||||||
id = Number(id) || 0;
|
|
||||||
|
|
||||||
if (id < 1) {
|
|
||||||
return callback(new Error(_('Missing Trigger ID')));
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('DELETE FROM triggers WHERE id=? LIMIT 1', [id], (err, result) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let affected = result && result.affectedRows || 0;
|
|
||||||
removeTriggerTable(id, err => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, affected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.filterSubscribers = (trigger, request, columns, callback) => {
|
|
||||||
let queryData = {
|
|
||||||
where: 'trigger__' + trigger.id + '.list=?',
|
|
||||||
values: [trigger.list]
|
|
||||||
};
|
|
||||||
|
|
||||||
tableHelpers.filter('subscription__' + trigger.list + ' JOIN trigger__' + trigger.id + ' ON trigger__' + trigger.id + '.subscription=subscription__' + trigger.list + '.id', ['*'], request, columns, ['email', 'first_name', 'last_name'], 'email ASC', queryData, callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
function createTriggerTable(id, callback) {
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
let query = 'CREATE TABLE `trigger__' + id + '` LIKE `trigger`';
|
|
||||||
connection.query(query, err => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeTriggerTable(id, callback) {
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
let query = 'DROP TABLE IF EXISTS `trigger__' + id + '`';
|
|
||||||
connection.query(query, err => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const fork = require('child_process').fork;
|
|
||||||
|
|
||||||
const config = require('config');
|
|
||||||
const log = require('npmlog');
|
|
||||||
const workers = new Set();
|
|
||||||
|
|
||||||
function spawn(callback) {
|
|
||||||
let processes = Math.max(Number(config.queue.processes) || 1, 1);
|
|
||||||
let spawned = 0;
|
|
||||||
let returned = false;
|
|
||||||
|
|
||||||
if (processes > 1 && !config.redis.enabled) {
|
|
||||||
log.error('Queue', '%s processes requested but Redis is not enabled, spawning 1 process', processes);
|
|
||||||
processes = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let spawnSender = function () {
|
|
||||||
if (spawned >= processes) {
|
|
||||||
if (!returned) {
|
|
||||||
returned = true;
|
|
||||||
return callback();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let child = fork(__dirname + '/../services/sender.js', []);
|
|
||||||
let pid = child.pid;
|
|
||||||
workers.add(child);
|
|
||||||
|
|
||||||
child.on('close', (code, signal) => {
|
|
||||||
spawned--;
|
|
||||||
workers.delete(child);
|
|
||||||
log.error('Child', 'Sender process %s exited with %s', pid, code || signal);
|
|
||||||
// Respawn after 5 seconds
|
|
||||||
setTimeout(() => spawnSender(), 5 * 1000).unref();
|
|
||||||
});
|
|
||||||
|
|
||||||
spawned++;
|
|
||||||
setImmediate(spawnSender);
|
|
||||||
};
|
|
||||||
|
|
||||||
spawnSender();
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
workers,
|
|
||||||
spawn
|
|
||||||
};
|
|
|
@ -1,134 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const db = require('./db');
|
|
||||||
const tools = require('./tools');
|
|
||||||
|
|
||||||
module.exports.list = (source, fields, orderBy, queryData, start, limit, callback) => {
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let limitQuery = '';
|
|
||||||
let limitValues = [];
|
|
||||||
if (limit) {
|
|
||||||
limitQuery = ' LIMIT ?';
|
|
||||||
limitValues.push(limit);
|
|
||||||
|
|
||||||
if (start) {
|
|
||||||
limitQuery += ' OFFSET ?';
|
|
||||||
limitValues.push(start);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let whereClause = '';
|
|
||||||
let whereValues = [];
|
|
||||||
|
|
||||||
if (queryData) {
|
|
||||||
whereClause = ' WHERE ' + queryData.where;
|
|
||||||
whereValues = queryData.values;
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('SELECT SQL_CALC_FOUND_ROWS ' + fields.join(', ') + ' FROM ' + source + whereClause + ' ORDER BY ' + orderBy + ' DESC' + limitQuery, whereValues.concat(limitValues), (err, rows) => {
|
|
||||||
if (err) {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
connection.query('SELECT FOUND_ROWS() AS total', (err, total) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, rows, total && total[0] && total[0].total);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.quicklist = (source, fields, orderBy, callback) => {
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query('SELECT ' + fields.join(', ') + ' FROM ' + source + ' ORDER BY ' + orderBy + ' LIMIT 1000', (err, rows) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, (rows || []).map(tools.convertKeys));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.filter = (source, fields, request, columns, searchFields, defaultOrdering, queryData, callback) => {
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = 'SELECT COUNT(*) AS total FROM ' + source;
|
|
||||||
let values = [];
|
|
||||||
|
|
||||||
if (queryData) {
|
|
||||||
query += ' WHERE ' + queryData.where;
|
|
||||||
values = values.concat(queryData.values || []);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query(query, values, (err, total) => {
|
|
||||||
if (err) {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
total = total && total[0] && total[0].total || 0;
|
|
||||||
|
|
||||||
let ordering = [];
|
|
||||||
|
|
||||||
if (request.order && request.order.length) {
|
|
||||||
|
|
||||||
request.order.forEach(order => {
|
|
||||||
let orderField = columns[Number(order.column)];
|
|
||||||
let orderDirection = (order.dir || '').toString().toLowerCase() === 'desc' ? 'DESC' : 'ASC';
|
|
||||||
if (orderField) {
|
|
||||||
ordering.push(orderField + ' ' + orderDirection);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ordering.length) {
|
|
||||||
ordering.push(defaultOrdering);
|
|
||||||
}
|
|
||||||
|
|
||||||
let searchWhere = '';
|
|
||||||
let searchArgs = [];
|
|
||||||
|
|
||||||
if (request.search && request.search.value) {
|
|
||||||
let searchVal = '%' + request.search.value.replace(/\\/g, '\\\\').replace(/([%_])/g, '\\$1') + '%';
|
|
||||||
|
|
||||||
searchWhere = searchFields.map(field => field + ' LIKE ?').join(' OR ');
|
|
||||||
searchArgs = searchFields.map(() => searchVal);
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = 'SELECT SQL_CALC_FOUND_ROWS ' + fields.join(', ') + ' FROM ' + source +' WHERE ' + (searchWhere ? '(' + searchWhere + ')': '1') + (queryData ? ' AND (' + queryData.where + ')' : '') + ' ORDER BY ' + ordering.join(', ') + ' LIMIT ? OFFSET ?';
|
|
||||||
let args = searchArgs.concat(queryData ? queryData.values : []).concat([Number(request.length) || 50, Number(request.start) || 0]);
|
|
||||||
|
|
||||||
connection.query(query, args, (err, rows) => {
|
|
||||||
if (err) {
|
|
||||||
connection.release();
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
connection.query('SELECT FOUND_ROWS() AS total', (err, filteredTotal) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
rows = rows.map(row => tools.convertKeys(row));
|
|
||||||
|
|
||||||
filteredTotal = filteredTotal && filteredTotal[0] && filteredTotal[0].total || 0;
|
|
||||||
return callback(null, rows, total, filteredTotal);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,283 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const config = require('config');
|
|
||||||
let fs = require('fs');
|
|
||||||
let path = require('path');
|
|
||||||
let db = require('./db');
|
|
||||||
let slugify = require('slugify');
|
|
||||||
let Isemail = require('isemail');
|
|
||||||
let urllib = require('url');
|
|
||||||
let juice = require('juice');
|
|
||||||
let jsdom = require('jsdom');
|
|
||||||
let he = require('he');
|
|
||||||
let _ = require('./translate')._;
|
|
||||||
let util = require('util');
|
|
||||||
let createDOMPurify = require('dompurify');
|
|
||||||
let htmlToText = require('html-to-text');
|
|
||||||
|
|
||||||
let blockedUsers = ['abuse', 'admin', 'billing', 'compliance', 'devnull', 'dns', 'ftp', 'hostmaster', 'inoc', 'ispfeedback', 'ispsupport', 'listrequest', 'list', 'maildaemon', 'noc', 'noreply', 'noreply', 'null', 'phish', 'phishing', 'postmaster', 'privacy', 'registrar', 'root', 'security', 'spam', 'support', 'sysadmin', 'tech', 'undisclosedrecipients', 'unsubscribe', 'usenet', 'uucp', 'webmaster', 'www'];
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
queryParams,
|
|
||||||
createSlug,
|
|
||||||
updateMenu,
|
|
||||||
validateEmail,
|
|
||||||
formatMessage,
|
|
||||||
getMessageLinks,
|
|
||||||
prepareHtml,
|
|
||||||
purifyHTML,
|
|
||||||
mergeTemplateIntoLayout,
|
|
||||||
workers: new Set()
|
|
||||||
};
|
|
||||||
|
|
||||||
function queryParams(obj) {
|
|
||||||
return Object.keys(obj).
|
|
||||||
filter(key => key !== '_csrf').
|
|
||||||
map(key => encodeURIComponent(key) + '=' + encodeURIComponent(obj[key])).
|
|
||||||
join('&');
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSlug(table, name, callback) {
|
|
||||||
|
|
||||||
let baseSlug = slugify(name).trim().toLowerCase() || 'list';
|
|
||||||
let counter = 0;
|
|
||||||
|
|
||||||
if (baseSlug.length > 80) {
|
|
||||||
baseSlug = baseSlug.substr(0, 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let finalize = (err, slug) => {
|
|
||||||
connection.release();
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, slug);
|
|
||||||
};
|
|
||||||
|
|
||||||
let trySlug = () => {
|
|
||||||
let currentSlug = baseSlug + (counter === 0 ? '' : '-' + counter);
|
|
||||||
counter++;
|
|
||||||
connection.query('SELECT id FROM ' + table + ' WHERE slug=?', [currentSlug], (err, rows) => {
|
|
||||||
if (err) {
|
|
||||||
return finalize(err);
|
|
||||||
}
|
|
||||||
if (!rows || !rows.length) {
|
|
||||||
return finalize(null, currentSlug);
|
|
||||||
}
|
|
||||||
trySlug();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
trySlug();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME - remove once we fully manage the menu in the client
|
|
||||||
function updateMenu(res) {
|
|
||||||
if (!res.locals.menu) {
|
|
||||||
res.locals.menu = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
res.locals.menu.push({
|
|
||||||
title: _('Lists'),
|
|
||||||
url: '/lists',
|
|
||||||
key: 'lists'
|
|
||||||
}, {
|
|
||||||
title: _('Templates'),
|
|
||||||
url: '/templates',
|
|
||||||
key: 'templates'
|
|
||||||
}, {
|
|
||||||
title: _('Campaigns'),
|
|
||||||
url: '/campaigns',
|
|
||||||
key: 'campaigns'
|
|
||||||
}, {
|
|
||||||
title: _('Automation'),
|
|
||||||
url: '/triggers',
|
|
||||||
key: 'triggers'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (config.reports && config.reports.enabled === true) {
|
|
||||||
res.locals.menu.push({
|
|
||||||
title: _('Reports'),
|
|
||||||
url: '/reports',
|
|
||||||
key: 'reports'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME - either remove of delegate to validateEmail in tools-async (or vice-versa)
|
|
||||||
function validateEmail(address, checkBlocked, callback) {
|
|
||||||
let user = (address || '').toString().split('@').shift().toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
||||||
if (checkBlocked && blockedUsers.indexOf(user) >= 0) {
|
|
||||||
return callback(new Error(util.format(_('Blocked email address "%s"'), address)));
|
|
||||||
}
|
|
||||||
|
|
||||||
Isemail.validate(address, {
|
|
||||||
checkDNS: true,
|
|
||||||
errorLevel: 1
|
|
||||||
}, result => {
|
|
||||||
|
|
||||||
if (result !== 0) {
|
|
||||||
let message = util.format(_('Invalid email address "%s".'), address);
|
|
||||||
switch (result) {
|
|
||||||
case 5:
|
|
||||||
message += ' ' + _('MX record not found for domain');
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
message += ' ' + _('Address domain not found');
|
|
||||||
break;
|
|
||||||
case 12:
|
|
||||||
message += ' ' + _('Address domain name is required');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return callback(new Error(message));
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMessageLinks(serviceUrl, campaign, list, subscription) {
|
|
||||||
return {
|
|
||||||
LINK_UNSUBSCRIBE: urllib.resolve(serviceUrl, '/subscription/' + list.cid + '/unsubscribe/' + subscription.cid + '?c=' + campaign.cid),
|
|
||||||
LINK_PREFERENCES: urllib.resolve(serviceUrl, '/subscription/' + list.cid + '/manage/' + subscription.cid),
|
|
||||||
LINK_BROWSER: urllib.resolve(serviceUrl, '/archive/' + campaign.cid + '/' + list.cid + '/' + subscription.cid),
|
|
||||||
CAMPAIGN_ID: campaign.cid,
|
|
||||||
LIST_ID: list.cid,
|
|
||||||
SUBSCRIPTION_ID: subscription.cid
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatMessage(serviceUrl, campaign, list, subscription, message, filter, isHTML) {
|
|
||||||
filter = typeof filter === 'function' ? filter : (str => str);
|
|
||||||
|
|
||||||
let links = getMessageLinks(serviceUrl, campaign, list, subscription);
|
|
||||||
|
|
||||||
let getValue = key => {
|
|
||||||
key = (key || '').toString().toUpperCase().trim();
|
|
||||||
if (links.hasOwnProperty(key)) {
|
|
||||||
return links[key];
|
|
||||||
}
|
|
||||||
if (subscription.mergeTags.hasOwnProperty(key)) {
|
|
||||||
let value = (subscription.mergeTags[key] || '').toString();
|
|
||||||
let containsHTML = /<[a-z][\s\S]*>/.test(value);
|
|
||||||
return isHTML ? he.encode((containsHTML ? value : value.replace(/(?:\r\n|\r|\n)/g, '<br/>')), {
|
|
||||||
useNamedReferences: true,
|
|
||||||
allowUnsafeSymbols: true
|
|
||||||
}) : (containsHTML ? htmlToText.fromString(value) : value);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
return message.replace(/\[([a-z0-9_]+)(?:\/([^\]]+))?\]/ig, (match, identifier, fallback) => {
|
|
||||||
identifier = identifier.toUpperCase();
|
|
||||||
let value = getValue(identifier);
|
|
||||||
if (value === false) {
|
|
||||||
return match;
|
|
||||||
}
|
|
||||||
value = (value || fallback || '').trim();
|
|
||||||
return filter(value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function prepareHtml(html, callback) {
|
|
||||||
if (!(html || '').toString().trim()) {
|
|
||||||
return callback(null, false);
|
|
||||||
}
|
|
||||||
jsdom.env(false, false, {
|
|
||||||
html,
|
|
||||||
features: {
|
|
||||||
FetchExternalResources: false, // disables resource loading over HTTP / filesystem
|
|
||||||
ProcessExternalResources: false // do not execute JS within script blocks
|
|
||||||
}
|
|
||||||
}, (err, win) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let head = win.document.querySelector('head');
|
|
||||||
let hasCharsetTag = false;
|
|
||||||
let metaTags = win.document.querySelectorAll('meta');
|
|
||||||
if (metaTags) {
|
|
||||||
for (let i = 0; i < metaTags.length; i++) {
|
|
||||||
if (metaTags[i].hasAttribute('charset')) {
|
|
||||||
metaTags[i].setAttribute('charset', 'utf-8');
|
|
||||||
hasCharsetTag = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!hasCharsetTag) {
|
|
||||||
let charsetTag = win.document.createElement('meta');
|
|
||||||
charsetTag.setAttribute('charset', 'utf-8');
|
|
||||||
head.appendChild(charsetTag);
|
|
||||||
}
|
|
||||||
let preparedHtml = '<!doctype html><html>' + win.document.documentElement.innerHTML + '</html>';
|
|
||||||
|
|
||||||
return callback(null, juice(preparedHtml));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function purifyHTML(html) {
|
|
||||||
let win = jsdom.jsdom('', {
|
|
||||||
features: {
|
|
||||||
FetchExternalResources: false, // disables resource loading over HTTP / filesystem
|
|
||||||
ProcessExternalResources: false // do not execute JS within script blocks
|
|
||||||
}
|
|
||||||
}).defaultView;
|
|
||||||
let DOMPurify = createDOMPurify(win);
|
|
||||||
return DOMPurify.sanitize(html);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Simplify!
|
|
||||||
function mergeTemplateIntoLayout(template, layout, callback) {
|
|
||||||
|
|
||||||
layout = layout || '{{{body}}}';
|
|
||||||
|
|
||||||
let readFile = (relPath, callback) => {
|
|
||||||
fs.readFile(path.join(__dirname, '..', 'views', relPath), 'utf-8', (err, source) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
callback(null, source);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
let done = (template, layout) => {
|
|
||||||
let source = layout.replace(/\{\{\{body\}\}\}/g, template);
|
|
||||||
return callback(null, source);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (layout.endsWith('.hbs')) {
|
|
||||||
readFile(layout, (err, layout) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
// Please dont end your custom messages with .hbs ...
|
|
||||||
if (template.endsWith('.hbs')) {
|
|
||||||
readFile(template, (err, template) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return done(template, layout);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return done(template, layout);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (template.endsWith('.hbs')) {
|
|
||||||
readFile(template, (err, template) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return done(template, layout);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return done(template, layout);
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1 +0,0 @@
|
||||||
ace.define("ace/mode/plain_text",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/text_highlight_rules","ace/mode/behaviour"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./text_highlight_rules").TextHighlightRules,o=e("./behaviour").Behaviour,u=function(){this.HighlightRules=s,this.$behaviour=new o};r.inherits(u,i),function(){this.type="text",this.getNextLineIndent=function(e,t,n){return""},this.$id="ace/mode/plain_text"}.call(u.prototype),t.Mode=u})
|
|
|
@ -1 +0,0 @@
|
||||||
ace.define("ace/theme/chrome",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!1,t.cssClass="ace-chrome",t.cssText='.ace-chrome .ace_gutter {background: #ebebeb;color: #333;overflow : hidden;}.ace-chrome .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-chrome {background-color: #FFFFFF;color: black;}.ace-chrome .ace_cursor {color: black;}.ace-chrome .ace_invisible {color: rgb(191, 191, 191);}.ace-chrome .ace_constant.ace_buildin {color: rgb(88, 72, 246);}.ace-chrome .ace_constant.ace_language {color: rgb(88, 92, 246);}.ace-chrome .ace_constant.ace_library {color: rgb(6, 150, 14);}.ace-chrome .ace_invalid {background-color: rgb(153, 0, 0);color: white;}.ace-chrome .ace_fold {}.ace-chrome .ace_support.ace_function {color: rgb(60, 76, 114);}.ace-chrome .ace_support.ace_constant {color: rgb(6, 150, 14);}.ace-chrome .ace_support.ace_type,.ace-chrome .ace_support.ace_class.ace-chrome .ace_support.ace_other {color: rgb(109, 121, 222);}.ace-chrome .ace_variable.ace_parameter {font-style:italic;color:#FD971F;}.ace-chrome .ace_keyword.ace_operator {color: rgb(104, 118, 135);}.ace-chrome .ace_comment {color: #236e24;}.ace-chrome .ace_comment.ace_doc {color: #236e24;}.ace-chrome .ace_comment.ace_doc.ace_tag {color: #236e24;}.ace-chrome .ace_constant.ace_numeric {color: rgb(0, 0, 205);}.ace-chrome .ace_variable {color: rgb(49, 132, 149);}.ace-chrome .ace_xml-pe {color: rgb(104, 104, 91);}.ace-chrome .ace_entity.ace_name.ace_function {color: #0000A2;}.ace-chrome .ace_heading {color: rgb(12, 7, 255);}.ace-chrome .ace_list {color:rgb(185, 6, 144);}.ace-chrome .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-chrome .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-chrome .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-chrome .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-chrome .ace_marker-layer .ace_active-line {background: rgba(0, 0, 0, 0.07);}.ace-chrome .ace_gutter-active-line {background-color : #dcdcdc;}.ace-chrome .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-chrome .ace_storage,.ace-chrome .ace_keyword,.ace-chrome .ace_meta.ace_tag {color: rgb(147, 15, 128);}.ace-chrome .ace_string.ace_regex {color: rgb(255, 0, 0)}.ace-chrome .ace_string {color: #1A1AA6;}.ace-chrome .ace_entity.ace_other.ace_attribute-name {color: #994409;}.ace-chrome .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;}';var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)})
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
15
obsolete/public/datatables/datatables.min.css
vendored
15
obsolete/public/datatables/datatables.min.css
vendored
|
@ -1,15 +0,0 @@
|
||||||
/*
|
|
||||||
* This combined file was created by the DataTables downloader builder:
|
|
||||||
* https://datatables.net/download
|
|
||||||
*
|
|
||||||
* To rebuild or modify this file with the latest versions of the included
|
|
||||||
* software please visit:
|
|
||||||
* https://datatables.net/download/#bs/dt-1.10.11
|
|
||||||
*
|
|
||||||
* Included libraries:
|
|
||||||
* DataTables 1.10.11
|
|
||||||
*/
|
|
||||||
|
|
||||||
table.dataTable{clear:both;margin-top:6px !important;margin-bottom:6px !important;max-width:none !important}table.dataTable td,table.dataTable th{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}table.dataTable td.dataTables_empty,table.dataTable th.dataTables_empty{text-align:center}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}div.dataTables_wrapper div.dataTables_length label{font-weight:normal;text-align:left;white-space:nowrap}div.dataTables_wrapper div.dataTables_length select{width:75px;display:inline-block}div.dataTables_wrapper div.dataTables_filter{text-align:right}div.dataTables_wrapper div.dataTables_filter label{font-weight:normal;white-space:nowrap;text-align:left}div.dataTables_wrapper div.dataTables_filter input{margin-left:0.5em;display:inline-block;width:auto}div.dataTables_wrapper div.dataTables_info{padding-top:8px;white-space:nowrap}div.dataTables_wrapper div.dataTables_paginate{margin:0;white-space:nowrap;text-align:right}div.dataTables_wrapper div.dataTables_paginate ul.pagination{margin:2px 0;white-space:nowrap}div.dataTables_wrapper div.dataTables_processing{position:absolute;top:50%;left:50%;width:200px;margin-left:-100px;margin-top:-26px;text-align:center;padding:1em 0}table.dataTable thead>tr>th.sorting_asc,table.dataTable thead>tr>th.sorting_desc,table.dataTable thead>tr>th.sorting,table.dataTable thead>tr>td.sorting_asc,table.dataTable thead>tr>td.sorting_desc,table.dataTable thead>tr>td.sorting{padding-right:30px}table.dataTable thead>tr>th:active,table.dataTable thead>tr>td:active{outline:none}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{cursor:pointer;position:relative}table.dataTable thead .sorting:after,table.dataTable thead .sorting_asc:after,table.dataTable thead .sorting_desc:after,table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:after{position:absolute;bottom:8px;right:8px;display:block;font-family:'Glyphicons Halflings';opacity:0.5}table.dataTable thead .sorting:after{opacity:0.2;content:"\e150"}table.dataTable thead .sorting_asc:after{content:"\e155"}table.dataTable thead .sorting_desc:after{content:"\e156"}table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:after{color:#eee}div.dataTables_scrollHead table.dataTable{margin-bottom:0 !important}div.dataTables_scrollBody table{border-top:none;margin-top:0 !important;margin-bottom:0 !important}div.dataTables_scrollBody table thead .sorting:after,div.dataTables_scrollBody table thead .sorting_asc:after,div.dataTables_scrollBody table thead .sorting_desc:after{display:none}div.dataTables_scrollBody table tbody tr:first-child th,div.dataTables_scrollBody table tbody tr:first-child td{border-top:none}div.dataTables_scrollFoot table{margin-top:0 !important;border-top:none}@media screen and (max-width: 767px){div.dataTables_wrapper div.dataTables_length,div.dataTables_wrapper div.dataTables_filter,div.dataTables_wrapper div.dataTables_info,div.dataTables_wrapper div.dataTables_paginate{text-align:center}}table.dataTable.table-condensed>thead>tr>th{padding-right:20px}table.dataTable.table-condensed .sorting:after,table.dataTable.table-condensed .sorting_asc:after,table.dataTable.table-condensed .sorting_desc:after{top:6px;right:6px}table.table-bordered.dataTable{border-collapse:separate !important}table.table-bordered.dataTable th,table.table-bordered.dataTable td{border-left-width:0}table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable td:last-child,table.table-bordered.dataTable td:last-child{border-right-width:0}table.table-bordered.dataTable tbody th,table.table-bordered.dataTable tbody td{border-bottom-width:0}div.dataTables_scrollHead table.table-bordered{border-bottom-width:0}div.table-responsive>div.dataTables_wrapper>div.row{margin:0}div.table-responsive>div.dataTables_wrapper>div.row>div[class^="col-"]:first-child{padding-left:0}div.table-responsive>div.dataTables_wrapper>div.row>div[class^="col-"]:last-child{padding-right:0}
|
|
||||||
|
|
||||||
|
|
190
obsolete/public/datatables/datatables.min.js
vendored
190
obsolete/public/datatables/datatables.min.js
vendored
|
@ -1,190 +0,0 @@
|
||||||
/*
|
|
||||||
* This combined file was created by the DataTables downloader builder:
|
|
||||||
* https://datatables.net/download
|
|
||||||
*
|
|
||||||
* To rebuild or modify this file with the latest versions of the included
|
|
||||||
* software please visit:
|
|
||||||
* https://datatables.net/download/#bs/dt-1.10.11
|
|
||||||
*
|
|
||||||
* Included libraries:
|
|
||||||
* DataTables 1.10.11
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*!
|
|
||||||
DataTables 1.10.11
|
|
||||||
©2008-2015 SpryMedia Ltd - datatables.net/license
|
|
||||||
*/
|
|
||||||
(function(h){"function"===typeof define&&define.amd?define(["jquery"],function(D){return h(D,window,document)}):"object"===typeof exports?module.exports=function(D,I){D||(D=window);I||(I="undefined"!==typeof window?require("jquery"):require("jquery")(D));return h(I,D,D.document)}:h(jQuery,window,document)})(function(h,D,I,k){function Y(a){var b,c,d={};h.each(a,function(e){if((b=e.match(/^([^A-Z]+?)([A-Z])/))&&-1!=="a aa ai ao as b fn i m o s ".indexOf(b[1]+" "))c=e.replace(b[0],b[2].toLowerCase()),
|
|
||||||
d[c]=e,"o"===b[1]&&Y(a[e])});a._hungarianMap=d}function K(a,b,c){a._hungarianMap||Y(a);var d;h.each(b,function(e){d=a._hungarianMap[e];if(d!==k&&(c||b[d]===k))"o"===d.charAt(0)?(b[d]||(b[d]={}),h.extend(!0,b[d],b[e]),K(a[d],b[d],c)):b[d]=b[e]})}function Fa(a){var b=m.defaults.oLanguage,c=a.sZeroRecords;!a.sEmptyTable&&(c&&"No data available in table"===b.sEmptyTable)&&E(a,a,"sZeroRecords","sEmptyTable");!a.sLoadingRecords&&(c&&"Loading..."===b.sLoadingRecords)&&E(a,a,"sZeroRecords","sLoadingRecords");
|
|
||||||
a.sInfoThousands&&(a.sThousands=a.sInfoThousands);(a=a.sDecimal)&&db(a)}function eb(a){A(a,"ordering","bSort");A(a,"orderMulti","bSortMulti");A(a,"orderClasses","bSortClasses");A(a,"orderCellsTop","bSortCellsTop");A(a,"order","aaSorting");A(a,"orderFixed","aaSortingFixed");A(a,"paging","bPaginate");A(a,"pagingType","sPaginationType");A(a,"pageLength","iDisplayLength");A(a,"searching","bFilter");"boolean"===typeof a.sScrollX&&(a.sScrollX=a.sScrollX?"100%":"");"boolean"===typeof a.scrollX&&(a.scrollX=
|
|
||||||
a.scrollX?"100%":"");if(a=a.aoSearchCols)for(var b=0,c=a.length;b<c;b++)a[b]&&K(m.models.oSearch,a[b])}function fb(a){A(a,"orderable","bSortable");A(a,"orderData","aDataSort");A(a,"orderSequence","asSorting");A(a,"orderDataType","sortDataType");var b=a.aDataSort;b&&!h.isArray(b)&&(a.aDataSort=[b])}function gb(a){if(!m.__browser){var b={};m.__browser=b;var c=h("<div/>").css({position:"fixed",top:0,left:0,height:1,width:1,overflow:"hidden"}).append(h("<div/>").css({position:"absolute",top:1,left:1,
|
|
||||||
width:100,overflow:"scroll"}).append(h("<div/>").css({width:"100%",height:10}))).appendTo("body"),d=c.children(),e=d.children();b.barWidth=d[0].offsetWidth-d[0].clientWidth;b.bScrollOversize=100===e[0].offsetWidth&&100!==d[0].clientWidth;b.bScrollbarLeft=1!==Math.round(e.offset().left);b.bBounding=c[0].getBoundingClientRect().width?!0:!1;c.remove()}h.extend(a.oBrowser,m.__browser);a.oScroll.iBarWidth=m.__browser.barWidth}function hb(a,b,c,d,e,f){var g,j=!1;c!==k&&(g=c,j=!0);for(;d!==e;)a.hasOwnProperty(d)&&
|
|
||||||
(g=j?b(g,a[d],d,a):a[d],j=!0,d+=f);return g}function Ga(a,b){var c=m.defaults.column,d=a.aoColumns.length,c=h.extend({},m.models.oColumn,c,{nTh:b?b:I.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[d],mData:c.mData?c.mData:d,idx:d});a.aoColumns.push(c);c=a.aoPreSearchCols;c[d]=h.extend({},m.models.oSearch,c[d]);ja(a,d,h(b).data())}function ja(a,b,c){var b=a.aoColumns[b],d=a.oClasses,e=h(b.nTh);if(!b.sWidthOrig){b.sWidthOrig=e.attr("width")||null;var f=
|
|
||||||
(e.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);f&&(b.sWidthOrig=f[1])}c!==k&&null!==c&&(fb(c),K(m.defaults.column,c),c.mDataProp!==k&&!c.mData&&(c.mData=c.mDataProp),c.sType&&(b._sManualType=c.sType),c.className&&!c.sClass&&(c.sClass=c.className),h.extend(b,c),E(b,c,"sWidth","sWidthOrig"),c.iDataSort!==k&&(b.aDataSort=[c.iDataSort]),E(b,c,"aDataSort"));var g=b.mData,j=Q(g),i=b.mRender?Q(b.mRender):null,c=function(a){return"string"===typeof a&&-1!==a.indexOf("@")};b._bAttrSrc=h.isPlainObject(g)&&
|
|
||||||
(c(g.sort)||c(g.type)||c(g.filter));b._setter=null;b.fnGetData=function(a,b,c){var d=j(a,b,k,c);return i&&b?i(d,b,a,c):d};b.fnSetData=function(a,b,c){return R(g)(a,b,c)};"number"!==typeof g&&(a._rowReadObject=!0);a.oFeatures.bSort||(b.bSortable=!1,e.addClass(d.sSortableNone));a=-1!==h.inArray("asc",b.asSorting);c=-1!==h.inArray("desc",b.asSorting);!b.bSortable||!a&&!c?(b.sSortingClass=d.sSortableNone,b.sSortingClassJUI=""):a&&!c?(b.sSortingClass=d.sSortableAsc,b.sSortingClassJUI=d.sSortJUIAscAllowed):
|
|
||||||
!a&&c?(b.sSortingClass=d.sSortableDesc,b.sSortingClassJUI=d.sSortJUIDescAllowed):(b.sSortingClass=d.sSortable,b.sSortingClassJUI=d.sSortJUI)}function U(a){if(!1!==a.oFeatures.bAutoWidth){var b=a.aoColumns;Ha(a);for(var c=0,d=b.length;c<d;c++)b[c].nTh.style.width=b[c].sWidth}b=a.oScroll;(""!==b.sY||""!==b.sX)&&ka(a);u(a,null,"column-sizing",[a])}function Z(a,b){var c=la(a,"bVisible");return"number"===typeof c[b]?c[b]:null}function $(a,b){var c=la(a,"bVisible"),c=h.inArray(b,c);return-1!==c?c:null}
|
|
||||||
function aa(a){return h(F(a.aoColumns,"nTh")).filter(":visible").length}function la(a,b){var c=[];h.map(a.aoColumns,function(a,e){a[b]&&c.push(e)});return c}function Ia(a){var b=a.aoColumns,c=a.aoData,d=m.ext.type.detect,e,f,g,j,i,h,l,q,t;e=0;for(f=b.length;e<f;e++)if(l=b[e],t=[],!l.sType&&l._sManualType)l.sType=l._sManualType;else if(!l.sType){g=0;for(j=d.length;g<j;g++){i=0;for(h=c.length;i<h;i++){t[i]===k&&(t[i]=B(a,i,e,"type"));q=d[g](t[i],a);if(!q&&g!==d.length-1)break;if("html"===q)break}if(q){l.sType=
|
|
||||||
q;break}}l.sType||(l.sType="string")}}function ib(a,b,c,d){var e,f,g,j,i,n,l=a.aoColumns;if(b)for(e=b.length-1;0<=e;e--){n=b[e];var q=n.targets!==k?n.targets:n.aTargets;h.isArray(q)||(q=[q]);f=0;for(g=q.length;f<g;f++)if("number"===typeof q[f]&&0<=q[f]){for(;l.length<=q[f];)Ga(a);d(q[f],n)}else if("number"===typeof q[f]&&0>q[f])d(l.length+q[f],n);else if("string"===typeof q[f]){j=0;for(i=l.length;j<i;j++)("_all"==q[f]||h(l[j].nTh).hasClass(q[f]))&&d(j,n)}}if(c){e=0;for(a=c.length;e<a;e++)d(e,c[e])}}
|
|
||||||
function N(a,b,c,d){var e=a.aoData.length,f=h.extend(!0,{},m.models.oRow,{src:c?"dom":"data",idx:e});f._aData=b;a.aoData.push(f);for(var g=a.aoColumns,j=0,i=g.length;j<i;j++)g[j].sType=null;a.aiDisplayMaster.push(e);b=a.rowIdFn(b);b!==k&&(a.aIds[b]=f);(c||!a.oFeatures.bDeferRender)&&Ja(a,e,c,d);return e}function ma(a,b){var c;b instanceof h||(b=h(b));return b.map(function(b,e){c=Ka(a,e);return N(a,c.data,e,c.cells)})}function B(a,b,c,d){var e=a.iDraw,f=a.aoColumns[c],g=a.aoData[b]._aData,j=f.sDefaultContent,
|
|
||||||
i=f.fnGetData(g,d,{settings:a,row:b,col:c});if(i===k)return a.iDrawError!=e&&null===j&&(L(a,0,"Requested unknown parameter "+("function"==typeof f.mData?"{function}":"'"+f.mData+"'")+" for row "+b+", column "+c,4),a.iDrawError=e),j;if((i===g||null===i)&&null!==j&&d!==k)i=j;else if("function"===typeof i)return i.call(g);return null===i&&"display"==d?"":i}function jb(a,b,c,d){a.aoColumns[c].fnSetData(a.aoData[b]._aData,d,{settings:a,row:b,col:c})}function La(a){return h.map(a.match(/(\\.|[^\.])+/g)||
|
|
||||||
[""],function(a){return a.replace(/\\./g,".")})}function Q(a){if(h.isPlainObject(a)){var b={};h.each(a,function(a,c){c&&(b[a]=Q(c))});return function(a,c,f,g){var j=b[c]||b._;return j!==k?j(a,c,f,g):a}}if(null===a)return function(a){return a};if("function"===typeof a)return function(b,c,f,g){return a(b,c,f,g)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("[")||-1!==a.indexOf("("))){var c=function(a,b,f){var g,j;if(""!==f){j=La(f);for(var i=0,n=j.length;i<n;i++){f=j[i].match(ba);g=
|
|
||||||
j[i].match(V);if(f){j[i]=j[i].replace(ba,"");""!==j[i]&&(a=a[j[i]]);g=[];j.splice(0,i+1);j=j.join(".");if(h.isArray(a)){i=0;for(n=a.length;i<n;i++)g.push(c(a[i],b,j))}a=f[0].substring(1,f[0].length-1);a=""===a?g:g.join(a);break}else if(g){j[i]=j[i].replace(V,"");a=a[j[i]]();continue}if(null===a||a[j[i]]===k)return k;a=a[j[i]]}}return a};return function(b,e){return c(b,e,a)}}return function(b){return b[a]}}function R(a){if(h.isPlainObject(a))return R(a._);if(null===a)return function(){};if("function"===
|
|
||||||
typeof a)return function(b,d,e){a(b,"set",d,e)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("[")||-1!==a.indexOf("("))){var b=function(a,d,e){var e=La(e),f;f=e[e.length-1];for(var g,j,i=0,n=e.length-1;i<n;i++){g=e[i].match(ba);j=e[i].match(V);if(g){e[i]=e[i].replace(ba,"");a[e[i]]=[];f=e.slice();f.splice(0,i+1);g=f.join(".");if(h.isArray(d)){j=0;for(n=d.length;j<n;j++)f={},b(f,d[j],g),a[e[i]].push(f)}else a[e[i]]=d;return}j&&(e[i]=e[i].replace(V,""),a=a[e[i]](d));if(null===a[e[i]]||
|
|
||||||
a[e[i]]===k)a[e[i]]={};a=a[e[i]]}if(f.match(V))a[f.replace(V,"")](d);else a[f.replace(ba,"")]=d};return function(c,d){return b(c,d,a)}}return function(b,d){b[a]=d}}function Ma(a){return F(a.aoData,"_aData")}function na(a){a.aoData.length=0;a.aiDisplayMaster.length=0;a.aiDisplay.length=0;a.aIds={}}function oa(a,b,c){for(var d=-1,e=0,f=a.length;e<f;e++)a[e]==b?d=e:a[e]>b&&a[e]--; -1!=d&&c===k&&a.splice(d,1)}function ca(a,b,c,d){var e=a.aoData[b],f,g=function(c,d){for(;c.childNodes.length;)c.removeChild(c.firstChild);
|
|
||||||
c.innerHTML=B(a,b,d,"display")};if("dom"===c||(!c||"auto"===c)&&"dom"===e.src)e._aData=Ka(a,e,d,d===k?k:e._aData).data;else{var j=e.anCells;if(j)if(d!==k)g(j[d],d);else{c=0;for(f=j.length;c<f;c++)g(j[c],c)}}e._aSortData=null;e._aFilterData=null;g=a.aoColumns;if(d!==k)g[d].sType=null;else{c=0;for(f=g.length;c<f;c++)g[c].sType=null;Na(a,e)}}function Ka(a,b,c,d){var e=[],f=b.firstChild,g,j,i=0,n,l=a.aoColumns,q=a._rowReadObject,d=d!==k?d:q?{}:[],t=function(a,b){if("string"===typeof a){var c=a.indexOf("@");
|
|
||||||
-1!==c&&(c=a.substring(c+1),R(a)(d,b.getAttribute(c)))}},S=function(a){if(c===k||c===i)j=l[i],n=h.trim(a.innerHTML),j&&j._bAttrSrc?(R(j.mData._)(d,n),t(j.mData.sort,a),t(j.mData.type,a),t(j.mData.filter,a)):q?(j._setter||(j._setter=R(j.mData)),j._setter(d,n)):d[i]=n;i++};if(f)for(;f;){g=f.nodeName.toUpperCase();if("TD"==g||"TH"==g)S(f),e.push(f);f=f.nextSibling}else{e=b.anCells;f=0;for(g=e.length;f<g;f++)S(e[f])}if(b=b.firstChild?b:b.nTr)(b=b.getAttribute("id"))&&R(a.rowId)(d,b);return{data:d,cells:e}}
|
|
||||||
function Ja(a,b,c,d){var e=a.aoData[b],f=e._aData,g=[],j,i,n,l,q;if(null===e.nTr){j=c||I.createElement("tr");e.nTr=j;e.anCells=g;j._DT_RowIndex=b;Na(a,e);l=0;for(q=a.aoColumns.length;l<q;l++){n=a.aoColumns[l];i=c?d[l]:I.createElement(n.sCellType);i._DT_CellIndex={row:b,column:l};g.push(i);if((!c||n.mRender||n.mData!==l)&&(!h.isPlainObject(n.mData)||n.mData._!==l+".display"))i.innerHTML=B(a,b,l,"display");n.sClass&&(i.className+=" "+n.sClass);n.bVisible&&!c?j.appendChild(i):!n.bVisible&&c&&i.parentNode.removeChild(i);
|
|
||||||
n.fnCreatedCell&&n.fnCreatedCell.call(a.oInstance,i,B(a,b,l),f,b,l)}u(a,"aoRowCreatedCallback",null,[j,f,b])}e.nTr.setAttribute("role","row")}function Na(a,b){var c=b.nTr,d=b._aData;if(c){var e=a.rowIdFn(d);e&&(c.id=e);d.DT_RowClass&&(e=d.DT_RowClass.split(" "),b.__rowc=b.__rowc?pa(b.__rowc.concat(e)):e,h(c).removeClass(b.__rowc.join(" ")).addClass(d.DT_RowClass));d.DT_RowAttr&&h(c).attr(d.DT_RowAttr);d.DT_RowData&&h(c).data(d.DT_RowData)}}function kb(a){var b,c,d,e,f,g=a.nTHead,j=a.nTFoot,i=0===
|
|
||||||
h("th, td",g).length,n=a.oClasses,l=a.aoColumns;i&&(e=h("<tr/>").appendTo(g));b=0;for(c=l.length;b<c;b++)f=l[b],d=h(f.nTh).addClass(f.sClass),i&&d.appendTo(e),a.oFeatures.bSort&&(d.addClass(f.sSortingClass),!1!==f.bSortable&&(d.attr("tabindex",a.iTabIndex).attr("aria-controls",a.sTableId),Oa(a,f.nTh,b))),f.sTitle!=d[0].innerHTML&&d.html(f.sTitle),Pa(a,"header")(a,d,f,n);i&&da(a.aoHeader,g);h(g).find(">tr").attr("role","row");h(g).find(">tr>th, >tr>td").addClass(n.sHeaderTH);h(j).find(">tr>th, >tr>td").addClass(n.sFooterTH);
|
|
||||||
if(null!==j){a=a.aoFooter[0];b=0;for(c=a.length;b<c;b++)f=l[b],f.nTf=a[b].cell,f.sClass&&h(f.nTf).addClass(f.sClass)}}function ea(a,b,c){var d,e,f,g=[],j=[],i=a.aoColumns.length,n;if(b){c===k&&(c=!1);d=0;for(e=b.length;d<e;d++){g[d]=b[d].slice();g[d].nTr=b[d].nTr;for(f=i-1;0<=f;f--)!a.aoColumns[f].bVisible&&!c&&g[d].splice(f,1);j.push([])}d=0;for(e=g.length;d<e;d++){if(a=g[d].nTr)for(;f=a.firstChild;)a.removeChild(f);f=0;for(b=g[d].length;f<b;f++)if(n=i=1,j[d][f]===k){a.appendChild(g[d][f].cell);
|
|
||||||
for(j[d][f]=1;g[d+i]!==k&&g[d][f].cell==g[d+i][f].cell;)j[d+i][f]=1,i++;for(;g[d][f+n]!==k&&g[d][f].cell==g[d][f+n].cell;){for(c=0;c<i;c++)j[d+c][f+n]=1;n++}h(g[d][f].cell).attr("rowspan",i).attr("colspan",n)}}}}function O(a){var b=u(a,"aoPreDrawCallback","preDraw",[a]);if(-1!==h.inArray(!1,b))C(a,!1);else{var b=[],c=0,d=a.asStripeClasses,e=d.length,f=a.oLanguage,g=a.iInitDisplayStart,j="ssp"==y(a),i=a.aiDisplay;a.bDrawing=!0;g!==k&&-1!==g&&(a._iDisplayStart=j?g:g>=a.fnRecordsDisplay()?0:g,a.iInitDisplayStart=
|
|
||||||
-1);var g=a._iDisplayStart,n=a.fnDisplayEnd();if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++,C(a,!1);else if(j){if(!a.bDestroying&&!lb(a))return}else a.iDraw++;if(0!==i.length){f=j?a.aoData.length:n;for(j=j?0:g;j<f;j++){var l=i[j],q=a.aoData[l];null===q.nTr&&Ja(a,l);l=q.nTr;if(0!==e){var t=d[c%e];q._sRowStripe!=t&&(h(l).removeClass(q._sRowStripe).addClass(t),q._sRowStripe=t)}u(a,"aoRowCallback",null,[l,q._aData,c,j]);b.push(l);c++}}else c=f.sZeroRecords,1==a.iDraw&&"ajax"==y(a)?c=f.sLoadingRecords:
|
|
||||||
f.sEmptyTable&&0===a.fnRecordsTotal()&&(c=f.sEmptyTable),b[0]=h("<tr/>",{"class":e?d[0]:""}).append(h("<td />",{valign:"top",colSpan:aa(a),"class":a.oClasses.sRowEmpty}).html(c))[0];u(a,"aoHeaderCallback","header",[h(a.nTHead).children("tr")[0],Ma(a),g,n,i]);u(a,"aoFooterCallback","footer",[h(a.nTFoot).children("tr")[0],Ma(a),g,n,i]);d=h(a.nTBody);d.children().detach();d.append(h(b));u(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=!1}}function T(a,b){var c=a.oFeatures,d=c.bFilter;
|
|
||||||
c.bSort&&mb(a);d?fa(a,a.oPreviousSearch):a.aiDisplay=a.aiDisplayMaster.slice();!0!==b&&(a._iDisplayStart=0);a._drawHold=b;O(a);a._drawHold=!1}function nb(a){var b=a.oClasses,c=h(a.nTable),c=h("<div/>").insertBefore(c),d=a.oFeatures,e=h("<div/>",{id:a.sTableId+"_wrapper","class":b.sWrapper+(a.nTFoot?"":" "+b.sNoFooter)});a.nHolding=c[0];a.nTableWrapper=e[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var f=a.sDom.split(""),g,j,i,n,l,q,t=0;t<f.length;t++){g=null;j=f[t];if("<"==j){i=h("<div/>")[0];
|
|
||||||
n=f[t+1];if("'"==n||'"'==n){l="";for(q=2;f[t+q]!=n;)l+=f[t+q],q++;"H"==l?l=b.sJUIHeader:"F"==l&&(l=b.sJUIFooter);-1!=l.indexOf(".")?(n=l.split("."),i.id=n[0].substr(1,n[0].length-1),i.className=n[1]):"#"==l.charAt(0)?i.id=l.substr(1,l.length-1):i.className=l;t+=q}e.append(i);e=h(i)}else if(">"==j)e=e.parent();else if("l"==j&&d.bPaginate&&d.bLengthChange)g=ob(a);else if("f"==j&&d.bFilter)g=pb(a);else if("r"==j&&d.bProcessing)g=qb(a);else if("t"==j)g=rb(a);else if("i"==j&&d.bInfo)g=sb(a);else if("p"==
|
|
||||||
j&&d.bPaginate)g=tb(a);else if(0!==m.ext.feature.length){i=m.ext.feature;q=0;for(n=i.length;q<n;q++)if(j==i[q].cFeature){g=i[q].fnInit(a);break}}g&&(i=a.aanFeatures,i[j]||(i[j]=[]),i[j].push(g),e.append(g))}c.replaceWith(e);a.nHolding=null}function da(a,b){var c=h(b).children("tr"),d,e,f,g,j,i,n,l,q,t;a.splice(0,a.length);f=0;for(i=c.length;f<i;f++)a.push([]);f=0;for(i=c.length;f<i;f++){d=c[f];for(e=d.firstChild;e;){if("TD"==e.nodeName.toUpperCase()||"TH"==e.nodeName.toUpperCase()){l=1*e.getAttribute("colspan");
|
|
||||||
q=1*e.getAttribute("rowspan");l=!l||0===l||1===l?1:l;q=!q||0===q||1===q?1:q;g=0;for(j=a[f];j[g];)g++;n=g;t=1===l?!0:!1;for(j=0;j<l;j++)for(g=0;g<q;g++)a[f+g][n+j]={cell:e,unique:t},a[f+g].nTr=d}e=e.nextSibling}}}function qa(a,b,c){var d=[];c||(c=a.aoHeader,b&&(c=[],da(c,b)));for(var b=0,e=c.length;b<e;b++)for(var f=0,g=c[b].length;f<g;f++)if(c[b][f].unique&&(!d[f]||!a.bSortCellsTop))d[f]=c[b][f].cell;return d}function ra(a,b,c){u(a,"aoServerParams","serverParams",[b]);if(b&&h.isArray(b)){var d={},
|
|
||||||
e=/(.*?)\[\]$/;h.each(b,function(a,b){var c=b.name.match(e);c?(c=c[0],d[c]||(d[c]=[]),d[c].push(b.value)):d[b.name]=b.value});b=d}var f,g=a.ajax,j=a.oInstance,i=function(b){u(a,null,"xhr",[a,b,a.jqXHR]);c(b)};if(h.isPlainObject(g)&&g.data){f=g.data;var n=h.isFunction(f)?f(b,a):f,b=h.isFunction(f)&&n?n:h.extend(!0,b,n);delete g.data}n={data:b,success:function(b){var c=b.error||b.sError;c&&L(a,0,c);a.json=b;i(b)},dataType:"json",cache:!1,type:a.sServerMethod,error:function(b,c){var d=u(a,null,"xhr",
|
|
||||||
[a,null,a.jqXHR]);-1===h.inArray(!0,d)&&("parsererror"==c?L(a,0,"Invalid JSON response",1):4===b.readyState&&L(a,0,"Ajax error",7));C(a,!1)}};a.oAjaxData=b;u(a,null,"preXhr",[a,b]);a.fnServerData?a.fnServerData.call(j,a.sAjaxSource,h.map(b,function(a,b){return{name:b,value:a}}),i,a):a.sAjaxSource||"string"===typeof g?a.jqXHR=h.ajax(h.extend(n,{url:g||a.sAjaxSource})):h.isFunction(g)?a.jqXHR=g.call(j,b,i,a):(a.jqXHR=h.ajax(h.extend(n,g)),g.data=f)}function lb(a){return a.bAjaxDataGet?(a.iDraw++,C(a,
|
|
||||||
!0),ra(a,ub(a),function(b){vb(a,b)}),!1):!0}function ub(a){var b=a.aoColumns,c=b.length,d=a.oFeatures,e=a.oPreviousSearch,f=a.aoPreSearchCols,g,j=[],i,n,l,q=W(a);g=a._iDisplayStart;i=!1!==d.bPaginate?a._iDisplayLength:-1;var k=function(a,b){j.push({name:a,value:b})};k("sEcho",a.iDraw);k("iColumns",c);k("sColumns",F(b,"sName").join(","));k("iDisplayStart",g);k("iDisplayLength",i);var S={draw:a.iDraw,columns:[],order:[],start:g,length:i,search:{value:e.sSearch,regex:e.bRegex}};for(g=0;g<c;g++)n=b[g],
|
|
||||||
l=f[g],i="function"==typeof n.mData?"function":n.mData,S.columns.push({data:i,name:n.sName,searchable:n.bSearchable,orderable:n.bSortable,search:{value:l.sSearch,regex:l.bRegex}}),k("mDataProp_"+g,i),d.bFilter&&(k("sSearch_"+g,l.sSearch),k("bRegex_"+g,l.bRegex),k("bSearchable_"+g,n.bSearchable)),d.bSort&&k("bSortable_"+g,n.bSortable);d.bFilter&&(k("sSearch",e.sSearch),k("bRegex",e.bRegex));d.bSort&&(h.each(q,function(a,b){S.order.push({column:b.col,dir:b.dir});k("iSortCol_"+a,b.col);k("sSortDir_"+
|
|
||||||
a,b.dir)}),k("iSortingCols",q.length));b=m.ext.legacy.ajax;return null===b?a.sAjaxSource?j:S:b?j:S}function vb(a,b){var c=sa(a,b),d=b.sEcho!==k?b.sEcho:b.draw,e=b.iTotalRecords!==k?b.iTotalRecords:b.recordsTotal,f=b.iTotalDisplayRecords!==k?b.iTotalDisplayRecords:b.recordsFiltered;if(d){if(1*d<a.iDraw)return;a.iDraw=1*d}na(a);a._iRecordsTotal=parseInt(e,10);a._iRecordsDisplay=parseInt(f,10);d=0;for(e=c.length;d<e;d++)N(a,c[d]);a.aiDisplay=a.aiDisplayMaster.slice();a.bAjaxDataGet=!1;O(a);a._bInitComplete||
|
|
||||||
ta(a,b);a.bAjaxDataGet=!0;C(a,!1)}function sa(a,b){var c=h.isPlainObject(a.ajax)&&a.ajax.dataSrc!==k?a.ajax.dataSrc:a.sAjaxDataProp;return"data"===c?b.aaData||b[c]:""!==c?Q(c)(b):b}function pb(a){var b=a.oClasses,c=a.sTableId,d=a.oLanguage,e=a.oPreviousSearch,f=a.aanFeatures,g='<input type="search" class="'+b.sFilterInput+'"/>',j=d.sSearch,j=j.match(/_INPUT_/)?j.replace("_INPUT_",g):j+g,b=h("<div/>",{id:!f.f?c+"_filter":null,"class":b.sFilter}).append(h("<label/>").append(j)),f=function(){var b=!this.value?
|
|
||||||
"":this.value;b!=e.sSearch&&(fa(a,{sSearch:b,bRegex:e.bRegex,bSmart:e.bSmart,bCaseInsensitive:e.bCaseInsensitive}),a._iDisplayStart=0,O(a))},g=null!==a.searchDelay?a.searchDelay:"ssp"===y(a)?400:0,i=h("input",b).val(e.sSearch).attr("placeholder",d.sSearchPlaceholder).bind("keyup.DT search.DT input.DT paste.DT cut.DT",g?ua(f,g):f).bind("keypress.DT",function(a){if(13==a.keyCode)return!1}).attr("aria-controls",c);h(a.nTable).on("search.dt.DT",function(b,c){if(a===c)try{i[0]!==I.activeElement&&i.val(e.sSearch)}catch(d){}});
|
|
||||||
return b[0]}function fa(a,b,c){var d=a.oPreviousSearch,e=a.aoPreSearchCols,f=function(a){d.sSearch=a.sSearch;d.bRegex=a.bRegex;d.bSmart=a.bSmart;d.bCaseInsensitive=a.bCaseInsensitive};Ia(a);if("ssp"!=y(a)){wb(a,b.sSearch,c,b.bEscapeRegex!==k?!b.bEscapeRegex:b.bRegex,b.bSmart,b.bCaseInsensitive);f(b);for(b=0;b<e.length;b++)xb(a,e[b].sSearch,b,e[b].bEscapeRegex!==k?!e[b].bEscapeRegex:e[b].bRegex,e[b].bSmart,e[b].bCaseInsensitive);yb(a)}else f(b);a.bFiltered=!0;u(a,null,"search",[a])}function yb(a){for(var b=
|
|
||||||
m.ext.search,c=a.aiDisplay,d,e,f=0,g=b.length;f<g;f++){for(var j=[],i=0,n=c.length;i<n;i++)e=c[i],d=a.aoData[e],b[f](a,d._aFilterData,e,d._aData,i)&&j.push(e);c.length=0;h.merge(c,j)}}function xb(a,b,c,d,e,f){if(""!==b)for(var g=a.aiDisplay,d=Qa(b,d,e,f),e=g.length-1;0<=e;e--)b=a.aoData[g[e]]._aFilterData[c],d.test(b)||g.splice(e,1)}function wb(a,b,c,d,e,f){var d=Qa(b,d,e,f),e=a.oPreviousSearch.sSearch,f=a.aiDisplayMaster,g;0!==m.ext.search.length&&(c=!0);g=zb(a);if(0>=b.length)a.aiDisplay=f.slice();
|
|
||||||
else{if(g||c||e.length>b.length||0!==b.indexOf(e)||a.bSorted)a.aiDisplay=f.slice();b=a.aiDisplay;for(c=b.length-1;0<=c;c--)d.test(a.aoData[b[c]]._sFilterRow)||b.splice(c,1)}}function Qa(a,b,c,d){a=b?a:va(a);c&&(a="^(?=.*?"+h.map(a.match(/"[^"]+"|[^ ]+/g)||[""],function(a){if('"'===a.charAt(0))var b=a.match(/^"(.*)"$/),a=b?b[1]:a;return a.replace('"',"")}).join(")(?=.*?")+").*$");return RegExp(a,d?"i":"")}function va(a){return a.replace(Zb,"\\$1")}function zb(a){var b=a.aoColumns,c,d,e,f,g,j,i,h,l=
|
|
||||||
m.ext.type.search;c=!1;d=0;for(f=a.aoData.length;d<f;d++)if(h=a.aoData[d],!h._aFilterData){j=[];e=0;for(g=b.length;e<g;e++)c=b[e],c.bSearchable?(i=B(a,d,e,"filter"),l[c.sType]&&(i=l[c.sType](i)),null===i&&(i=""),"string"!==typeof i&&i.toString&&(i=i.toString())):i="",i.indexOf&&-1!==i.indexOf("&")&&(wa.innerHTML=i,i=$b?wa.textContent:wa.innerText),i.replace&&(i=i.replace(/[\r\n]/g,"")),j.push(i);h._aFilterData=j;h._sFilterRow=j.join(" ");c=!0}return c}function Ab(a){return{search:a.sSearch,smart:a.bSmart,
|
|
||||||
regex:a.bRegex,caseInsensitive:a.bCaseInsensitive}}function Bb(a){return{sSearch:a.search,bSmart:a.smart,bRegex:a.regex,bCaseInsensitive:a.caseInsensitive}}function sb(a){var b=a.sTableId,c=a.aanFeatures.i,d=h("<div/>",{"class":a.oClasses.sInfo,id:!c?b+"_info":null});c||(a.aoDrawCallback.push({fn:Cb,sName:"information"}),d.attr("role","status").attr("aria-live","polite"),h(a.nTable).attr("aria-describedby",b+"_info"));return d[0]}function Cb(a){var b=a.aanFeatures.i;if(0!==b.length){var c=a.oLanguage,
|
|
||||||
d=a._iDisplayStart+1,e=a.fnDisplayEnd(),f=a.fnRecordsTotal(),g=a.fnRecordsDisplay(),j=g?c.sInfo:c.sInfoEmpty;g!==f&&(j+=" "+c.sInfoFiltered);j+=c.sInfoPostFix;j=Db(a,j);c=c.fnInfoCallback;null!==c&&(j=c.call(a.oInstance,a,d,e,f,g,j));h(b).html(j)}}function Db(a,b){var c=a.fnFormatNumber,d=a._iDisplayStart+1,e=a._iDisplayLength,f=a.fnRecordsDisplay(),g=-1===e;return b.replace(/_START_/g,c.call(a,d)).replace(/_END_/g,c.call(a,a.fnDisplayEnd())).replace(/_MAX_/g,c.call(a,a.fnRecordsTotal())).replace(/_TOTAL_/g,
|
|
||||||
c.call(a,f)).replace(/_PAGE_/g,c.call(a,g?1:Math.ceil(d/e))).replace(/_PAGES_/g,c.call(a,g?1:Math.ceil(f/e)))}function ga(a){var b,c,d=a.iInitDisplayStart,e=a.aoColumns,f;c=a.oFeatures;var g=a.bDeferLoading;if(a.bInitialised){nb(a);kb(a);ea(a,a.aoHeader);ea(a,a.aoFooter);C(a,!0);c.bAutoWidth&&Ha(a);b=0;for(c=e.length;b<c;b++)f=e[b],f.sWidth&&(f.nTh.style.width=x(f.sWidth));u(a,null,"preInit",[a]);T(a);e=y(a);if("ssp"!=e||g)"ajax"==e?ra(a,[],function(c){var f=sa(a,c);for(b=0;b<f.length;b++)N(a,f[b]);
|
|
||||||
a.iInitDisplayStart=d;T(a);C(a,!1);ta(a,c)},a):(C(a,!1),ta(a))}else setTimeout(function(){ga(a)},200)}function ta(a,b){a._bInitComplete=!0;(b||a.oInit.aaData)&&U(a);u(a,null,"plugin-init",[a,b]);u(a,"aoInitComplete","init",[a,b])}function Ra(a,b){var c=parseInt(b,10);a._iDisplayLength=c;Sa(a);u(a,null,"length",[a,c])}function ob(a){for(var b=a.oClasses,c=a.sTableId,d=a.aLengthMenu,e=h.isArray(d[0]),f=e?d[0]:d,d=e?d[1]:d,e=h("<select/>",{name:c+"_length","aria-controls":c,"class":b.sLengthSelect}),
|
|
||||||
g=0,j=f.length;g<j;g++)e[0][g]=new Option(d[g],f[g]);var i=h("<div><label/></div>").addClass(b.sLength);a.aanFeatures.l||(i[0].id=c+"_length");i.children().append(a.oLanguage.sLengthMenu.replace("_MENU_",e[0].outerHTML));h("select",i).val(a._iDisplayLength).bind("change.DT",function(){Ra(a,h(this).val());O(a)});h(a.nTable).bind("length.dt.DT",function(b,c,d){a===c&&h("select",i).val(d)});return i[0]}function tb(a){var b=a.sPaginationType,c=m.ext.pager[b],d="function"===typeof c,e=function(a){O(a)},
|
|
||||||
b=h("<div/>").addClass(a.oClasses.sPaging+b)[0],f=a.aanFeatures;d||c.fnInit(a,b,e);f.p||(b.id=a.sTableId+"_paginate",a.aoDrawCallback.push({fn:function(a){if(d){var b=a._iDisplayStart,i=a._iDisplayLength,h=a.fnRecordsDisplay(),l=-1===i,b=l?0:Math.ceil(b/i),i=l?1:Math.ceil(h/i),h=c(b,i),k,l=0;for(k=f.p.length;l<k;l++)Pa(a,"pageButton")(a,f.p[l],l,h,b,i)}else c.fnUpdate(a,e)},sName:"pagination"}));return b}function Ta(a,b,c){var d=a._iDisplayStart,e=a._iDisplayLength,f=a.fnRecordsDisplay();0===f||-1===
|
|
||||||
e?d=0:"number"===typeof b?(d=b*e,d>f&&(d=0)):"first"==b?d=0:"previous"==b?(d=0<=e?d-e:0,0>d&&(d=0)):"next"==b?d+e<f&&(d+=e):"last"==b?d=Math.floor((f-1)/e)*e:L(a,0,"Unknown paging action: "+b,5);b=a._iDisplayStart!==d;a._iDisplayStart=d;b&&(u(a,null,"page",[a]),c&&O(a));return b}function qb(a){return h("<div/>",{id:!a.aanFeatures.r?a.sTableId+"_processing":null,"class":a.oClasses.sProcessing}).html(a.oLanguage.sProcessing).insertBefore(a.nTable)[0]}function C(a,b){a.oFeatures.bProcessing&&h(a.aanFeatures.r).css("display",
|
|
||||||
b?"block":"none");u(a,null,"processing",[a,b])}function rb(a){var b=h(a.nTable);b.attr("role","grid");var c=a.oScroll;if(""===c.sX&&""===c.sY)return a.nTable;var d=c.sX,e=c.sY,f=a.oClasses,g=b.children("caption"),j=g.length?g[0]._captionSide:null,i=h(b[0].cloneNode(!1)),n=h(b[0].cloneNode(!1)),l=b.children("tfoot");l.length||(l=null);i=h("<div/>",{"class":f.sScrollWrapper}).append(h("<div/>",{"class":f.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,width:d?!d?null:x(d):"100%"}).append(h("<div/>",
|
|
||||||
{"class":f.sScrollHeadInner}).css({"box-sizing":"content-box",width:c.sXInner||"100%"}).append(i.removeAttr("id").css("margin-left",0).append("top"===j?g:null).append(b.children("thead"))))).append(h("<div/>",{"class":f.sScrollBody}).css({position:"relative",overflow:"auto",width:!d?null:x(d)}).append(b));l&&i.append(h("<div/>",{"class":f.sScrollFoot}).css({overflow:"hidden",border:0,width:d?!d?null:x(d):"100%"}).append(h("<div/>",{"class":f.sScrollFootInner}).append(n.removeAttr("id").css("margin-left",
|
|
||||||
0).append("bottom"===j?g:null).append(b.children("tfoot")))));var b=i.children(),k=b[0],f=b[1],t=l?b[2]:null;if(d)h(f).on("scroll.DT",function(){var a=this.scrollLeft;k.scrollLeft=a;l&&(t.scrollLeft=a)});h(f).css(e&&c.bCollapse?"max-height":"height",e);a.nScrollHead=k;a.nScrollBody=f;a.nScrollFoot=t;a.aoDrawCallback.push({fn:ka,sName:"scrolling"});return i[0]}function ka(a){var b=a.oScroll,c=b.sX,d=b.sXInner,e=b.sY,b=b.iBarWidth,f=h(a.nScrollHead),g=f[0].style,j=f.children("div"),i=j[0].style,n=j.children("table"),
|
|
||||||
j=a.nScrollBody,l=h(j),q=j.style,t=h(a.nScrollFoot).children("div"),m=t.children("table"),o=h(a.nTHead),G=h(a.nTable),p=G[0],r=p.style,u=a.nTFoot?h(a.nTFoot):null,Eb=a.oBrowser,Ua=Eb.bScrollOversize,s=F(a.aoColumns,"nTh"),P,v,w,y,z=[],A=[],B=[],C=[],D,E=function(a){a=a.style;a.paddingTop="0";a.paddingBottom="0";a.borderTopWidth="0";a.borderBottomWidth="0";a.height=0};v=j.scrollHeight>j.clientHeight;if(a.scrollBarVis!==v&&a.scrollBarVis!==k)a.scrollBarVis=v,U(a);else{a.scrollBarVis=v;G.children("thead, tfoot").remove();
|
|
||||||
u&&(w=u.clone().prependTo(G),P=u.find("tr"),w=w.find("tr"));y=o.clone().prependTo(G);o=o.find("tr");v=y.find("tr");y.find("th, td").removeAttr("tabindex");c||(q.width="100%",f[0].style.width="100%");h.each(qa(a,y),function(b,c){D=Z(a,b);c.style.width=a.aoColumns[D].sWidth});u&&J(function(a){a.style.width=""},w);f=G.outerWidth();if(""===c){r.width="100%";if(Ua&&(G.find("tbody").height()>j.offsetHeight||"scroll"==l.css("overflow-y")))r.width=x(G.outerWidth()-b);f=G.outerWidth()}else""!==d&&(r.width=
|
|
||||||
x(d),f=G.outerWidth());J(E,v);J(function(a){B.push(a.innerHTML);z.push(x(h(a).css("width")))},v);J(function(a,b){if(h.inArray(a,s)!==-1)a.style.width=z[b]},o);h(v).height(0);u&&(J(E,w),J(function(a){C.push(a.innerHTML);A.push(x(h(a).css("width")))},w),J(function(a,b){a.style.width=A[b]},P),h(w).height(0));J(function(a,b){a.innerHTML='<div class="dataTables_sizing" style="height:0;overflow:hidden;">'+B[b]+"</div>";a.style.width=z[b]},v);u&&J(function(a,b){a.innerHTML='<div class="dataTables_sizing" style="height:0;overflow:hidden;">'+
|
|
||||||
C[b]+"</div>";a.style.width=A[b]},w);if(G.outerWidth()<f){P=j.scrollHeight>j.offsetHeight||"scroll"==l.css("overflow-y")?f+b:f;if(Ua&&(j.scrollHeight>j.offsetHeight||"scroll"==l.css("overflow-y")))r.width=x(P-b);(""===c||""!==d)&&L(a,1,"Possible column misalignment",6)}else P="100%";q.width=x(P);g.width=x(P);u&&(a.nScrollFoot.style.width=x(P));!e&&Ua&&(q.height=x(p.offsetHeight+b));c=G.outerWidth();n[0].style.width=x(c);i.width=x(c);d=G.height()>j.clientHeight||"scroll"==l.css("overflow-y");e="padding"+
|
|
||||||
(Eb.bScrollbarLeft?"Left":"Right");i[e]=d?b+"px":"0px";u&&(m[0].style.width=x(c),t[0].style.width=x(c),t[0].style[e]=d?b+"px":"0px");G.children("colgroup").insertBefore(G.children("thead"));l.scroll();if((a.bSorted||a.bFiltered)&&!a._drawHold)j.scrollTop=0}}function J(a,b,c){for(var d=0,e=0,f=b.length,g,j;e<f;){g=b[e].firstChild;for(j=c?c[e].firstChild:null;g;)1===g.nodeType&&(c?a(g,j,d):a(g,d),d++),g=g.nextSibling,j=c?j.nextSibling:null;e++}}function Ha(a){var b=a.nTable,c=a.aoColumns,d=a.oScroll,
|
|
||||||
e=d.sY,f=d.sX,g=d.sXInner,j=c.length,i=la(a,"bVisible"),n=h("th",a.nTHead),l=b.getAttribute("width"),k=b.parentNode,t=!1,m,o,p=a.oBrowser,d=p.bScrollOversize;(m=b.style.width)&&-1!==m.indexOf("%")&&(l=m);for(m=0;m<i.length;m++)o=c[i[m]],null!==o.sWidth&&(o.sWidth=Fb(o.sWidthOrig,k),t=!0);if(d||!t&&!f&&!e&&j==aa(a)&&j==n.length)for(m=0;m<j;m++)i=Z(a,m),null!==i&&(c[i].sWidth=x(n.eq(m).width()));else{j=h(b).clone().css("visibility","hidden").removeAttr("id");j.find("tbody tr").remove();var r=h("<tr/>").appendTo(j.find("tbody"));
|
|
||||||
j.find("thead, tfoot").remove();j.append(h(a.nTHead).clone()).append(h(a.nTFoot).clone());j.find("tfoot th, tfoot td").css("width","");n=qa(a,j.find("thead")[0]);for(m=0;m<i.length;m++)o=c[i[m]],n[m].style.width=null!==o.sWidthOrig&&""!==o.sWidthOrig?x(o.sWidthOrig):"",o.sWidthOrig&&f&&h(n[m]).append(h("<div/>").css({width:o.sWidthOrig,margin:0,padding:0,border:0,height:1}));if(a.aoData.length)for(m=0;m<i.length;m++)t=i[m],o=c[t],h(Gb(a,t)).clone(!1).append(o.sContentPadding).appendTo(r);h("[name]",
|
|
||||||
j).removeAttr("name");o=h("<div/>").css(f||e?{position:"absolute",top:0,left:0,height:1,right:0,overflow:"hidden"}:{}).append(j).appendTo(k);f&&g?j.width(g):f?(j.css("width","auto"),j.removeAttr("width"),j.width()<k.clientWidth&&l&&j.width(k.clientWidth)):e?j.width(k.clientWidth):l&&j.width(l);for(m=e=0;m<i.length;m++)k=h(n[m]),g=k.outerWidth()-k.width(),k=p.bBounding?Math.ceil(n[m].getBoundingClientRect().width):k.outerWidth(),e+=k,c[i[m]].sWidth=x(k-g);b.style.width=x(e);o.remove()}l&&(b.style.width=
|
|
||||||
x(l));if((l||f)&&!a._reszEvt)b=function(){h(D).bind("resize.DT-"+a.sInstance,ua(function(){U(a)}))},d?setTimeout(b,1E3):b(),a._reszEvt=!0}function ua(a,b){var c=b!==k?b:200,d,e;return function(){var b=this,g=+new Date,j=arguments;d&&g<d+c?(clearTimeout(e),e=setTimeout(function(){d=k;a.apply(b,j)},c)):(d=g,a.apply(b,j))}}function Fb(a,b){if(!a)return 0;var c=h("<div/>").css("width",x(a)).appendTo(b||I.body),d=c[0].offsetWidth;c.remove();return d}function Gb(a,b){var c=Hb(a,b);if(0>c)return null;var d=
|
|
||||||
a.aoData[c];return!d.nTr?h("<td/>").html(B(a,c,b,"display"))[0]:d.anCells[b]}function Hb(a,b){for(var c,d=-1,e=-1,f=0,g=a.aoData.length;f<g;f++)c=B(a,f,b,"display")+"",c=c.replace(ac,""),c=c.replace(/ /g," "),c.length>d&&(d=c.length,e=f);return e}function x(a){return null===a?"0px":"number"==typeof a?0>a?"0px":a+"px":a.match(/\d$/)?a+"px":a}function W(a){var b,c,d=[],e=a.aoColumns,f,g,j,i;b=a.aaSortingFixed;c=h.isPlainObject(b);var n=[];f=function(a){a.length&&!h.isArray(a[0])?n.push(a):h.merge(n,
|
|
||||||
a)};h.isArray(b)&&f(b);c&&b.pre&&f(b.pre);f(a.aaSorting);c&&b.post&&f(b.post);for(a=0;a<n.length;a++){i=n[a][0];f=e[i].aDataSort;b=0;for(c=f.length;b<c;b++)g=f[b],j=e[g].sType||"string",n[a]._idx===k&&(n[a]._idx=h.inArray(n[a][1],e[g].asSorting)),d.push({src:i,col:g,dir:n[a][1],index:n[a]._idx,type:j,formatter:m.ext.type.order[j+"-pre"]})}return d}function mb(a){var b,c,d=[],e=m.ext.type.order,f=a.aoData,g=0,j,i=a.aiDisplayMaster,h;Ia(a);h=W(a);b=0;for(c=h.length;b<c;b++)j=h[b],j.formatter&&g++,Ib(a,
|
|
||||||
j.col);if("ssp"!=y(a)&&0!==h.length){b=0;for(c=i.length;b<c;b++)d[i[b]]=b;g===h.length?i.sort(function(a,b){var c,e,g,j,i=h.length,k=f[a]._aSortData,m=f[b]._aSortData;for(g=0;g<i;g++)if(j=h[g],c=k[j.col],e=m[j.col],c=c<e?-1:c>e?1:0,0!==c)return"asc"===j.dir?c:-c;c=d[a];e=d[b];return c<e?-1:c>e?1:0}):i.sort(function(a,b){var c,g,j,i,k=h.length,m=f[a]._aSortData,p=f[b]._aSortData;for(j=0;j<k;j++)if(i=h[j],c=m[i.col],g=p[i.col],i=e[i.type+"-"+i.dir]||e["string-"+i.dir],c=i(c,g),0!==c)return c;c=d[a];
|
|
||||||
g=d[b];return c<g?-1:c>g?1:0})}a.bSorted=!0}function Jb(a){for(var b,c,d=a.aoColumns,e=W(a),a=a.oLanguage.oAria,f=0,g=d.length;f<g;f++){c=d[f];var j=c.asSorting;b=c.sTitle.replace(/<.*?>/g,"");var i=c.nTh;i.removeAttribute("aria-sort");c.bSortable&&(0<e.length&&e[0].col==f?(i.setAttribute("aria-sort","asc"==e[0].dir?"ascending":"descending"),c=j[e[0].index+1]||j[0]):c=j[0],b+="asc"===c?a.sSortAscending:a.sSortDescending);i.setAttribute("aria-label",b)}}function Va(a,b,c,d){var e=a.aaSorting,f=a.aoColumns[b].asSorting,
|
|
||||||
g=function(a,b){var c=a._idx;c===k&&(c=h.inArray(a[1],f));return c+1<f.length?c+1:b?null:0};"number"===typeof e[0]&&(e=a.aaSorting=[e]);c&&a.oFeatures.bSortMulti?(c=h.inArray(b,F(e,"0")),-1!==c?(b=g(e[c],!0),null===b&&1===e.length&&(b=0),null===b?e.splice(c,1):(e[c][1]=f[b],e[c]._idx=b)):(e.push([b,f[0],0]),e[e.length-1]._idx=0)):e.length&&e[0][0]==b?(b=g(e[0]),e.length=1,e[0][1]=f[b],e[0]._idx=b):(e.length=0,e.push([b,f[0]]),e[0]._idx=0);T(a);"function"==typeof d&&d(a)}function Oa(a,b,c,d){var e=
|
|
||||||
a.aoColumns[c];Wa(b,{},function(b){!1!==e.bSortable&&(a.oFeatures.bProcessing?(C(a,!0),setTimeout(function(){Va(a,c,b.shiftKey,d);"ssp"!==y(a)&&C(a,!1)},0)):Va(a,c,b.shiftKey,d))})}function xa(a){var b=a.aLastSort,c=a.oClasses.sSortColumn,d=W(a),e=a.oFeatures,f,g;if(e.bSort&&e.bSortClasses){e=0;for(f=b.length;e<f;e++)g=b[e].src,h(F(a.aoData,"anCells",g)).removeClass(c+(2>e?e+1:3));e=0;for(f=d.length;e<f;e++)g=d[e].src,h(F(a.aoData,"anCells",g)).addClass(c+(2>e?e+1:3))}a.aLastSort=d}function Ib(a,
|
|
||||||
b){var c=a.aoColumns[b],d=m.ext.order[c.sSortDataType],e;d&&(e=d.call(a.oInstance,a,b,$(a,b)));for(var f,g=m.ext.type.order[c.sType+"-pre"],j=0,i=a.aoData.length;j<i;j++)if(c=a.aoData[j],c._aSortData||(c._aSortData=[]),!c._aSortData[b]||d)f=d?e[j]:B(a,j,b,"sort"),c._aSortData[b]=g?g(f):f}function ya(a){if(a.oFeatures.bStateSave&&!a.bDestroying){var b={time:+new Date,start:a._iDisplayStart,length:a._iDisplayLength,order:h.extend(!0,[],a.aaSorting),search:Ab(a.oPreviousSearch),columns:h.map(a.aoColumns,
|
|
||||||
function(b,d){return{visible:b.bVisible,search:Ab(a.aoPreSearchCols[d])}})};u(a,"aoStateSaveParams","stateSaveParams",[a,b]);a.oSavedState=b;a.fnStateSaveCallback.call(a.oInstance,a,b)}}function Kb(a){var b,c,d=a.aoColumns;if(a.oFeatures.bStateSave){var e=a.fnStateLoadCallback.call(a.oInstance,a);if(e&&e.time&&(b=u(a,"aoStateLoadParams","stateLoadParams",[a,e]),-1===h.inArray(!1,b)&&(b=a.iStateDuration,!(0<b&&e.time<+new Date-1E3*b)&&d.length===e.columns.length))){a.oLoadedState=h.extend(!0,{},e);
|
|
||||||
e.start!==k&&(a._iDisplayStart=e.start,a.iInitDisplayStart=e.start);e.length!==k&&(a._iDisplayLength=e.length);e.order!==k&&(a.aaSorting=[],h.each(e.order,function(b,c){a.aaSorting.push(c[0]>=d.length?[0,c[1]]:c)}));e.search!==k&&h.extend(a.oPreviousSearch,Bb(e.search));b=0;for(c=e.columns.length;b<c;b++){var f=e.columns[b];f.visible!==k&&(d[b].bVisible=f.visible);f.search!==k&&h.extend(a.aoPreSearchCols[b],Bb(f.search))}u(a,"aoStateLoaded","stateLoaded",[a,e])}}}function za(a){var b=m.settings,a=
|
|
||||||
h.inArray(a,F(b,"nTable"));return-1!==a?b[a]:null}function L(a,b,c,d){c="DataTables warning: "+(a?"table id="+a.sTableId+" - ":"")+c;d&&(c+=". For more information about this error, please see http://datatables.net/tn/"+d);if(b)D.console&&console.log&&console.log(c);else if(b=m.ext,b=b.sErrMode||b.errMode,a&&u(a,null,"error",[a,d,c]),"alert"==b)alert(c);else{if("throw"==b)throw Error(c);"function"==typeof b&&b(a,d,c)}}function E(a,b,c,d){h.isArray(c)?h.each(c,function(c,d){h.isArray(d)?E(a,b,d[0],
|
|
||||||
d[1]):E(a,b,d)}):(d===k&&(d=c),b[c]!==k&&(a[d]=b[c]))}function Lb(a,b,c){var d,e;for(e in b)b.hasOwnProperty(e)&&(d=b[e],h.isPlainObject(d)?(h.isPlainObject(a[e])||(a[e]={}),h.extend(!0,a[e],d)):a[e]=c&&"data"!==e&&"aaData"!==e&&h.isArray(d)?d.slice():d);return a}function Wa(a,b,c){h(a).bind("click.DT",b,function(b){a.blur();c(b)}).bind("keypress.DT",b,function(a){13===a.which&&(a.preventDefault(),c(a))}).bind("selectstart.DT",function(){return!1})}function z(a,b,c,d){c&&a[b].push({fn:c,sName:d})}
|
|
||||||
function u(a,b,c,d){var e=[];b&&(e=h.map(a[b].slice().reverse(),function(b){return b.fn.apply(a.oInstance,d)}));null!==c&&(b=h.Event(c+".dt"),h(a.nTable).trigger(b,d),e.push(b.result));return e}function Sa(a){var b=a._iDisplayStart,c=a.fnDisplayEnd(),d=a._iDisplayLength;b>=c&&(b=c-d);b-=b%d;if(-1===d||0>b)b=0;a._iDisplayStart=b}function Pa(a,b){var c=a.renderer,d=m.ext.renderer[b];return h.isPlainObject(c)&&c[b]?d[c[b]]||d._:"string"===typeof c?d[c]||d._:d._}function y(a){return a.oFeatures.bServerSide?
|
|
||||||
"ssp":a.ajax||a.sAjaxSource?"ajax":"dom"}function Aa(a,b){var c=[],c=Mb.numbers_length,d=Math.floor(c/2);b<=c?c=X(0,b):a<=d?(c=X(0,c-2),c.push("ellipsis"),c.push(b-1)):(a>=b-1-d?c=X(b-(c-2),b):(c=X(a-d+2,a+d-1),c.push("ellipsis"),c.push(b-1)),c.splice(0,0,"ellipsis"),c.splice(0,0,0));c.DT_el="span";return c}function db(a){h.each({num:function(b){return Ba(b,a)},"num-fmt":function(b){return Ba(b,a,Xa)},"html-num":function(b){return Ba(b,a,Ca)},"html-num-fmt":function(b){return Ba(b,a,Ca,Xa)}},function(b,
|
|
||||||
c){v.type.order[b+a+"-pre"]=c;b.match(/^html\-/)&&(v.type.search[b+a]=v.type.search.html)})}function Nb(a){return function(){var b=[za(this[m.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return m.ext.internal[a].apply(this,b)}}var m,v,r,p,s,Ya={},Ob=/[\r\n]/g,Ca=/<.*?>/g,bc=/^[\w\+\-]/,cc=/[\w\+\-]$/,Zb=RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\$|\\^|\\-)","g"),Xa=/[',$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfk]/gi,M=function(a){return!a||!0===a||"-"===a?!0:!1},
|
|
||||||
Pb=function(a){var b=parseInt(a,10);return!isNaN(b)&&isFinite(a)?b:null},Qb=function(a,b){Ya[b]||(Ya[b]=RegExp(va(b),"g"));return"string"===typeof a&&"."!==b?a.replace(/\./g,"").replace(Ya[b],"."):a},Za=function(a,b,c){var d="string"===typeof a;if(M(a))return!0;b&&d&&(a=Qb(a,b));c&&d&&(a=a.replace(Xa,""));return!isNaN(parseFloat(a))&&isFinite(a)},Rb=function(a,b,c){return M(a)?!0:!(M(a)||"string"===typeof a)?null:Za(a.replace(Ca,""),b,c)?!0:null},F=function(a,b,c){var d=[],e=0,f=a.length;if(c!==k)for(;e<
|
|
||||||
f;e++)a[e]&&a[e][b]&&d.push(a[e][b][c]);else for(;e<f;e++)a[e]&&d.push(a[e][b]);return d},ha=function(a,b,c,d){var e=[],f=0,g=b.length;if(d!==k)for(;f<g;f++)a[b[f]][c]&&e.push(a[b[f]][c][d]);else for(;f<g;f++)e.push(a[b[f]][c]);return e},X=function(a,b){var c=[],d;b===k?(b=0,d=a):(d=b,b=a);for(var e=b;e<d;e++)c.push(e);return c},Sb=function(a){for(var b=[],c=0,d=a.length;c<d;c++)a[c]&&b.push(a[c]);return b},pa=function(a){var b=[],c,d,e=a.length,f,g=0;d=0;a:for(;d<e;d++){c=a[d];for(f=0;f<g;f++)if(b[f]===
|
|
||||||
c)continue a;b.push(c);g++}return b},A=function(a,b,c){a[b]!==k&&(a[c]=a[b])},ba=/\[.*?\]$/,V=/\(\)$/,wa=h("<div>")[0],$b=wa.textContent!==k,ac=/<.*?>/g;m=function(a){this.$=function(a,b){return this.api(!0).$(a,b)};this._=function(a,b){return this.api(!0).rows(a,b).data()};this.api=function(a){return a?new r(za(this[v.iApiIndex])):new r(this)};this.fnAddData=function(a,b){var c=this.api(!0),d=h.isArray(a)&&(h.isArray(a[0])||h.isPlainObject(a[0]))?c.rows.add(a):c.row.add(a);(b===k||b)&&c.draw();return d.flatten().toArray()};
|
|
||||||
this.fnAdjustColumnSizing=function(a){var b=this.api(!0).columns.adjust(),c=b.settings()[0],d=c.oScroll;a===k||a?b.draw(!1):(""!==d.sX||""!==d.sY)&&ka(c)};this.fnClearTable=function(a){var b=this.api(!0).clear();(a===k||a)&&b.draw()};this.fnClose=function(a){this.api(!0).row(a).child.hide()};this.fnDeleteRow=function(a,b,c){var d=this.api(!0),a=d.rows(a),e=a.settings()[0],h=e.aoData[a[0][0]];a.remove();b&&b.call(this,e,h);(c===k||c)&&d.draw();return h};this.fnDestroy=function(a){this.api(!0).destroy(a)};
|
|
||||||
this.fnDraw=function(a){this.api(!0).draw(a)};this.fnFilter=function(a,b,c,d,e,h){e=this.api(!0);null===b||b===k?e.search(a,c,d,h):e.column(b).search(a,c,d,h);e.draw()};this.fnGetData=function(a,b){var c=this.api(!0);if(a!==k){var d=a.nodeName?a.nodeName.toLowerCase():"";return b!==k||"td"==d||"th"==d?c.cell(a,b).data():c.row(a).data()||null}return c.data().toArray()};this.fnGetNodes=function(a){var b=this.api(!0);return a!==k?b.row(a).node():b.rows().nodes().flatten().toArray()};this.fnGetPosition=
|
|
||||||
function(a){var b=this.api(!0),c=a.nodeName.toUpperCase();return"TR"==c?b.row(a).index():"TD"==c||"TH"==c?(a=b.cell(a).index(),[a.row,a.columnVisible,a.column]):null};this.fnIsOpen=function(a){return this.api(!0).row(a).child.isShown()};this.fnOpen=function(a,b,c){return this.api(!0).row(a).child(b,c).show().child()[0]};this.fnPageChange=function(a,b){var c=this.api(!0).page(a);(b===k||b)&&c.draw(!1)};this.fnSetColumnVis=function(a,b,c){a=this.api(!0).column(a).visible(b);(c===k||c)&&a.columns.adjust().draw()};
|
|
||||||
this.fnSettings=function(){return za(this[v.iApiIndex])};this.fnSort=function(a){this.api(!0).order(a).draw()};this.fnSortListener=function(a,b,c){this.api(!0).order.listener(a,b,c)};this.fnUpdate=function(a,b,c,d,e){var h=this.api(!0);c===k||null===c?h.row(b).data(a):h.cell(b,c).data(a);(e===k||e)&&h.columns.adjust();(d===k||d)&&h.draw();return 0};this.fnVersionCheck=v.fnVersionCheck;var b=this,c=a===k,d=this.length;c&&(a={});this.oApi=this.internal=v.internal;for(var e in m.ext.internal)e&&(this[e]=
|
|
||||||
Nb(e));this.each(function(){var e={},e=1<d?Lb(e,a,!0):a,g=0,j,i=this.getAttribute("id"),n=!1,l=m.defaults,q=h(this);if("table"!=this.nodeName.toLowerCase())L(null,0,"Non-table node initialisation ("+this.nodeName+")",2);else{eb(l);fb(l.column);K(l,l,!0);K(l.column,l.column,!0);K(l,h.extend(e,q.data()));var t=m.settings,g=0;for(j=t.length;g<j;g++){var p=t[g];if(p.nTable==this||p.nTHead.parentNode==this||p.nTFoot&&p.nTFoot.parentNode==this){g=e.bRetrieve!==k?e.bRetrieve:l.bRetrieve;if(c||g)return p.oInstance;
|
|
||||||
if(e.bDestroy!==k?e.bDestroy:l.bDestroy){p.oInstance.fnDestroy();break}else{L(p,0,"Cannot reinitialise DataTable",3);return}}if(p.sTableId==this.id){t.splice(g,1);break}}if(null===i||""===i)this.id=i="DataTables_Table_"+m.ext._unique++;var o=h.extend(!0,{},m.models.oSettings,{sDestroyWidth:q[0].style.width,sInstance:i,sTableId:i});o.nTable=this;o.oApi=b.internal;o.oInit=e;t.push(o);o.oInstance=1===b.length?b:q.dataTable();eb(e);e.oLanguage&&Fa(e.oLanguage);e.aLengthMenu&&!e.iDisplayLength&&(e.iDisplayLength=
|
|
||||||
h.isArray(e.aLengthMenu[0])?e.aLengthMenu[0][0]:e.aLengthMenu[0]);e=Lb(h.extend(!0,{},l),e);E(o.oFeatures,e,"bPaginate bLengthChange bFilter bSort bSortMulti bInfo bProcessing bAutoWidth bSortClasses bServerSide bDeferRender".split(" "));E(o,e,["asStripeClasses","ajax","fnServerData","fnFormatNumber","sServerMethod","aaSorting","aaSortingFixed","aLengthMenu","sPaginationType","sAjaxSource","sAjaxDataProp","iStateDuration","sDom","bSortCellsTop","iTabIndex","fnStateLoadCallback","fnStateSaveCallback",
|
|
||||||
"renderer","searchDelay","rowId",["iCookieDuration","iStateDuration"],["oSearch","oPreviousSearch"],["aoSearchCols","aoPreSearchCols"],["iDisplayLength","_iDisplayLength"],["bJQueryUI","bJUI"]]);E(o.oScroll,e,[["sScrollX","sX"],["sScrollXInner","sXInner"],["sScrollY","sY"],["bScrollCollapse","bCollapse"]]);E(o.oLanguage,e,"fnInfoCallback");z(o,"aoDrawCallback",e.fnDrawCallback,"user");z(o,"aoServerParams",e.fnServerParams,"user");z(o,"aoStateSaveParams",e.fnStateSaveParams,"user");z(o,"aoStateLoadParams",
|
|
||||||
e.fnStateLoadParams,"user");z(o,"aoStateLoaded",e.fnStateLoaded,"user");z(o,"aoRowCallback",e.fnRowCallback,"user");z(o,"aoRowCreatedCallback",e.fnCreatedRow,"user");z(o,"aoHeaderCallback",e.fnHeaderCallback,"user");z(o,"aoFooterCallback",e.fnFooterCallback,"user");z(o,"aoInitComplete",e.fnInitComplete,"user");z(o,"aoPreDrawCallback",e.fnPreDrawCallback,"user");o.rowIdFn=Q(e.rowId);gb(o);i=o.oClasses;e.bJQueryUI?(h.extend(i,m.ext.oJUIClasses,e.oClasses),e.sDom===l.sDom&&"lfrtip"===l.sDom&&(o.sDom=
|
|
||||||
'<"H"lfr>t<"F"ip>'),o.renderer)?h.isPlainObject(o.renderer)&&!o.renderer.header&&(o.renderer.header="jqueryui"):o.renderer="jqueryui":h.extend(i,m.ext.classes,e.oClasses);q.addClass(i.sTable);o.iInitDisplayStart===k&&(o.iInitDisplayStart=e.iDisplayStart,o._iDisplayStart=e.iDisplayStart);null!==e.iDeferLoading&&(o.bDeferLoading=!0,g=h.isArray(e.iDeferLoading),o._iRecordsDisplay=g?e.iDeferLoading[0]:e.iDeferLoading,o._iRecordsTotal=g?e.iDeferLoading[1]:e.iDeferLoading);var r=o.oLanguage;h.extend(!0,
|
|
||||||
r,e.oLanguage);""!==r.sUrl&&(h.ajax({dataType:"json",url:r.sUrl,success:function(a){Fa(a);K(l.oLanguage,a);h.extend(true,r,a);ga(o)},error:function(){ga(o)}}),n=!0);null===e.asStripeClasses&&(o.asStripeClasses=[i.sStripeOdd,i.sStripeEven]);var g=o.asStripeClasses,v=q.children("tbody").find("tr").eq(0);-1!==h.inArray(!0,h.map(g,function(a){return v.hasClass(a)}))&&(h("tbody tr",this).removeClass(g.join(" ")),o.asDestroyStripes=g.slice());t=[];g=this.getElementsByTagName("thead");0!==g.length&&(da(o.aoHeader,
|
|
||||||
g[0]),t=qa(o));if(null===e.aoColumns){p=[];g=0;for(j=t.length;g<j;g++)p.push(null)}else p=e.aoColumns;g=0;for(j=p.length;g<j;g++)Ga(o,t?t[g]:null);ib(o,e.aoColumnDefs,p,function(a,b){ja(o,a,b)});if(v.length){var s=function(a,b){return a.getAttribute("data-"+b)!==null?b:null};h(v[0]).children("th, td").each(function(a,b){var c=o.aoColumns[a];if(c.mData===a){var d=s(b,"sort")||s(b,"order"),e=s(b,"filter")||s(b,"search");if(d!==null||e!==null){c.mData={_:a+".display",sort:d!==null?a+".@data-"+d:k,type:d!==
|
|
||||||
null?a+".@data-"+d:k,filter:e!==null?a+".@data-"+e:k};ja(o,a)}}})}var w=o.oFeatures;e.bStateSave&&(w.bStateSave=!0,Kb(o,e),z(o,"aoDrawCallback",ya,"state_save"));if(e.aaSorting===k){t=o.aaSorting;g=0;for(j=t.length;g<j;g++)t[g][1]=o.aoColumns[g].asSorting[0]}xa(o);w.bSort&&z(o,"aoDrawCallback",function(){if(o.bSorted){var a=W(o),b={};h.each(a,function(a,c){b[c.src]=c.dir});u(o,null,"order",[o,a,b]);Jb(o)}});z(o,"aoDrawCallback",function(){(o.bSorted||y(o)==="ssp"||w.bDeferRender)&&xa(o)},"sc");g=
|
|
||||||
q.children("caption").each(function(){this._captionSide=q.css("caption-side")});j=q.children("thead");0===j.length&&(j=h("<thead/>").appendTo(this));o.nTHead=j[0];j=q.children("tbody");0===j.length&&(j=h("<tbody/>").appendTo(this));o.nTBody=j[0];j=q.children("tfoot");if(0===j.length&&0<g.length&&(""!==o.oScroll.sX||""!==o.oScroll.sY))j=h("<tfoot/>").appendTo(this);0===j.length||0===j.children().length?q.addClass(i.sNoFooter):0<j.length&&(o.nTFoot=j[0],da(o.aoFooter,o.nTFoot));if(e.aaData)for(g=0;g<
|
|
||||||
e.aaData.length;g++)N(o,e.aaData[g]);else(o.bDeferLoading||"dom"==y(o))&&ma(o,h(o.nTBody).children("tr"));o.aiDisplay=o.aiDisplayMaster.slice();o.bInitialised=!0;!1===n&&ga(o)}});b=null;return this};var Tb=[],w=Array.prototype,dc=function(a){var b,c,d=m.settings,e=h.map(d,function(a){return a.nTable});if(a){if(a.nTable&&a.oApi)return[a];if(a.nodeName&&"table"===a.nodeName.toLowerCase())return b=h.inArray(a,e),-1!==b?[d[b]]:null;if(a&&"function"===typeof a.settings)return a.settings().toArray();"string"===
|
|
||||||
typeof a?c=h(a):a instanceof h&&(c=a)}else return[];if(c)return c.map(function(){b=h.inArray(this,e);return-1!==b?d[b]:null}).toArray()};r=function(a,b){if(!(this instanceof r))return new r(a,b);var c=[],d=function(a){(a=dc(a))&&(c=c.concat(a))};if(h.isArray(a))for(var e=0,f=a.length;e<f;e++)d(a[e]);else d(a);this.context=pa(c);b&&h.merge(this,b);this.selector={rows:null,cols:null,opts:null};r.extend(this,this,Tb)};m.Api=r;h.extend(r.prototype,{any:function(){return 0!==this.count()},concat:w.concat,
|
|
||||||
context:[],count:function(){return this.flatten().length},each:function(a){for(var b=0,c=this.length;b<c;b++)a.call(this,this[b],b,this);return this},eq:function(a){var b=this.context;return b.length>a?new r(b[a],this[a]):null},filter:function(a){var b=[];if(w.filter)b=w.filter.call(this,a,this);else for(var c=0,d=this.length;c<d;c++)a.call(this,this[c],c,this)&&b.push(this[c]);return new r(this.context,b)},flatten:function(){var a=[];return new r(this.context,a.concat.apply(a,this.toArray()))},join:w.join,
|
|
||||||
indexOf:w.indexOf||function(a,b){for(var c=b||0,d=this.length;c<d;c++)if(this[c]===a)return c;return-1},iterator:function(a,b,c,d){var e=[],f,g,h,i,n,l=this.context,m,t,p=this.selector;"string"===typeof a&&(d=c,c=b,b=a,a=!1);g=0;for(h=l.length;g<h;g++){var o=new r(l[g]);if("table"===b)f=c.call(o,l[g],g),f!==k&&e.push(f);else if("columns"===b||"rows"===b)f=c.call(o,l[g],this[g],g),f!==k&&e.push(f);else if("column"===b||"column-rows"===b||"row"===b||"cell"===b){t=this[g];"column-rows"===b&&(m=Da(l[g],
|
|
||||||
p.opts));i=0;for(n=t.length;i<n;i++)f=t[i],f="cell"===b?c.call(o,l[g],f.row,f.column,g,i):c.call(o,l[g],f,g,i,m),f!==k&&e.push(f)}}return e.length||d?(a=new r(l,a?e.concat.apply([],e):e),b=a.selector,b.rows=p.rows,b.cols=p.cols,b.opts=p.opts,a):this},lastIndexOf:w.lastIndexOf||function(a,b){return this.indexOf.apply(this.toArray.reverse(),arguments)},length:0,map:function(a){var b=[];if(w.map)b=w.map.call(this,a,this);else for(var c=0,d=this.length;c<d;c++)b.push(a.call(this,this[c],c));return new r(this.context,
|
|
||||||
b)},pluck:function(a){return this.map(function(b){return b[a]})},pop:w.pop,push:w.push,reduce:w.reduce||function(a,b){return hb(this,a,b,0,this.length,1)},reduceRight:w.reduceRight||function(a,b){return hb(this,a,b,this.length-1,-1,-1)},reverse:w.reverse,selector:null,shift:w.shift,sort:w.sort,splice:w.splice,toArray:function(){return w.slice.call(this)},to$:function(){return h(this)},toJQuery:function(){return h(this)},unique:function(){return new r(this.context,pa(this))},unshift:w.unshift});r.extend=
|
|
||||||
function(a,b,c){if(c.length&&b&&(b instanceof r||b.__dt_wrapper)){var d,e,f,g=function(a,b,c){return function(){var d=b.apply(a,arguments);r.extend(d,d,c.methodExt);return d}};d=0;for(e=c.length;d<e;d++)f=c[d],b[f.name]="function"===typeof f.val?g(a,f.val,f):h.isPlainObject(f.val)?{}:f.val,b[f.name].__dt_wrapper=!0,r.extend(a,b[f.name],f.propExt)}};r.register=p=function(a,b){if(h.isArray(a))for(var c=0,d=a.length;c<d;c++)r.register(a[c],b);else for(var e=a.split("."),f=Tb,g,j,c=0,d=e.length;c<d;c++){g=
|
|
||||||
(j=-1!==e[c].indexOf("()"))?e[c].replace("()",""):e[c];var i;a:{i=0;for(var n=f.length;i<n;i++)if(f[i].name===g){i=f[i];break a}i=null}i||(i={name:g,val:{},methodExt:[],propExt:[]},f.push(i));c===d-1?i.val=b:f=j?i.methodExt:i.propExt}};r.registerPlural=s=function(a,b,c){r.register(a,c);r.register(b,function(){var a=c.apply(this,arguments);return a===this?this:a instanceof r?a.length?h.isArray(a[0])?new r(a.context,a[0]):a[0]:k:a})};p("tables()",function(a){var b;if(a){b=r;var c=this.context;if("number"===
|
|
||||||
typeof a)a=[c[a]];else var d=h.map(c,function(a){return a.nTable}),a=h(d).filter(a).map(function(){var a=h.inArray(this,d);return c[a]}).toArray();b=new b(a)}else b=this;return b});p("table()",function(a){var a=this.tables(a),b=a.context;return b.length?new r(b[0]):a});s("tables().nodes()","table().node()",function(){return this.iterator("table",function(a){return a.nTable},1)});s("tables().body()","table().body()",function(){return this.iterator("table",function(a){return a.nTBody},1)});s("tables().header()",
|
|
||||||
"table().header()",function(){return this.iterator("table",function(a){return a.nTHead},1)});s("tables().footer()","table().footer()",function(){return this.iterator("table",function(a){return a.nTFoot},1)});s("tables().containers()","table().container()",function(){return this.iterator("table",function(a){return a.nTableWrapper},1)});p("draw()",function(a){return this.iterator("table",function(b){"page"===a?O(b):("string"===typeof a&&(a="full-hold"===a?!1:!0),T(b,!1===a))})});p("page()",function(a){return a===
|
|
||||||
k?this.page.info().page:this.iterator("table",function(b){Ta(b,a)})});p("page.info()",function(){if(0===this.context.length)return k;var a=this.context[0],b=a._iDisplayStart,c=a.oFeatures.bPaginate?a._iDisplayLength:-1,d=a.fnRecordsDisplay(),e=-1===c;return{page:e?0:Math.floor(b/c),pages:e?1:Math.ceil(d/c),start:b,end:a.fnDisplayEnd(),length:c,recordsTotal:a.fnRecordsTotal(),recordsDisplay:d,serverSide:"ssp"===y(a)}});p("page.len()",function(a){return a===k?0!==this.context.length?this.context[0]._iDisplayLength:
|
|
||||||
k:this.iterator("table",function(b){Ra(b,a)})});var Ub=function(a,b,c){if(c){var d=new r(a);d.one("draw",function(){c(d.ajax.json())})}if("ssp"==y(a))T(a,b);else{C(a,!0);var e=a.jqXHR;e&&4!==e.readyState&&e.abort();ra(a,[],function(c){na(a);for(var c=sa(a,c),d=0,e=c.length;d<e;d++)N(a,c[d]);T(a,b);C(a,!1)})}};p("ajax.json()",function(){var a=this.context;if(0<a.length)return a[0].json});p("ajax.params()",function(){var a=this.context;if(0<a.length)return a[0].oAjaxData});p("ajax.reload()",function(a,
|
|
||||||
b){return this.iterator("table",function(c){Ub(c,!1===b,a)})});p("ajax.url()",function(a){var b=this.context;if(a===k){if(0===b.length)return k;b=b[0];return b.ajax?h.isPlainObject(b.ajax)?b.ajax.url:b.ajax:b.sAjaxSource}return this.iterator("table",function(b){h.isPlainObject(b.ajax)?b.ajax.url=a:b.ajax=a})});p("ajax.url().load()",function(a,b){return this.iterator("table",function(c){Ub(c,!1===b,a)})});var $a=function(a,b,c,d,e){var f=[],g,j,i,n,l,m;i=typeof b;if(!b||"string"===i||"function"===
|
|
||||||
i||b.length===k)b=[b];i=0;for(n=b.length;i<n;i++){j=b[i]&&b[i].split?b[i].split(","):[b[i]];l=0;for(m=j.length;l<m;l++)(g=c("string"===typeof j[l]?h.trim(j[l]):j[l]))&&g.length&&(f=f.concat(g))}a=v.selector[a];if(a.length){i=0;for(n=a.length;i<n;i++)f=a[i](d,e,f)}return pa(f)},ab=function(a){a||(a={});a.filter&&a.search===k&&(a.search=a.filter);return h.extend({search:"none",order:"current",page:"all"},a)},bb=function(a){for(var b=0,c=a.length;b<c;b++)if(0<a[b].length)return a[0]=a[b],a[0].length=
|
|
||||||
1,a.length=1,a.context=[a.context[b]],a;a.length=0;return a},Da=function(a,b){var c,d,e,f=[],g=a.aiDisplay;c=a.aiDisplayMaster;var j=b.search;d=b.order;e=b.page;if("ssp"==y(a))return"removed"===j?[]:X(0,c.length);if("current"==e){c=a._iDisplayStart;for(d=a.fnDisplayEnd();c<d;c++)f.push(g[c])}else if("current"==d||"applied"==d)f="none"==j?c.slice():"applied"==j?g.slice():h.map(c,function(a){return-1===h.inArray(a,g)?a:null});else if("index"==d||"original"==d){c=0;for(d=a.aoData.length;c<d;c++)"none"==
|
|
||||||
j?f.push(c):(e=h.inArray(c,g),(-1===e&&"removed"==j||0<=e&&"applied"==j)&&f.push(c))}return f};p("rows()",function(a,b){a===k?a="":h.isPlainObject(a)&&(b=a,a="");var b=ab(b),c=this.iterator("table",function(c){var e=b;return $a("row",a,function(a){var b=Pb(a);if(b!==null&&!e)return[b];var j=Da(c,e);if(b!==null&&h.inArray(b,j)!==-1)return[b];if(!a)return j;if(typeof a==="function")return h.map(j,function(b){var e=c.aoData[b];return a(b,e._aData,e.nTr)?b:null});b=Sb(ha(c.aoData,j,"nTr"));if(a.nodeName){if(a._DT_RowIndex!==
|
|
||||||
k)return[a._DT_RowIndex];if(a._DT_CellIndex)return[a._DT_CellIndex.row];b=h(a).closest("*[data-dt-row]");return b.length?[b.data("dt-row")]:[]}if(typeof a==="string"&&a.charAt(0)==="#"){j=c.aIds[a.replace(/^#/,"")];if(j!==k)return[j.idx]}return h(b).filter(a).map(function(){return this._DT_RowIndex}).toArray()},c,e)},1);c.selector.rows=a;c.selector.opts=b;return c});p("rows().nodes()",function(){return this.iterator("row",function(a,b){return a.aoData[b].nTr||k},1)});p("rows().data()",function(){return this.iterator(!0,
|
|
||||||
"rows",function(a,b){return ha(a.aoData,b,"_aData")},1)});s("rows().cache()","row().cache()",function(a){return this.iterator("row",function(b,c){var d=b.aoData[c];return"search"===a?d._aFilterData:d._aSortData},1)});s("rows().invalidate()","row().invalidate()",function(a){return this.iterator("row",function(b,c){ca(b,c,a)})});s("rows().indexes()","row().index()",function(){return this.iterator("row",function(a,b){return b},1)});s("rows().ids()","row().id()",function(a){for(var b=[],c=this.context,
|
|
||||||
d=0,e=c.length;d<e;d++)for(var f=0,g=this[d].length;f<g;f++){var h=c[d].rowIdFn(c[d].aoData[this[d][f]]._aData);b.push((!0===a?"#":"")+h)}return new r(c,b)});s("rows().remove()","row().remove()",function(){var a=this;this.iterator("row",function(b,c,d){var e=b.aoData,f=e[c],g,h,i,n,l;e.splice(c,1);g=0;for(h=e.length;g<h;g++)if(i=e[g],l=i.anCells,null!==i.nTr&&(i.nTr._DT_RowIndex=g),null!==l){i=0;for(n=l.length;i<n;i++)l[i]._DT_CellIndex.row=g}oa(b.aiDisplayMaster,c);oa(b.aiDisplay,c);oa(a[d],c,!1);
|
|
||||||
Sa(b);c=b.rowIdFn(f._aData);c!==k&&delete b.aIds[c]});this.iterator("table",function(a){for(var c=0,d=a.aoData.length;c<d;c++)a.aoData[c].idx=c});return this});p("rows.add()",function(a){var b=this.iterator("table",function(b){var c,f,g,h=[];f=0;for(g=a.length;f<g;f++)c=a[f],c.nodeName&&"TR"===c.nodeName.toUpperCase()?h.push(ma(b,c)[0]):h.push(N(b,c));return h},1),c=this.rows(-1);c.pop();h.merge(c,b);return c});p("row()",function(a,b){return bb(this.rows(a,b))});p("row().data()",function(a){var b=
|
|
||||||
this.context;if(a===k)return b.length&&this.length?b[0].aoData[this[0]]._aData:k;b[0].aoData[this[0]]._aData=a;ca(b[0],this[0],"data");return this});p("row().node()",function(){var a=this.context;return a.length&&this.length?a[0].aoData[this[0]].nTr||null:null});p("row.add()",function(a){a instanceof h&&a.length&&(a=a[0]);var b=this.iterator("table",function(b){return a.nodeName&&"TR"===a.nodeName.toUpperCase()?ma(b,a)[0]:N(b,a)});return this.row(b[0])});var cb=function(a,b){var c=a.context;if(c.length&&
|
|
||||||
(c=c[0].aoData[b!==k?b:a[0]])&&c._details)c._details.remove(),c._detailsShow=k,c._details=k},Vb=function(a,b){var c=a.context;if(c.length&&a.length){var d=c[0].aoData[a[0]];if(d._details){(d._detailsShow=b)?d._details.insertAfter(d.nTr):d._details.detach();var e=c[0],f=new r(e),g=e.aoData;f.off("draw.dt.DT_details column-visibility.dt.DT_details destroy.dt.DT_details");0<F(g,"_details").length&&(f.on("draw.dt.DT_details",function(a,b){e===b&&f.rows({page:"current"}).eq(0).each(function(a){a=g[a];
|
|
||||||
a._detailsShow&&a._details.insertAfter(a.nTr)})}),f.on("column-visibility.dt.DT_details",function(a,b){if(e===b)for(var c,d=aa(b),f=0,h=g.length;f<h;f++)c=g[f],c._details&&c._details.children("td[colspan]").attr("colspan",d)}),f.on("destroy.dt.DT_details",function(a,b){if(e===b)for(var c=0,d=g.length;c<d;c++)g[c]._details&&cb(f,c)}))}}};p("row().child()",function(a,b){var c=this.context;if(a===k)return c.length&&this.length?c[0].aoData[this[0]]._details:k;if(!0===a)this.child.show();else if(!1===
|
|
||||||
a)cb(this);else if(c.length&&this.length){var d=c[0],c=c[0].aoData[this[0]],e=[],f=function(a,b){if(h.isArray(a)||a instanceof h)for(var c=0,k=a.length;c<k;c++)f(a[c],b);else a.nodeName&&"tr"===a.nodeName.toLowerCase()?e.push(a):(c=h("<tr><td/></tr>").addClass(b),h("td",c).addClass(b).html(a)[0].colSpan=aa(d),e.push(c[0]))};f(a,b);c._details&&c._details.remove();c._details=h(e);c._detailsShow&&c._details.insertAfter(c.nTr)}return this});p(["row().child.show()","row().child().show()"],function(){Vb(this,
|
|
||||||
!0);return this});p(["row().child.hide()","row().child().hide()"],function(){Vb(this,!1);return this});p(["row().child.remove()","row().child().remove()"],function(){cb(this);return this});p("row().child.isShown()",function(){var a=this.context;return a.length&&this.length?a[0].aoData[this[0]]._detailsShow||!1:!1});var ec=/^(.+):(name|visIdx|visible)$/,Wb=function(a,b,c,d,e){for(var c=[],d=0,f=e.length;d<f;d++)c.push(B(a,e[d],b));return c};p("columns()",function(a,b){a===k?a="":h.isPlainObject(a)&&
|
|
||||||
(b=a,a="");var b=ab(b),c=this.iterator("table",function(c){var e=a,f=b,g=c.aoColumns,j=F(g,"sName"),i=F(g,"nTh");return $a("column",e,function(a){var b=Pb(a);if(a==="")return X(g.length);if(b!==null)return[b>=0?b:g.length+b];if(typeof a==="function"){var e=Da(c,f);return h.map(g,function(b,f){return a(f,Wb(c,f,0,0,e),i[f])?f:null})}var k=typeof a==="string"?a.match(ec):"";if(k)switch(k[2]){case "visIdx":case "visible":b=parseInt(k[1],10);if(b<0){var m=h.map(g,function(a,b){return a.bVisible?b:null});
|
|
||||||
return[m[m.length+b]]}return[Z(c,b)];case "name":return h.map(j,function(a,b){return a===k[1]?b:null});default:return[]}if(a.nodeName&&a._DT_CellIndex)return[a._DT_CellIndex.column];b=h(i).filter(a).map(function(){return h.inArray(this,i)}).toArray();if(b.length||!a.nodeName)return b;b=h(a).closest("*[data-dt-column]");return b.length?[b.data("dt-column")]:[]},c,f)},1);c.selector.cols=a;c.selector.opts=b;return c});s("columns().header()","column().header()",function(){return this.iterator("column",
|
|
||||||
function(a,b){return a.aoColumns[b].nTh},1)});s("columns().footer()","column().footer()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTf},1)});s("columns().data()","column().data()",function(){return this.iterator("column-rows",Wb,1)});s("columns().dataSrc()","column().dataSrc()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].mData},1)});s("columns().cache()","column().cache()",function(a){return this.iterator("column-rows",function(b,
|
|
||||||
c,d,e,f){return ha(b.aoData,f,"search"===a?"_aFilterData":"_aSortData",c)},1)});s("columns().nodes()","column().nodes()",function(){return this.iterator("column-rows",function(a,b,c,d,e){return ha(a.aoData,e,"anCells",b)},1)});s("columns().visible()","column().visible()",function(a,b){return this.iterator("column",function(c,d){if(a===k)return c.aoColumns[d].bVisible;var e=c.aoColumns,f=e[d],g=c.aoData,j,i,n;if(a!==k&&f.bVisible!==a){if(a){var l=h.inArray(!0,F(e,"bVisible"),d+1);j=0;for(i=g.length;j<
|
|
||||||
i;j++)n=g[j].nTr,e=g[j].anCells,n&&n.insertBefore(e[d],e[l]||null)}else h(F(c.aoData,"anCells",d)).detach();f.bVisible=a;ea(c,c.aoHeader);ea(c,c.aoFooter);(b===k||b)&&U(c);u(c,null,"column-visibility",[c,d,a,b]);ya(c)}})});s("columns().indexes()","column().index()",function(a){return this.iterator("column",function(b,c){return"visible"===a?$(b,c):c},1)});p("columns.adjust()",function(){return this.iterator("table",function(a){U(a)},1)});p("column.index()",function(a,b){if(0!==this.context.length){var c=
|
|
||||||
this.context[0];if("fromVisible"===a||"toData"===a)return Z(c,b);if("fromData"===a||"toVisible"===a)return $(c,b)}});p("column()",function(a,b){return bb(this.columns(a,b))});p("cells()",function(a,b,c){h.isPlainObject(a)&&(a.row===k?(c=a,a=null):(c=b,b=null));h.isPlainObject(b)&&(c=b,b=null);if(null===b||b===k)return this.iterator("table",function(b){var d=a,e=ab(c),f=b.aoData,g=Da(b,e),j=Sb(ha(f,g,"anCells")),i=h([].concat.apply([],j)),l,n=b.aoColumns.length,m,p,r,u,v,s;return $a("cell",d,function(a){var c=
|
|
||||||
typeof a==="function";if(a===null||a===k||c){m=[];p=0;for(r=g.length;p<r;p++){l=g[p];for(u=0;u<n;u++){v={row:l,column:u};if(c){s=f[l];a(v,B(b,l,u),s.anCells?s.anCells[u]:null)&&m.push(v)}else m.push(v)}}return m}if(h.isPlainObject(a))return[a];c=i.filter(a).map(function(a,b){return{row:b._DT_CellIndex.row,column:b._DT_CellIndex.column}}).toArray();if(c.length||!a.nodeName)return c;s=h(a).closest("*[data-dt-row]");return s.length?[{row:s.data("dt-row"),column:s.data("dt-column")}]:[]},b,e)});var d=
|
|
||||||
this.columns(b,c),e=this.rows(a,c),f,g,j,i,n,l=this.iterator("table",function(a,b){f=[];g=0;for(j=e[b].length;g<j;g++){i=0;for(n=d[b].length;i<n;i++)f.push({row:e[b][g],column:d[b][i]})}return f},1);h.extend(l.selector,{cols:b,rows:a,opts:c});return l});s("cells().nodes()","cell().node()",function(){return this.iterator("cell",function(a,b,c){return(a=a.aoData[b])&&a.anCells?a.anCells[c]:k},1)});p("cells().data()",function(){return this.iterator("cell",function(a,b,c){return B(a,b,c)},1)});s("cells().cache()",
|
|
||||||
"cell().cache()",function(a){a="search"===a?"_aFilterData":"_aSortData";return this.iterator("cell",function(b,c,d){return b.aoData[c][a][d]},1)});s("cells().render()","cell().render()",function(a){return this.iterator("cell",function(b,c,d){return B(b,c,d,a)},1)});s("cells().indexes()","cell().index()",function(){return this.iterator("cell",function(a,b,c){return{row:b,column:c,columnVisible:$(a,c)}},1)});s("cells().invalidate()","cell().invalidate()",function(a){return this.iterator("cell",function(b,
|
|
||||||
c,d){ca(b,c,a,d)})});p("cell()",function(a,b,c){return bb(this.cells(a,b,c))});p("cell().data()",function(a){var b=this.context,c=this[0];if(a===k)return b.length&&c.length?B(b[0],c[0].row,c[0].column):k;jb(b[0],c[0].row,c[0].column,a);ca(b[0],c[0].row,"data",c[0].column);return this});p("order()",function(a,b){var c=this.context;if(a===k)return 0!==c.length?c[0].aaSorting:k;"number"===typeof a?a=[[a,b]]:h.isArray(a[0])||(a=Array.prototype.slice.call(arguments));return this.iterator("table",function(b){b.aaSorting=
|
|
||||||
a.slice()})});p("order.listener()",function(a,b,c){return this.iterator("table",function(d){Oa(d,a,b,c)})});p("order.fixed()",function(a){if(!a){var b=this.context,b=b.length?b[0].aaSortingFixed:k;return h.isArray(b)?{pre:b}:b}return this.iterator("table",function(b){b.aaSortingFixed=h.extend(!0,{},a)})});p(["columns().order()","column().order()"],function(a){var b=this;return this.iterator("table",function(c,d){var e=[];h.each(b[d],function(b,c){e.push([c,a])});c.aaSorting=e})});p("search()",function(a,
|
|
||||||
b,c,d){var e=this.context;return a===k?0!==e.length?e[0].oPreviousSearch.sSearch:k:this.iterator("table",function(e){e.oFeatures.bFilter&&fa(e,h.extend({},e.oPreviousSearch,{sSearch:a+"",bRegex:null===b?!1:b,bSmart:null===c?!0:c,bCaseInsensitive:null===d?!0:d}),1)})});s("columns().search()","column().search()",function(a,b,c,d){return this.iterator("column",function(e,f){var g=e.aoPreSearchCols;if(a===k)return g[f].sSearch;e.oFeatures.bFilter&&(h.extend(g[f],{sSearch:a+"",bRegex:null===b?!1:b,bSmart:null===
|
|
||||||
c?!0:c,bCaseInsensitive:null===d?!0:d}),fa(e,e.oPreviousSearch,1))})});p("state()",function(){return this.context.length?this.context[0].oSavedState:null});p("state.clear()",function(){return this.iterator("table",function(a){a.fnStateSaveCallback.call(a.oInstance,a,{})})});p("state.loaded()",function(){return this.context.length?this.context[0].oLoadedState:null});p("state.save()",function(){return this.iterator("table",function(a){ya(a)})});m.versionCheck=m.fnVersionCheck=function(a){for(var b=
|
|
||||||
m.version.split("."),a=a.split("."),c,d,e=0,f=a.length;e<f;e++)if(c=parseInt(b[e],10)||0,d=parseInt(a[e],10)||0,c!==d)return c>d;return!0};m.isDataTable=m.fnIsDataTable=function(a){var b=h(a).get(0),c=!1;h.each(m.settings,function(a,e){var f=e.nScrollHead?h("table",e.nScrollHead)[0]:null,g=e.nScrollFoot?h("table",e.nScrollFoot)[0]:null;if(e.nTable===b||f===b||g===b)c=!0});return c};m.tables=m.fnTables=function(a){var b=!1;h.isPlainObject(a)&&(b=a.api,a=a.visible);var c=h.map(m.settings,function(b){if(!a||
|
|
||||||
a&&h(b.nTable).is(":visible"))return b.nTable});return b?new r(c):c};m.util={throttle:ua,escapeRegex:va};m.camelToHungarian=K;p("$()",function(a,b){var c=this.rows(b).nodes(),c=h(c);return h([].concat(c.filter(a).toArray(),c.find(a).toArray()))});h.each(["on","one","off"],function(a,b){p(b+"()",function(){var a=Array.prototype.slice.call(arguments);a[0].match(/\.dt\b/)||(a[0]+=".dt");var d=h(this.tables().nodes());d[b].apply(d,a);return this})});p("clear()",function(){return this.iterator("table",
|
|
||||||
function(a){na(a)})});p("settings()",function(){return new r(this.context,this.context)});p("init()",function(){var a=this.context;return a.length?a[0].oInit:null});p("data()",function(){return this.iterator("table",function(a){return F(a.aoData,"_aData")}).flatten()});p("destroy()",function(a){a=a||!1;return this.iterator("table",function(b){var c=b.nTableWrapper.parentNode,d=b.oClasses,e=b.nTable,f=b.nTBody,g=b.nTHead,j=b.nTFoot,i=h(e),f=h(f),k=h(b.nTableWrapper),l=h.map(b.aoData,function(a){return a.nTr}),
|
|
||||||
p;b.bDestroying=!0;u(b,"aoDestroyCallback","destroy",[b]);a||(new r(b)).columns().visible(!0);k.unbind(".DT").find(":not(tbody *)").unbind(".DT");h(D).unbind(".DT-"+b.sInstance);e!=g.parentNode&&(i.children("thead").detach(),i.append(g));j&&e!=j.parentNode&&(i.children("tfoot").detach(),i.append(j));b.aaSorting=[];b.aaSortingFixed=[];xa(b);h(l).removeClass(b.asStripeClasses.join(" "));h("th, td",g).removeClass(d.sSortable+" "+d.sSortableAsc+" "+d.sSortableDesc+" "+d.sSortableNone);b.bJUI&&(h("th span."+
|
|
||||||
d.sSortIcon+", td span."+d.sSortIcon,g).detach(),h("th, td",g).each(function(){var a=h("div."+d.sSortJUIWrapper,this);h(this).append(a.contents());a.detach()}));f.children().detach();f.append(l);g=a?"remove":"detach";i[g]();k[g]();!a&&c&&(c.insertBefore(e,b.nTableReinsertBefore),i.css("width",b.sDestroyWidth).removeClass(d.sTable),(p=b.asDestroyStripes.length)&&f.children().each(function(a){h(this).addClass(b.asDestroyStripes[a%p])}));c=h.inArray(b,m.settings);-1!==c&&m.settings.splice(c,1)})});h.each(["column",
|
|
||||||
"row","cell"],function(a,b){p(b+"s().every()",function(a){var d=this.selector.opts,e=this;return this.iterator(b,function(f,g,h,i,n){a.call(e[b](g,"cell"===b?h:d,"cell"===b?d:k),g,h,i,n)})})});p("i18n()",function(a,b,c){var d=this.context[0],a=Q(a)(d.oLanguage);a===k&&(a=b);c!==k&&h.isPlainObject(a)&&(a=a[c]!==k?a[c]:a._);return a.replace("%d",c)});m.version="1.10.11";m.settings=[];m.models={};m.models.oSearch={bCaseInsensitive:!0,sSearch:"",bRegex:!1,bSmart:!0};m.models.oRow={nTr:null,anCells:null,
|
|
||||||
_aData:[],_aSortData:null,_aFilterData:null,_sFilterRow:null,_sRowStripe:"",src:null,idx:-1};m.models.oColumn={idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null,sDefaultContent:null,sName:null,sSortDataType:"std",sSortingClass:null,sSortingClassJUI:null,sTitle:null,sType:null,sWidth:null,sWidthOrig:null};m.defaults=
|
|
||||||
{aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[],ajax:null,aLengthMenu:[10,25,50,100],aoColumns:null,aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bJQueryUI:!1,bLengthChange:!0,bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollCollapse:!1,bServerSide:!1,bSort:!0,bSortMulti:!0,bSortCellsTop:!1,bSortClasses:!0,bStateSave:!1,fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,fnFormatNumber:function(a){return a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,
|
|
||||||
this.oLanguage.sThousands)},fnHeaderCallback:null,fnInfoCallback:null,fnInitComplete:null,fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(a){try{return JSON.parse((-1===a.iStateDuration?sessionStorage:localStorage).getItem("DataTables_"+a.sInstance+"_"+location.pathname))}catch(b){}},fnStateLoadParams:null,fnStateLoaded:null,fnStateSaveCallback:function(a,b){try{(-1===a.iStateDuration?sessionStorage:localStorage).setItem("DataTables_"+a.sInstance+
|
|
||||||
"_"+location.pathname,JSON.stringify(b))}catch(c){}},fnStateSaveParams:null,iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last",sNext:"Next",sPrevious:"Previous"},sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",sInfoEmpty:"Showing 0 to 0 of 0 entries",
|
|
||||||
sInfoFiltered:"(filtered from _MAX_ total entries)",sInfoPostFix:"",sDecimal:"",sThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"Processing...",sSearch:"Search:",sSearchPlaceholder:"",sUrl:"",sZeroRecords:"No matching records found"},oSearch:h.extend({},m.models.oSearch),sAjaxDataProp:"data",sAjaxSource:null,sDom:"lfrtip",searchDelay:null,sPaginationType:"simple_numbers",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET",renderer:null,rowId:"DT_RowId"};
|
|
||||||
Y(m.defaults);m.defaults.column={aDataSort:null,iDataSort:-1,asSorting:["asc","desc"],bSearchable:!0,bSortable:!0,bVisible:!0,fnCreatedCell:null,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null};Y(m.defaults.column);m.models.oSettings={oFeatures:{bAutoWidth:null,bDeferRender:null,bFilter:null,bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,bSort:null,bSortMulti:null,
|
|
||||||
bSortClasses:null,bStateSave:null},oScroll:{bCollapse:null,iBarWidth:0,sX:null,sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1,bScrollbarLeft:!1,bBounding:!1,barWidth:0},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aIds:{},aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],aoFooterCallback:[],
|
|
||||||
aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[],aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",iStateDuration:0,aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,bAjaxDataGet:!0,jqXHR:null,json:k,oAjaxData:k,fnServerData:null,
|
|
||||||
aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,bJUI:null,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return"ssp"==y(this)?1*this._iRecordsTotal:this.aiDisplayMaster.length},fnRecordsDisplay:function(){return"ssp"==y(this)?1*this._iRecordsDisplay:this.aiDisplay.length},fnDisplayEnd:function(){var a=
|
|
||||||
this._iDisplayLength,b=this._iDisplayStart,c=b+a,d=this.aiDisplay.length,e=this.oFeatures,f=e.bPaginate;return e.bServerSide?!1===f||-1===a?b+d:Math.min(b+a,this._iRecordsDisplay):!f||c>d||-1===a?d:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{},rowIdFn:null,rowId:null};m.ext=v={buttons:{},classes:{},build:"bs/dt-1.10.11",errMode:"alert",feature:[],search:[],selector:{cell:[],column:[],row:[]},internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},
|
|
||||||
header:{}},order:{},type:{detect:[],search:{},order:{}},_unique:0,fnVersionCheck:m.fnVersionCheck,iApiIndex:0,oJUIClasses:{},sVersion:m.version};h.extend(v,{afnFiltering:v.search,aTypes:v.type.detect,ofnSearch:v.type.search,oSort:v.type.order,afnSortData:v.order,aoFeatures:v.feature,oApi:v.internal,oStdClasses:v.classes,oPagination:v.pager});h.extend(m.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",
|
|
||||||
sStripeEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_asc_disabled",sSortableDesc:"sorting_desc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",
|
|
||||||
sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""});var Ea="",Ea="",H=Ea+"ui-state-default",ia=Ea+"css_right ui-icon ui-icon-",Xb=Ea+"fg-toolbar ui-toolbar ui-widget-header ui-helper-clearfix";h.extend(m.ext.oJUIClasses,
|
|
||||||
m.ext.classes,{sPageButton:"fg-button ui-button "+H,sPageButtonActive:"ui-state-disabled",sPageButtonDisabled:"ui-state-disabled",sPaging:"dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi ui-buttonset-multi paging_",sSortAsc:H+" sorting_asc",sSortDesc:H+" sorting_desc",sSortable:H+" sorting",sSortableAsc:H+" sorting_asc_disabled",sSortableDesc:H+" sorting_desc_disabled",sSortableNone:H+" sorting_disabled",sSortJUIAsc:ia+"triangle-1-n",sSortJUIDesc:ia+"triangle-1-s",sSortJUI:ia+"carat-2-n-s",
|
|
||||||
sSortJUIAscAllowed:ia+"carat-1-n",sSortJUIDescAllowed:ia+"carat-1-s",sSortJUIWrapper:"DataTables_sort_wrapper",sSortIcon:"DataTables_sort_icon",sScrollHead:"dataTables_scrollHead "+H,sScrollFoot:"dataTables_scrollFoot "+H,sHeaderTH:H,sFooterTH:H,sJUIHeader:Xb+" ui-corner-tl ui-corner-tr",sJUIFooter:Xb+" ui-corner-bl ui-corner-br"});var Mb=m.ext.pager;h.extend(Mb,{simple:function(){return["previous","next"]},full:function(){return["first","previous","next","last"]},numbers:function(a,b){return[Aa(a,
|
|
||||||
b)]},simple_numbers:function(a,b){return["previous",Aa(a,b),"next"]},full_numbers:function(a,b){return["first","previous",Aa(a,b),"next","last"]},_numbers:Aa,numbers_length:7});h.extend(!0,m.ext.renderer,{pageButton:{_:function(a,b,c,d,e,f){var g=a.oClasses,j=a.oLanguage.oPaginate,i=a.oLanguage.oAria.paginate||{},k,l,m=0,p=function(b,d){var o,r,u,s,v=function(b){Ta(a,b.data.action,true)};o=0;for(r=d.length;o<r;o++){s=d[o];if(h.isArray(s)){u=h("<"+(s.DT_el||"div")+"/>").appendTo(b);p(u,s)}else{k=null;
|
|
||||||
l="";switch(s){case "ellipsis":b.append('<span class="ellipsis">…</span>');break;case "first":k=j.sFirst;l=s+(e>0?"":" "+g.sPageButtonDisabled);break;case "previous":k=j.sPrevious;l=s+(e>0?"":" "+g.sPageButtonDisabled);break;case "next":k=j.sNext;l=s+(e<f-1?"":" "+g.sPageButtonDisabled);break;case "last":k=j.sLast;l=s+(e<f-1?"":" "+g.sPageButtonDisabled);break;default:k=s+1;l=e===s?g.sPageButtonActive:""}if(k!==null){u=h("<a>",{"class":g.sPageButton+" "+l,"aria-controls":a.sTableId,"aria-label":i[s],
|
|
||||||
"data-dt-idx":m,tabindex:a.iTabIndex,id:c===0&&typeof s==="string"?a.sTableId+"_"+s:null}).html(k).appendTo(b);Wa(u,{action:s},v);m++}}}},r;try{r=h(b).find(I.activeElement).data("dt-idx")}catch(o){}p(h(b).empty(),d);r&&h(b).find("[data-dt-idx="+r+"]").focus()}}});h.extend(m.ext.type.detect,[function(a,b){var c=b.oLanguage.sDecimal;return Za(a,c)?"num"+c:null},function(a){if(a&&!(a instanceof Date)&&(!bc.test(a)||!cc.test(a)))return null;var b=Date.parse(a);return null!==b&&!isNaN(b)||M(a)?"date":
|
|
||||||
null},function(a,b){var c=b.oLanguage.sDecimal;return Za(a,c,!0)?"num-fmt"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Rb(a,c)?"html-num"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Rb(a,c,!0)?"html-num-fmt"+c:null},function(a){return M(a)||"string"===typeof a&&-1!==a.indexOf("<")?"html":null}]);h.extend(m.ext.type.search,{html:function(a){return M(a)?a:"string"===typeof a?a.replace(Ob," ").replace(Ca,""):""},string:function(a){return M(a)?a:"string"===typeof a?a.replace(Ob,
|
|
||||||
" "):a}});var Ba=function(a,b,c,d){if(0!==a&&(!a||"-"===a))return-Infinity;b&&(a=Qb(a,b));a.replace&&(c&&(a=a.replace(c,"")),d&&(a=a.replace(d,"")));return 1*a};h.extend(v.type.order,{"date-pre":function(a){return Date.parse(a)||0},"html-pre":function(a){return M(a)?"":a.replace?a.replace(/<.*?>/g,"").toLowerCase():a+""},"string-pre":function(a){return M(a)?"":"string"===typeof a?a.toLowerCase():!a.toString?"":a.toString()},"string-asc":function(a,b){return a<b?-1:a>b?1:0},"string-desc":function(a,
|
|
||||||
b){return a<b?1:a>b?-1:0}});db("");h.extend(!0,m.ext.renderer,{header:{_:function(a,b,c,d){h(a.nTable).on("order.dt.DT",function(e,f,g,h){if(a===f){e=c.idx;b.removeClass(c.sSortingClass+" "+d.sSortAsc+" "+d.sSortDesc).addClass(h[e]=="asc"?d.sSortAsc:h[e]=="desc"?d.sSortDesc:c.sSortingClass)}})},jqueryui:function(a,b,c,d){h("<div/>").addClass(d.sSortJUIWrapper).append(b.contents()).append(h("<span/>").addClass(d.sSortIcon+" "+c.sSortingClassJUI)).appendTo(b);h(a.nTable).on("order.dt.DT",function(e,
|
|
||||||
f,g,h){if(a===f){e=c.idx;b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass(h[e]=="asc"?d.sSortAsc:h[e]=="desc"?d.sSortDesc:c.sSortingClass);b.find("span."+d.sSortIcon).removeClass(d.sSortJUIAsc+" "+d.sSortJUIDesc+" "+d.sSortJUI+" "+d.sSortJUIAscAllowed+" "+d.sSortJUIDescAllowed).addClass(h[e]=="asc"?d.sSortJUIAsc:h[e]=="desc"?d.sSortJUIDesc:c.sSortingClassJUI)}})}}});var Yb=function(a){return"string"===typeof a?a.replace(/</g,"<").replace(/>/g,">").replace(/"/g,"""):a};m.render={number:function(a,
|
|
||||||
b,c,d,e){return{display:function(f){if("number"!==typeof f&&"string"!==typeof f)return f;var g=0>f?"-":"",h=parseFloat(f);if(isNaN(h))return Yb(f);f=Math.abs(h);h=parseInt(f,10);f=c?b+(f-h).toFixed(c).substring(2):"";return g+(d||"")+h.toString().replace(/\B(?=(\d{3})+(?!\d))/g,a)+f+(e||"")}}},text:function(){return{display:Yb}}};h.extend(m.ext.internal,{_fnExternApiFunc:Nb,_fnBuildAjax:ra,_fnAjaxUpdate:lb,_fnAjaxParameters:ub,_fnAjaxUpdateDraw:vb,_fnAjaxDataSrc:sa,_fnAddColumn:Ga,_fnColumnOptions:ja,
|
|
||||||
_fnAdjustColumnSizing:U,_fnVisibleToColumnIndex:Z,_fnColumnIndexToVisible:$,_fnVisbleColumns:aa,_fnGetColumns:la,_fnColumnTypes:Ia,_fnApplyColumnDefs:ib,_fnHungarianMap:Y,_fnCamelToHungarian:K,_fnLanguageCompat:Fa,_fnBrowserDetect:gb,_fnAddData:N,_fnAddTr:ma,_fnNodeToDataIndex:function(a,b){return b._DT_RowIndex!==k?b._DT_RowIndex:null},_fnNodeToColumnIndex:function(a,b,c){return h.inArray(c,a.aoData[b].anCells)},_fnGetCellData:B,_fnSetCellData:jb,_fnSplitObjNotation:La,_fnGetObjectDataFn:Q,_fnSetObjectDataFn:R,
|
|
||||||
_fnGetDataMaster:Ma,_fnClearTable:na,_fnDeleteIndex:oa,_fnInvalidate:ca,_fnGetRowElements:Ka,_fnCreateTr:Ja,_fnBuildHead:kb,_fnDrawHead:ea,_fnDraw:O,_fnReDraw:T,_fnAddOptionsHtml:nb,_fnDetectHeader:da,_fnGetUniqueThs:qa,_fnFeatureHtmlFilter:pb,_fnFilterComplete:fa,_fnFilterCustom:yb,_fnFilterColumn:xb,_fnFilter:wb,_fnFilterCreateSearch:Qa,_fnEscapeRegex:va,_fnFilterData:zb,_fnFeatureHtmlInfo:sb,_fnUpdateInfo:Cb,_fnInfoMacros:Db,_fnInitialise:ga,_fnInitComplete:ta,_fnLengthChange:Ra,_fnFeatureHtmlLength:ob,
|
|
||||||
_fnFeatureHtmlPaginate:tb,_fnPageChange:Ta,_fnFeatureHtmlProcessing:qb,_fnProcessingDisplay:C,_fnFeatureHtmlTable:rb,_fnScrollDraw:ka,_fnApplyToChildren:J,_fnCalculateColumnWidths:Ha,_fnThrottle:ua,_fnConvertToWidth:Fb,_fnGetWidestNode:Gb,_fnGetMaxLenString:Hb,_fnStringToCss:x,_fnSortFlatten:W,_fnSort:mb,_fnSortAria:Jb,_fnSortListener:Va,_fnSortAttachListener:Oa,_fnSortingClasses:xa,_fnSortData:Ib,_fnSaveState:ya,_fnLoadState:Kb,_fnSettingsFromNode:za,_fnLog:L,_fnMap:E,_fnBindAction:Wa,_fnCallbackReg:z,
|
|
||||||
_fnCallbackFire:u,_fnLengthOverflow:Sa,_fnRenderer:Pa,_fnDataSource:y,_fnRowAttributes:Na,_fnCalculateEnd:function(){}});h.fn.dataTable=m;m.$=h;h.fn.dataTableSettings=m.settings;h.fn.dataTableExt=m.ext;h.fn.DataTable=function(a){return h(this).dataTable(a).api()};h.each(m,function(a,b){h.fn.DataTable[a]=b});return h.fn.dataTable});
|
|
||||||
|
|
||||||
|
|
||||||
/*!
|
|
||||||
DataTables Bootstrap 3 integration
|
|
||||||
©2011-2015 SpryMedia Ltd - datatables.net/license
|
|
||||||
*/
|
|
||||||
(function(b){"function"===typeof define&&define.amd?define(["jquery","datatables.net"],function(a){return b(a,window,document)}):"object"===typeof exports?module.exports=function(a,d){a||(a=window);if(!d||!d.fn.dataTable)d=require("datatables.net")(a,d).$;return b(d,a,a.document)}:b(jQuery,window,document)})(function(b,a,d){var f=b.fn.dataTable;b.extend(!0,f.defaults,{dom:"<'row'<'col-sm-6'l><'col-sm-6'f>><'row'<'col-sm-12'tr>><'row'<'col-sm-5'i><'col-sm-7'p>>",renderer:"bootstrap"});b.extend(f.ext.classes,
|
|
||||||
{sWrapper:"dataTables_wrapper form-inline dt-bootstrap",sFilterInput:"form-control input-sm",sLengthSelect:"form-control input-sm",sProcessing:"dataTables_processing panel panel-default"});f.ext.renderer.pageButton.bootstrap=function(a,h,r,m,j,n){var o=new f.Api(a),s=a.oClasses,k=a.oLanguage.oPaginate,t=a.oLanguage.oAria.paginate||{},e,g,p=0,q=function(d,f){var l,h,i,c,m=function(a){a.preventDefault();!b(a.currentTarget).hasClass("disabled")&&o.page()!=a.data.action&&o.page(a.data.action).draw("page")};
|
|
||||||
l=0;for(h=f.length;l<h;l++)if(c=f[l],b.isArray(c))q(d,c);else{g=e="";switch(c){case "ellipsis":e="…";g="disabled";break;case "first":e=k.sFirst;g=c+(0<j?"":" disabled");break;case "previous":e=k.sPrevious;g=c+(0<j?"":" disabled");break;case "next":e=k.sNext;g=c+(j<n-1?"":" disabled");break;case "last":e=k.sLast;g=c+(j<n-1?"":" disabled");break;default:e=c+1,g=j===c?"active":""}e&&(i=b("<li>",{"class":s.sPageButton+" "+g,id:0===r&&"string"===typeof c?a.sTableId+"_"+c:null}).append(b("<a>",{href:"#",
|
|
||||||
"aria-controls":a.sTableId,"aria-label":t[c],"data-dt-idx":p,tabindex:a.iTabIndex}).html(e)).appendTo(d),a.oApi._fnBindAction(i,{action:c},m),p++)}},i;try{i=b(h).find(d.activeElement).data("dt-idx")}catch(u){}q(b(h).empty().html('<ul class="pagination"/>').children("ul"),m);i&&b(h).find("[data-dt-idx="+i+"]").focus()};return f});
|
|
||||||
|
|
||||||
|
|
|
@ -1,472 +0,0 @@
|
||||||
/*!
|
|
||||||
* Datepicker for Bootstrap v1.5.1 (https://github.com/eternicode/bootstrap-datepicker)
|
|
||||||
*
|
|
||||||
* Copyright 2012 Stefan Petre
|
|
||||||
* Improvements by Andrew Rowls
|
|
||||||
* Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
|
||||||
*/
|
|
||||||
.datepicker {
|
|
||||||
padding: 4px;
|
|
||||||
-webkit-border-radius: 4px;
|
|
||||||
-moz-border-radius: 4px;
|
|
||||||
border-radius: 4px;
|
|
||||||
direction: ltr;
|
|
||||||
}
|
|
||||||
.datepicker-inline {
|
|
||||||
width: 220px;
|
|
||||||
}
|
|
||||||
.datepicker.datepicker-rtl {
|
|
||||||
direction: rtl;
|
|
||||||
}
|
|
||||||
.datepicker.datepicker-rtl table tr td span {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown {
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown:before {
|
|
||||||
content: '';
|
|
||||||
display: inline-block;
|
|
||||||
border-left: 7px solid transparent;
|
|
||||||
border-right: 7px solid transparent;
|
|
||||||
border-bottom: 7px solid #999999;
|
|
||||||
border-top: 0;
|
|
||||||
border-bottom-color: rgba(0, 0, 0, 0.2);
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown:after {
|
|
||||||
content: '';
|
|
||||||
display: inline-block;
|
|
||||||
border-left: 6px solid transparent;
|
|
||||||
border-right: 6px solid transparent;
|
|
||||||
border-bottom: 6px solid #ffffff;
|
|
||||||
border-top: 0;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-left:before {
|
|
||||||
left: 6px;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-left:after {
|
|
||||||
left: 7px;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-right:before {
|
|
||||||
right: 6px;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-right:after {
|
|
||||||
right: 7px;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-bottom:before {
|
|
||||||
top: -7px;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-bottom:after {
|
|
||||||
top: -6px;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-top:before {
|
|
||||||
bottom: -7px;
|
|
||||||
border-bottom: 0;
|
|
||||||
border-top: 7px solid #999999;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-top:after {
|
|
||||||
bottom: -6px;
|
|
||||||
border-bottom: 0;
|
|
||||||
border-top: 6px solid #ffffff;
|
|
||||||
}
|
|
||||||
.datepicker > div {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.datepicker table {
|
|
||||||
margin: 0;
|
|
||||||
-webkit-touch-callout: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-khtml-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
.datepicker td,
|
|
||||||
.datepicker th {
|
|
||||||
text-align: center;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
-webkit-border-radius: 4px;
|
|
||||||
-moz-border-radius: 4px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
.table-striped .datepicker table tr td,
|
|
||||||
.table-striped .datepicker table tr th {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.day:hover,
|
|
||||||
.datepicker table tr td.day.focused {
|
|
||||||
background: #eeeeee;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.old,
|
|
||||||
.datepicker table tr td.new {
|
|
||||||
color: #999999;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.disabled,
|
|
||||||
.datepicker table tr td.disabled:hover {
|
|
||||||
background: none;
|
|
||||||
color: #999999;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.highlighted {
|
|
||||||
background: #d9edf7;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.today,
|
|
||||||
.datepicker table tr td.today:hover,
|
|
||||||
.datepicker table tr td.today.disabled,
|
|
||||||
.datepicker table tr td.today.disabled:hover {
|
|
||||||
background-color: #fde19a;
|
|
||||||
background-image: -moz-linear-gradient(to bottom, #fdd49a, #fdf59a);
|
|
||||||
background-image: -ms-linear-gradient(to bottom, #fdd49a, #fdf59a);
|
|
||||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fdd49a), to(#fdf59a));
|
|
||||||
background-image: -webkit-linear-gradient(to bottom, #fdd49a, #fdf59a);
|
|
||||||
background-image: -o-linear-gradient(to bottom, #fdd49a, #fdf59a);
|
|
||||||
background-image: linear-gradient(to bottom, #fdd49a, #fdf59a);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0);
|
|
||||||
border-color: #fdf59a #fdf59a #fbed50;
|
|
||||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.today:hover,
|
|
||||||
.datepicker table tr td.today:hover:hover,
|
|
||||||
.datepicker table tr td.today.disabled:hover,
|
|
||||||
.datepicker table tr td.today.disabled:hover:hover,
|
|
||||||
.datepicker table tr td.today:active,
|
|
||||||
.datepicker table tr td.today:hover:active,
|
|
||||||
.datepicker table tr td.today.disabled:active,
|
|
||||||
.datepicker table tr td.today.disabled:hover:active,
|
|
||||||
.datepicker table tr td.today.active,
|
|
||||||
.datepicker table tr td.today:hover.active,
|
|
||||||
.datepicker table tr td.today.disabled.active,
|
|
||||||
.datepicker table tr td.today.disabled:hover.active,
|
|
||||||
.datepicker table tr td.today.disabled,
|
|
||||||
.datepicker table tr td.today:hover.disabled,
|
|
||||||
.datepicker table tr td.today.disabled.disabled,
|
|
||||||
.datepicker table tr td.today.disabled:hover.disabled,
|
|
||||||
.datepicker table tr td.today[disabled],
|
|
||||||
.datepicker table tr td.today:hover[disabled],
|
|
||||||
.datepicker table tr td.today.disabled[disabled],
|
|
||||||
.datepicker table tr td.today.disabled:hover[disabled] {
|
|
||||||
background-color: #fdf59a;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.today:active,
|
|
||||||
.datepicker table tr td.today:hover:active,
|
|
||||||
.datepicker table tr td.today.disabled:active,
|
|
||||||
.datepicker table tr td.today.disabled:hover:active,
|
|
||||||
.datepicker table tr td.today.active,
|
|
||||||
.datepicker table tr td.today:hover.active,
|
|
||||||
.datepicker table tr td.today.disabled.active,
|
|
||||||
.datepicker table tr td.today.disabled:hover.active {
|
|
||||||
background-color: #fbf069 \9;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.today:hover:hover {
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.today.active:hover {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range,
|
|
||||||
.datepicker table tr td.range:hover,
|
|
||||||
.datepicker table tr td.range.disabled,
|
|
||||||
.datepicker table tr td.range.disabled:hover {
|
|
||||||
background: #eeeeee;
|
|
||||||
-webkit-border-radius: 0;
|
|
||||||
-moz-border-radius: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.today,
|
|
||||||
.datepicker table tr td.range.today:hover,
|
|
||||||
.datepicker table tr td.range.today.disabled,
|
|
||||||
.datepicker table tr td.range.today.disabled:hover {
|
|
||||||
background-color: #f3d17a;
|
|
||||||
background-image: -moz-linear-gradient(to bottom, #f3c17a, #f3e97a);
|
|
||||||
background-image: -ms-linear-gradient(to bottom, #f3c17a, #f3e97a);
|
|
||||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f3c17a), to(#f3e97a));
|
|
||||||
background-image: -webkit-linear-gradient(to bottom, #f3c17a, #f3e97a);
|
|
||||||
background-image: -o-linear-gradient(to bottom, #f3c17a, #f3e97a);
|
|
||||||
background-image: linear-gradient(to bottom, #f3c17a, #f3e97a);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3c17a', endColorstr='#f3e97a', GradientType=0);
|
|
||||||
border-color: #f3e97a #f3e97a #edde34;
|
|
||||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
|
|
||||||
-webkit-border-radius: 0;
|
|
||||||
-moz-border-radius: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.today:hover,
|
|
||||||
.datepicker table tr td.range.today:hover:hover,
|
|
||||||
.datepicker table tr td.range.today.disabled:hover,
|
|
||||||
.datepicker table tr td.range.today.disabled:hover:hover,
|
|
||||||
.datepicker table tr td.range.today:active,
|
|
||||||
.datepicker table tr td.range.today:hover:active,
|
|
||||||
.datepicker table tr td.range.today.disabled:active,
|
|
||||||
.datepicker table tr td.range.today.disabled:hover:active,
|
|
||||||
.datepicker table tr td.range.today.active,
|
|
||||||
.datepicker table tr td.range.today:hover.active,
|
|
||||||
.datepicker table tr td.range.today.disabled.active,
|
|
||||||
.datepicker table tr td.range.today.disabled:hover.active,
|
|
||||||
.datepicker table tr td.range.today.disabled,
|
|
||||||
.datepicker table tr td.range.today:hover.disabled,
|
|
||||||
.datepicker table tr td.range.today.disabled.disabled,
|
|
||||||
.datepicker table tr td.range.today.disabled:hover.disabled,
|
|
||||||
.datepicker table tr td.range.today[disabled],
|
|
||||||
.datepicker table tr td.range.today:hover[disabled],
|
|
||||||
.datepicker table tr td.range.today.disabled[disabled],
|
|
||||||
.datepicker table tr td.range.today.disabled:hover[disabled] {
|
|
||||||
background-color: #f3e97a;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.today:active,
|
|
||||||
.datepicker table tr td.range.today:hover:active,
|
|
||||||
.datepicker table tr td.range.today.disabled:active,
|
|
||||||
.datepicker table tr td.range.today.disabled:hover:active,
|
|
||||||
.datepicker table tr td.range.today.active,
|
|
||||||
.datepicker table tr td.range.today:hover.active,
|
|
||||||
.datepicker table tr td.range.today.disabled.active,
|
|
||||||
.datepicker table tr td.range.today.disabled:hover.active {
|
|
||||||
background-color: #efe24b \9;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.selected,
|
|
||||||
.datepicker table tr td.selected:hover,
|
|
||||||
.datepicker table tr td.selected.disabled,
|
|
||||||
.datepicker table tr td.selected.disabled:hover {
|
|
||||||
background-color: #9e9e9e;
|
|
||||||
background-image: -moz-linear-gradient(to bottom, #b3b3b3, #808080);
|
|
||||||
background-image: -ms-linear-gradient(to bottom, #b3b3b3, #808080);
|
|
||||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#b3b3b3), to(#808080));
|
|
||||||
background-image: -webkit-linear-gradient(to bottom, #b3b3b3, #808080);
|
|
||||||
background-image: -o-linear-gradient(to bottom, #b3b3b3, #808080);
|
|
||||||
background-image: linear-gradient(to bottom, #b3b3b3, #808080);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#b3b3b3', endColorstr='#808080', GradientType=0);
|
|
||||||
border-color: #808080 #808080 #595959;
|
|
||||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
|
|
||||||
color: #fff;
|
|
||||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
.datepicker table tr td.selected:hover,
|
|
||||||
.datepicker table tr td.selected:hover:hover,
|
|
||||||
.datepicker table tr td.selected.disabled:hover,
|
|
||||||
.datepicker table tr td.selected.disabled:hover:hover,
|
|
||||||
.datepicker table tr td.selected:active,
|
|
||||||
.datepicker table tr td.selected:hover:active,
|
|
||||||
.datepicker table tr td.selected.disabled:active,
|
|
||||||
.datepicker table tr td.selected.disabled:hover:active,
|
|
||||||
.datepicker table tr td.selected.active,
|
|
||||||
.datepicker table tr td.selected:hover.active,
|
|
||||||
.datepicker table tr td.selected.disabled.active,
|
|
||||||
.datepicker table tr td.selected.disabled:hover.active,
|
|
||||||
.datepicker table tr td.selected.disabled,
|
|
||||||
.datepicker table tr td.selected:hover.disabled,
|
|
||||||
.datepicker table tr td.selected.disabled.disabled,
|
|
||||||
.datepicker table tr td.selected.disabled:hover.disabled,
|
|
||||||
.datepicker table tr td.selected[disabled],
|
|
||||||
.datepicker table tr td.selected:hover[disabled],
|
|
||||||
.datepicker table tr td.selected.disabled[disabled],
|
|
||||||
.datepicker table tr td.selected.disabled:hover[disabled] {
|
|
||||||
background-color: #808080;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.selected:active,
|
|
||||||
.datepicker table tr td.selected:hover:active,
|
|
||||||
.datepicker table tr td.selected.disabled:active,
|
|
||||||
.datepicker table tr td.selected.disabled:hover:active,
|
|
||||||
.datepicker table tr td.selected.active,
|
|
||||||
.datepicker table tr td.selected:hover.active,
|
|
||||||
.datepicker table tr td.selected.disabled.active,
|
|
||||||
.datepicker table tr td.selected.disabled:hover.active {
|
|
||||||
background-color: #666666 \9;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.active,
|
|
||||||
.datepicker table tr td.active:hover,
|
|
||||||
.datepicker table tr td.active.disabled,
|
|
||||||
.datepicker table tr td.active.disabled:hover {
|
|
||||||
background-color: #006dcc;
|
|
||||||
background-image: -moz-linear-gradient(to bottom, #0088cc, #0044cc);
|
|
||||||
background-image: -ms-linear-gradient(to bottom, #0088cc, #0044cc);
|
|
||||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
|
|
||||||
background-image: -webkit-linear-gradient(to bottom, #0088cc, #0044cc);
|
|
||||||
background-image: -o-linear-gradient(to bottom, #0088cc, #0044cc);
|
|
||||||
background-image: linear-gradient(to bottom, #0088cc, #0044cc);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
|
|
||||||
border-color: #0044cc #0044cc #002a80;
|
|
||||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
|
|
||||||
color: #fff;
|
|
||||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
.datepicker table tr td.active:hover,
|
|
||||||
.datepicker table tr td.active:hover:hover,
|
|
||||||
.datepicker table tr td.active.disabled:hover,
|
|
||||||
.datepicker table tr td.active.disabled:hover:hover,
|
|
||||||
.datepicker table tr td.active:active,
|
|
||||||
.datepicker table tr td.active:hover:active,
|
|
||||||
.datepicker table tr td.active.disabled:active,
|
|
||||||
.datepicker table tr td.active.disabled:hover:active,
|
|
||||||
.datepicker table tr td.active.active,
|
|
||||||
.datepicker table tr td.active:hover.active,
|
|
||||||
.datepicker table tr td.active.disabled.active,
|
|
||||||
.datepicker table tr td.active.disabled:hover.active,
|
|
||||||
.datepicker table tr td.active.disabled,
|
|
||||||
.datepicker table tr td.active:hover.disabled,
|
|
||||||
.datepicker table tr td.active.disabled.disabled,
|
|
||||||
.datepicker table tr td.active.disabled:hover.disabled,
|
|
||||||
.datepicker table tr td.active[disabled],
|
|
||||||
.datepicker table tr td.active:hover[disabled],
|
|
||||||
.datepicker table tr td.active.disabled[disabled],
|
|
||||||
.datepicker table tr td.active.disabled:hover[disabled] {
|
|
||||||
background-color: #0044cc;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.active:active,
|
|
||||||
.datepicker table tr td.active:hover:active,
|
|
||||||
.datepicker table tr td.active.disabled:active,
|
|
||||||
.datepicker table tr td.active.disabled:hover:active,
|
|
||||||
.datepicker table tr td.active.active,
|
|
||||||
.datepicker table tr td.active:hover.active,
|
|
||||||
.datepicker table tr td.active.disabled.active,
|
|
||||||
.datepicker table tr td.active.disabled:hover.active {
|
|
||||||
background-color: #003399 \9;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span {
|
|
||||||
display: block;
|
|
||||||
width: 23%;
|
|
||||||
height: 54px;
|
|
||||||
line-height: 54px;
|
|
||||||
float: left;
|
|
||||||
margin: 1%;
|
|
||||||
cursor: pointer;
|
|
||||||
-webkit-border-radius: 4px;
|
|
||||||
-moz-border-radius: 4px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span:hover {
|
|
||||||
background: #eeeeee;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span.disabled,
|
|
||||||
.datepicker table tr td span.disabled:hover {
|
|
||||||
background: none;
|
|
||||||
color: #999999;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span.active,
|
|
||||||
.datepicker table tr td span.active:hover,
|
|
||||||
.datepicker table tr td span.active.disabled,
|
|
||||||
.datepicker table tr td span.active.disabled:hover {
|
|
||||||
background-color: #006dcc;
|
|
||||||
background-image: -moz-linear-gradient(to bottom, #0088cc, #0044cc);
|
|
||||||
background-image: -ms-linear-gradient(to bottom, #0088cc, #0044cc);
|
|
||||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
|
|
||||||
background-image: -webkit-linear-gradient(to bottom, #0088cc, #0044cc);
|
|
||||||
background-image: -o-linear-gradient(to bottom, #0088cc, #0044cc);
|
|
||||||
background-image: linear-gradient(to bottom, #0088cc, #0044cc);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
|
|
||||||
border-color: #0044cc #0044cc #002a80;
|
|
||||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
|
|
||||||
color: #fff;
|
|
||||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
.datepicker table tr td span.active:hover,
|
|
||||||
.datepicker table tr td span.active:hover:hover,
|
|
||||||
.datepicker table tr td span.active.disabled:hover,
|
|
||||||
.datepicker table tr td span.active.disabled:hover:hover,
|
|
||||||
.datepicker table tr td span.active:active,
|
|
||||||
.datepicker table tr td span.active:hover:active,
|
|
||||||
.datepicker table tr td span.active.disabled:active,
|
|
||||||
.datepicker table tr td span.active.disabled:hover:active,
|
|
||||||
.datepicker table tr td span.active.active,
|
|
||||||
.datepicker table tr td span.active:hover.active,
|
|
||||||
.datepicker table tr td span.active.disabled.active,
|
|
||||||
.datepicker table tr td span.active.disabled:hover.active,
|
|
||||||
.datepicker table tr td span.active.disabled,
|
|
||||||
.datepicker table tr td span.active:hover.disabled,
|
|
||||||
.datepicker table tr td span.active.disabled.disabled,
|
|
||||||
.datepicker table tr td span.active.disabled:hover.disabled,
|
|
||||||
.datepicker table tr td span.active[disabled],
|
|
||||||
.datepicker table tr td span.active:hover[disabled],
|
|
||||||
.datepicker table tr td span.active.disabled[disabled],
|
|
||||||
.datepicker table tr td span.active.disabled:hover[disabled] {
|
|
||||||
background-color: #0044cc;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span.active:active,
|
|
||||||
.datepicker table tr td span.active:hover:active,
|
|
||||||
.datepicker table tr td span.active.disabled:active,
|
|
||||||
.datepicker table tr td span.active.disabled:hover:active,
|
|
||||||
.datepicker table tr td span.active.active,
|
|
||||||
.datepicker table tr td span.active:hover.active,
|
|
||||||
.datepicker table tr td span.active.disabled.active,
|
|
||||||
.datepicker table tr td span.active.disabled:hover.active {
|
|
||||||
background-color: #003399 \9;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span.old,
|
|
||||||
.datepicker table tr td span.new {
|
|
||||||
color: #999999;
|
|
||||||
}
|
|
||||||
.datepicker .datepicker-switch {
|
|
||||||
width: 145px;
|
|
||||||
}
|
|
||||||
.datepicker .datepicker-switch,
|
|
||||||
.datepicker .prev,
|
|
||||||
.datepicker .next,
|
|
||||||
.datepicker tfoot tr th {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.datepicker .datepicker-switch:hover,
|
|
||||||
.datepicker .prev:hover,
|
|
||||||
.datepicker .next:hover,
|
|
||||||
.datepicker tfoot tr th:hover {
|
|
||||||
background: #eeeeee;
|
|
||||||
}
|
|
||||||
.datepicker .cw {
|
|
||||||
font-size: 10px;
|
|
||||||
width: 12px;
|
|
||||||
padding: 0 2px 0 5px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
.input-append.date .add-on,
|
|
||||||
.input-prepend.date .add-on {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.input-append.date .add-on i,
|
|
||||||
.input-prepend.date .add-on i {
|
|
||||||
margin-top: 3px;
|
|
||||||
}
|
|
||||||
.input-daterange input {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.input-daterange input:first-child {
|
|
||||||
-webkit-border-radius: 3px 0 0 3px;
|
|
||||||
-moz-border-radius: 3px 0 0 3px;
|
|
||||||
border-radius: 3px 0 0 3px;
|
|
||||||
}
|
|
||||||
.input-daterange input:last-child {
|
|
||||||
-webkit-border-radius: 0 3px 3px 0;
|
|
||||||
-moz-border-radius: 0 3px 3px 0;
|
|
||||||
border-radius: 0 3px 3px 0;
|
|
||||||
}
|
|
||||||
.input-daterange .add-on {
|
|
||||||
display: inline-block;
|
|
||||||
width: auto;
|
|
||||||
min-width: 16px;
|
|
||||||
height: 18px;
|
|
||||||
padding: 4px 5px;
|
|
||||||
font-weight: normal;
|
|
||||||
line-height: 18px;
|
|
||||||
text-align: center;
|
|
||||||
text-shadow: 0 1px 0 #ffffff;
|
|
||||||
vertical-align: middle;
|
|
||||||
background-color: #eeeeee;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
margin-left: -5px;
|
|
||||||
margin-right: -5px;
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
|
@ -1,505 +0,0 @@
|
||||||
/*!
|
|
||||||
* Datepicker for Bootstrap v1.5.1 (https://github.com/eternicode/bootstrap-datepicker)
|
|
||||||
*
|
|
||||||
* Copyright 2012 Stefan Petre
|
|
||||||
* Improvements by Andrew Rowls
|
|
||||||
* Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
|
||||||
*/
|
|
||||||
.datepicker {
|
|
||||||
padding: 4px;
|
|
||||||
-webkit-border-radius: 4px;
|
|
||||||
-moz-border-radius: 4px;
|
|
||||||
border-radius: 4px;
|
|
||||||
direction: ltr;
|
|
||||||
}
|
|
||||||
.datepicker-inline {
|
|
||||||
width: 220px;
|
|
||||||
}
|
|
||||||
.datepicker.datepicker-rtl {
|
|
||||||
direction: rtl;
|
|
||||||
}
|
|
||||||
.datepicker.datepicker-rtl table tr td span {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown {
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown:before {
|
|
||||||
content: '';
|
|
||||||
display: inline-block;
|
|
||||||
border-left: 7px solid transparent;
|
|
||||||
border-right: 7px solid transparent;
|
|
||||||
border-bottom: 7px solid #999999;
|
|
||||||
border-top: 0;
|
|
||||||
border-bottom-color: rgba(0, 0, 0, 0.2);
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown:after {
|
|
||||||
content: '';
|
|
||||||
display: inline-block;
|
|
||||||
border-left: 6px solid transparent;
|
|
||||||
border-right: 6px solid transparent;
|
|
||||||
border-bottom: 6px solid #ffffff;
|
|
||||||
border-top: 0;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-left:before {
|
|
||||||
left: 6px;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-left:after {
|
|
||||||
left: 7px;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-right:before {
|
|
||||||
right: 6px;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-right:after {
|
|
||||||
right: 7px;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-bottom:before {
|
|
||||||
top: -7px;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-bottom:after {
|
|
||||||
top: -6px;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-top:before {
|
|
||||||
bottom: -7px;
|
|
||||||
border-bottom: 0;
|
|
||||||
border-top: 7px solid #999999;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-top:after {
|
|
||||||
bottom: -6px;
|
|
||||||
border-bottom: 0;
|
|
||||||
border-top: 6px solid #ffffff;
|
|
||||||
}
|
|
||||||
.datepicker > div {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.datepicker table {
|
|
||||||
margin: 0;
|
|
||||||
-webkit-touch-callout: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-khtml-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
.datepicker td,
|
|
||||||
.datepicker th {
|
|
||||||
text-align: center;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
-webkit-border-radius: 4px;
|
|
||||||
-moz-border-radius: 4px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
.table-striped .datepicker table tr td,
|
|
||||||
.table-striped .datepicker table tr th {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.day:hover,
|
|
||||||
.datepicker table tr td.day.focused {
|
|
||||||
background: #eeeeee;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.old,
|
|
||||||
.datepicker table tr td.new {
|
|
||||||
color: #999999;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.disabled,
|
|
||||||
.datepicker table tr td.disabled:hover {
|
|
||||||
background: none;
|
|
||||||
color: #999999;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.highlighted {
|
|
||||||
background: #d9edf7;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.today,
|
|
||||||
.datepicker table tr td.today:hover,
|
|
||||||
.datepicker table tr td.today.disabled,
|
|
||||||
.datepicker table tr td.today.disabled:hover {
|
|
||||||
background-color: #fde19a;
|
|
||||||
background-image: -moz-linear-gradient(to bottom, #fdd49a, #fdf59a);
|
|
||||||
background-image: -ms-linear-gradient(to bottom, #fdd49a, #fdf59a);
|
|
||||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fdd49a), to(#fdf59a));
|
|
||||||
background-image: -webkit-linear-gradient(to bottom, #fdd49a, #fdf59a);
|
|
||||||
background-image: -o-linear-gradient(to bottom, #fdd49a, #fdf59a);
|
|
||||||
background-image: linear-gradient(to bottom, #fdd49a, #fdf59a);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0);
|
|
||||||
border-color: #fdf59a #fdf59a #fbed50;
|
|
||||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.today:hover,
|
|
||||||
.datepicker table tr td.today:hover:hover,
|
|
||||||
.datepicker table tr td.today.disabled:hover,
|
|
||||||
.datepicker table tr td.today.disabled:hover:hover,
|
|
||||||
.datepicker table tr td.today:active,
|
|
||||||
.datepicker table tr td.today:hover:active,
|
|
||||||
.datepicker table tr td.today.disabled:active,
|
|
||||||
.datepicker table tr td.today.disabled:hover:active,
|
|
||||||
.datepicker table tr td.today.active,
|
|
||||||
.datepicker table tr td.today:hover.active,
|
|
||||||
.datepicker table tr td.today.disabled.active,
|
|
||||||
.datepicker table tr td.today.disabled:hover.active,
|
|
||||||
.datepicker table tr td.today.disabled,
|
|
||||||
.datepicker table tr td.today:hover.disabled,
|
|
||||||
.datepicker table tr td.today.disabled.disabled,
|
|
||||||
.datepicker table tr td.today.disabled:hover.disabled,
|
|
||||||
.datepicker table tr td.today[disabled],
|
|
||||||
.datepicker table tr td.today:hover[disabled],
|
|
||||||
.datepicker table tr td.today.disabled[disabled],
|
|
||||||
.datepicker table tr td.today.disabled:hover[disabled] {
|
|
||||||
background-color: #fdf59a;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.today:active,
|
|
||||||
.datepicker table tr td.today:hover:active,
|
|
||||||
.datepicker table tr td.today.disabled:active,
|
|
||||||
.datepicker table tr td.today.disabled:hover:active,
|
|
||||||
.datepicker table tr td.today.active,
|
|
||||||
.datepicker table tr td.today:hover.active,
|
|
||||||
.datepicker table tr td.today.disabled.active,
|
|
||||||
.datepicker table tr td.today.disabled:hover.active {
|
|
||||||
background-color: #fbf069 \9;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.today:hover:hover {
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.today.active:hover {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range,
|
|
||||||
.datepicker table tr td.range:hover,
|
|
||||||
.datepicker table tr td.range.disabled,
|
|
||||||
.datepicker table tr td.range.disabled:hover {
|
|
||||||
background: #eeeeee;
|
|
||||||
-webkit-border-radius: 0;
|
|
||||||
-moz-border-radius: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.today,
|
|
||||||
.datepicker table tr td.range.today:hover,
|
|
||||||
.datepicker table tr td.range.today.disabled,
|
|
||||||
.datepicker table tr td.range.today.disabled:hover {
|
|
||||||
background-color: #f3d17a;
|
|
||||||
background-image: -moz-linear-gradient(to bottom, #f3c17a, #f3e97a);
|
|
||||||
background-image: -ms-linear-gradient(to bottom, #f3c17a, #f3e97a);
|
|
||||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f3c17a), to(#f3e97a));
|
|
||||||
background-image: -webkit-linear-gradient(to bottom, #f3c17a, #f3e97a);
|
|
||||||
background-image: -o-linear-gradient(to bottom, #f3c17a, #f3e97a);
|
|
||||||
background-image: linear-gradient(to bottom, #f3c17a, #f3e97a);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3c17a', endColorstr='#f3e97a', GradientType=0);
|
|
||||||
border-color: #f3e97a #f3e97a #edde34;
|
|
||||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
|
|
||||||
-webkit-border-radius: 0;
|
|
||||||
-moz-border-radius: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.today:hover,
|
|
||||||
.datepicker table tr td.range.today:hover:hover,
|
|
||||||
.datepicker table tr td.range.today.disabled:hover,
|
|
||||||
.datepicker table tr td.range.today.disabled:hover:hover,
|
|
||||||
.datepicker table tr td.range.today:active,
|
|
||||||
.datepicker table tr td.range.today:hover:active,
|
|
||||||
.datepicker table tr td.range.today.disabled:active,
|
|
||||||
.datepicker table tr td.range.today.disabled:hover:active,
|
|
||||||
.datepicker table tr td.range.today.active,
|
|
||||||
.datepicker table tr td.range.today:hover.active,
|
|
||||||
.datepicker table tr td.range.today.disabled.active,
|
|
||||||
.datepicker table tr td.range.today.disabled:hover.active,
|
|
||||||
.datepicker table tr td.range.today.disabled,
|
|
||||||
.datepicker table tr td.range.today:hover.disabled,
|
|
||||||
.datepicker table tr td.range.today.disabled.disabled,
|
|
||||||
.datepicker table tr td.range.today.disabled:hover.disabled,
|
|
||||||
.datepicker table tr td.range.today[disabled],
|
|
||||||
.datepicker table tr td.range.today:hover[disabled],
|
|
||||||
.datepicker table tr td.range.today.disabled[disabled],
|
|
||||||
.datepicker table tr td.range.today.disabled:hover[disabled] {
|
|
||||||
background-color: #f3e97a;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.today:active,
|
|
||||||
.datepicker table tr td.range.today:hover:active,
|
|
||||||
.datepicker table tr td.range.today.disabled:active,
|
|
||||||
.datepicker table tr td.range.today.disabled:hover:active,
|
|
||||||
.datepicker table tr td.range.today.active,
|
|
||||||
.datepicker table tr td.range.today:hover.active,
|
|
||||||
.datepicker table tr td.range.today.disabled.active,
|
|
||||||
.datepicker table tr td.range.today.disabled:hover.active {
|
|
||||||
background-color: #efe24b \9;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.selected,
|
|
||||||
.datepicker table tr td.selected:hover,
|
|
||||||
.datepicker table tr td.selected.disabled,
|
|
||||||
.datepicker table tr td.selected.disabled:hover {
|
|
||||||
background-color: #9e9e9e;
|
|
||||||
background-image: -moz-linear-gradient(to bottom, #b3b3b3, #808080);
|
|
||||||
background-image: -ms-linear-gradient(to bottom, #b3b3b3, #808080);
|
|
||||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#b3b3b3), to(#808080));
|
|
||||||
background-image: -webkit-linear-gradient(to bottom, #b3b3b3, #808080);
|
|
||||||
background-image: -o-linear-gradient(to bottom, #b3b3b3, #808080);
|
|
||||||
background-image: linear-gradient(to bottom, #b3b3b3, #808080);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#b3b3b3', endColorstr='#808080', GradientType=0);
|
|
||||||
border-color: #808080 #808080 #595959;
|
|
||||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
|
|
||||||
color: #fff;
|
|
||||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
.datepicker table tr td.selected:hover,
|
|
||||||
.datepicker table tr td.selected:hover:hover,
|
|
||||||
.datepicker table tr td.selected.disabled:hover,
|
|
||||||
.datepicker table tr td.selected.disabled:hover:hover,
|
|
||||||
.datepicker table tr td.selected:active,
|
|
||||||
.datepicker table tr td.selected:hover:active,
|
|
||||||
.datepicker table tr td.selected.disabled:active,
|
|
||||||
.datepicker table tr td.selected.disabled:hover:active,
|
|
||||||
.datepicker table tr td.selected.active,
|
|
||||||
.datepicker table tr td.selected:hover.active,
|
|
||||||
.datepicker table tr td.selected.disabled.active,
|
|
||||||
.datepicker table tr td.selected.disabled:hover.active,
|
|
||||||
.datepicker table tr td.selected.disabled,
|
|
||||||
.datepicker table tr td.selected:hover.disabled,
|
|
||||||
.datepicker table tr td.selected.disabled.disabled,
|
|
||||||
.datepicker table tr td.selected.disabled:hover.disabled,
|
|
||||||
.datepicker table tr td.selected[disabled],
|
|
||||||
.datepicker table tr td.selected:hover[disabled],
|
|
||||||
.datepicker table tr td.selected.disabled[disabled],
|
|
||||||
.datepicker table tr td.selected.disabled:hover[disabled] {
|
|
||||||
background-color: #808080;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.selected:active,
|
|
||||||
.datepicker table tr td.selected:hover:active,
|
|
||||||
.datepicker table tr td.selected.disabled:active,
|
|
||||||
.datepicker table tr td.selected.disabled:hover:active,
|
|
||||||
.datepicker table tr td.selected.active,
|
|
||||||
.datepicker table tr td.selected:hover.active,
|
|
||||||
.datepicker table tr td.selected.disabled.active,
|
|
||||||
.datepicker table tr td.selected.disabled:hover.active {
|
|
||||||
background-color: #666666 \9;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.active,
|
|
||||||
.datepicker table tr td.active:hover,
|
|
||||||
.datepicker table tr td.active.disabled,
|
|
||||||
.datepicker table tr td.active.disabled:hover {
|
|
||||||
background-color: #006dcc;
|
|
||||||
background-image: -moz-linear-gradient(to bottom, #0088cc, #0044cc);
|
|
||||||
background-image: -ms-linear-gradient(to bottom, #0088cc, #0044cc);
|
|
||||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
|
|
||||||
background-image: -webkit-linear-gradient(to bottom, #0088cc, #0044cc);
|
|
||||||
background-image: -o-linear-gradient(to bottom, #0088cc, #0044cc);
|
|
||||||
background-image: linear-gradient(to bottom, #0088cc, #0044cc);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
|
|
||||||
border-color: #0044cc #0044cc #002a80;
|
|
||||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
|
|
||||||
color: #fff;
|
|
||||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
.datepicker table tr td.active:hover,
|
|
||||||
.datepicker table tr td.active:hover:hover,
|
|
||||||
.datepicker table tr td.active.disabled:hover,
|
|
||||||
.datepicker table tr td.active.disabled:hover:hover,
|
|
||||||
.datepicker table tr td.active:active,
|
|
||||||
.datepicker table tr td.active:hover:active,
|
|
||||||
.datepicker table tr td.active.disabled:active,
|
|
||||||
.datepicker table tr td.active.disabled:hover:active,
|
|
||||||
.datepicker table tr td.active.active,
|
|
||||||
.datepicker table tr td.active:hover.active,
|
|
||||||
.datepicker table tr td.active.disabled.active,
|
|
||||||
.datepicker table tr td.active.disabled:hover.active,
|
|
||||||
.datepicker table tr td.active.disabled,
|
|
||||||
.datepicker table tr td.active:hover.disabled,
|
|
||||||
.datepicker table tr td.active.disabled.disabled,
|
|
||||||
.datepicker table tr td.active.disabled:hover.disabled,
|
|
||||||
.datepicker table tr td.active[disabled],
|
|
||||||
.datepicker table tr td.active:hover[disabled],
|
|
||||||
.datepicker table tr td.active.disabled[disabled],
|
|
||||||
.datepicker table tr td.active.disabled:hover[disabled] {
|
|
||||||
background-color: #0044cc;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.active:active,
|
|
||||||
.datepicker table tr td.active:hover:active,
|
|
||||||
.datepicker table tr td.active.disabled:active,
|
|
||||||
.datepicker table tr td.active.disabled:hover:active,
|
|
||||||
.datepicker table tr td.active.active,
|
|
||||||
.datepicker table tr td.active:hover.active,
|
|
||||||
.datepicker table tr td.active.disabled.active,
|
|
||||||
.datepicker table tr td.active.disabled:hover.active {
|
|
||||||
background-color: #003399 \9;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span {
|
|
||||||
display: block;
|
|
||||||
width: 23%;
|
|
||||||
height: 54px;
|
|
||||||
line-height: 54px;
|
|
||||||
float: left;
|
|
||||||
margin: 1%;
|
|
||||||
cursor: pointer;
|
|
||||||
-webkit-border-radius: 4px;
|
|
||||||
-moz-border-radius: 4px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span:hover {
|
|
||||||
background: #eeeeee;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span.disabled,
|
|
||||||
.datepicker table tr td span.disabled:hover {
|
|
||||||
background: none;
|
|
||||||
color: #999999;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span.active,
|
|
||||||
.datepicker table tr td span.active:hover,
|
|
||||||
.datepicker table tr td span.active.disabled,
|
|
||||||
.datepicker table tr td span.active.disabled:hover {
|
|
||||||
background-color: #006dcc;
|
|
||||||
background-image: -moz-linear-gradient(to bottom, #0088cc, #0044cc);
|
|
||||||
background-image: -ms-linear-gradient(to bottom, #0088cc, #0044cc);
|
|
||||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
|
|
||||||
background-image: -webkit-linear-gradient(to bottom, #0088cc, #0044cc);
|
|
||||||
background-image: -o-linear-gradient(to bottom, #0088cc, #0044cc);
|
|
||||||
background-image: linear-gradient(to bottom, #0088cc, #0044cc);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
|
|
||||||
border-color: #0044cc #0044cc #002a80;
|
|
||||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
|
|
||||||
color: #fff;
|
|
||||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
.datepicker table tr td span.active:hover,
|
|
||||||
.datepicker table tr td span.active:hover:hover,
|
|
||||||
.datepicker table tr td span.active.disabled:hover,
|
|
||||||
.datepicker table tr td span.active.disabled:hover:hover,
|
|
||||||
.datepicker table tr td span.active:active,
|
|
||||||
.datepicker table tr td span.active:hover:active,
|
|
||||||
.datepicker table tr td span.active.disabled:active,
|
|
||||||
.datepicker table tr td span.active.disabled:hover:active,
|
|
||||||
.datepicker table tr td span.active.active,
|
|
||||||
.datepicker table tr td span.active:hover.active,
|
|
||||||
.datepicker table tr td span.active.disabled.active,
|
|
||||||
.datepicker table tr td span.active.disabled:hover.active,
|
|
||||||
.datepicker table tr td span.active.disabled,
|
|
||||||
.datepicker table tr td span.active:hover.disabled,
|
|
||||||
.datepicker table tr td span.active.disabled.disabled,
|
|
||||||
.datepicker table tr td span.active.disabled:hover.disabled,
|
|
||||||
.datepicker table tr td span.active[disabled],
|
|
||||||
.datepicker table tr td span.active:hover[disabled],
|
|
||||||
.datepicker table tr td span.active.disabled[disabled],
|
|
||||||
.datepicker table tr td span.active.disabled:hover[disabled] {
|
|
||||||
background-color: #0044cc;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span.active:active,
|
|
||||||
.datepicker table tr td span.active:hover:active,
|
|
||||||
.datepicker table tr td span.active.disabled:active,
|
|
||||||
.datepicker table tr td span.active.disabled:hover:active,
|
|
||||||
.datepicker table tr td span.active.active,
|
|
||||||
.datepicker table tr td span.active:hover.active,
|
|
||||||
.datepicker table tr td span.active.disabled.active,
|
|
||||||
.datepicker table tr td span.active.disabled:hover.active {
|
|
||||||
background-color: #003399 \9;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span.old,
|
|
||||||
.datepicker table tr td span.new {
|
|
||||||
color: #999999;
|
|
||||||
}
|
|
||||||
.datepicker .datepicker-switch {
|
|
||||||
width: 145px;
|
|
||||||
}
|
|
||||||
.datepicker .datepicker-switch,
|
|
||||||
.datepicker .prev,
|
|
||||||
.datepicker .next,
|
|
||||||
.datepicker tfoot tr th {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.datepicker .datepicker-switch:hover,
|
|
||||||
.datepicker .prev:hover,
|
|
||||||
.datepicker .next:hover,
|
|
||||||
.datepicker tfoot tr th:hover {
|
|
||||||
background: #eeeeee;
|
|
||||||
}
|
|
||||||
.datepicker .cw {
|
|
||||||
font-size: 10px;
|
|
||||||
width: 12px;
|
|
||||||
padding: 0 2px 0 5px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
.input-append.date .add-on,
|
|
||||||
.input-prepend.date .add-on {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.input-append.date .add-on i,
|
|
||||||
.input-prepend.date .add-on i {
|
|
||||||
margin-top: 3px;
|
|
||||||
}
|
|
||||||
.input-daterange input {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.input-daterange input:first-child {
|
|
||||||
-webkit-border-radius: 3px 0 0 3px;
|
|
||||||
-moz-border-radius: 3px 0 0 3px;
|
|
||||||
border-radius: 3px 0 0 3px;
|
|
||||||
}
|
|
||||||
.input-daterange input:last-child {
|
|
||||||
-webkit-border-radius: 0 3px 3px 0;
|
|
||||||
-moz-border-radius: 0 3px 3px 0;
|
|
||||||
border-radius: 0 3px 3px 0;
|
|
||||||
}
|
|
||||||
.input-daterange .add-on {
|
|
||||||
display: inline-block;
|
|
||||||
width: auto;
|
|
||||||
min-width: 16px;
|
|
||||||
height: 20px;
|
|
||||||
padding: 4px 5px;
|
|
||||||
font-weight: normal;
|
|
||||||
line-height: 20px;
|
|
||||||
text-align: center;
|
|
||||||
text-shadow: 0 1px 0 #ffffff;
|
|
||||||
vertical-align: middle;
|
|
||||||
background-color: #eeeeee;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
margin-left: -5px;
|
|
||||||
margin-right: -5px;
|
|
||||||
}
|
|
||||||
.datepicker.dropdown-menu {
|
|
||||||
position: absolute;
|
|
||||||
top: 100%;
|
|
||||||
left: 0;
|
|
||||||
z-index: 1000;
|
|
||||||
float: left;
|
|
||||||
display: none;
|
|
||||||
min-width: 160px;
|
|
||||||
list-style: none;
|
|
||||||
background-color: #ffffff;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
|
||||||
-webkit-border-radius: 5px;
|
|
||||||
-moz-border-radius: 5px;
|
|
||||||
border-radius: 5px;
|
|
||||||
-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
|
||||||
-moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
|
||||||
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
|
||||||
-webkit-background-clip: padding-box;
|
|
||||||
-moz-background-clip: padding;
|
|
||||||
background-clip: padding-box;
|
|
||||||
*border-right-width: 2px;
|
|
||||||
*border-bottom-width: 2px;
|
|
||||||
color: #333333;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 20px;
|
|
||||||
}
|
|
||||||
.datepicker.dropdown-menu th,
|
|
||||||
.datepicker.datepicker-inline th,
|
|
||||||
.datepicker.dropdown-menu td,
|
|
||||||
.datepicker.datepicker-inline td {
|
|
||||||
padding: 4px 5px;
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
|
@ -1,791 +0,0 @@
|
||||||
/*!
|
|
||||||
* Datepicker for Bootstrap v1.5.1 (https://github.com/eternicode/bootstrap-datepicker)
|
|
||||||
*
|
|
||||||
* Copyright 2012 Stefan Petre
|
|
||||||
* Improvements by Andrew Rowls
|
|
||||||
* Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
|
||||||
*/
|
|
||||||
.datepicker {
|
|
||||||
border-radius: 4px;
|
|
||||||
direction: ltr;
|
|
||||||
}
|
|
||||||
.datepicker-inline {
|
|
||||||
width: 220px;
|
|
||||||
}
|
|
||||||
.datepicker.datepicker-rtl {
|
|
||||||
direction: rtl;
|
|
||||||
}
|
|
||||||
.datepicker.datepicker-rtl table tr td span {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown {
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown:before {
|
|
||||||
content: '';
|
|
||||||
display: inline-block;
|
|
||||||
border-left: 7px solid transparent;
|
|
||||||
border-right: 7px solid transparent;
|
|
||||||
border-bottom: 7px solid rgba(0, 0, 0, 0.15);
|
|
||||||
border-top: 0;
|
|
||||||
border-bottom-color: rgba(0, 0, 0, 0.2);
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown:after {
|
|
||||||
content: '';
|
|
||||||
display: inline-block;
|
|
||||||
border-left: 6px solid transparent;
|
|
||||||
border-right: 6px solid transparent;
|
|
||||||
border-bottom: 6px solid #ffffff;
|
|
||||||
border-top: 0;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-left:before {
|
|
||||||
left: 6px;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-left:after {
|
|
||||||
left: 7px;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-right:before {
|
|
||||||
right: 6px;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-right:after {
|
|
||||||
right: 7px;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-bottom:before {
|
|
||||||
top: -7px;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-bottom:after {
|
|
||||||
top: -6px;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-top:before {
|
|
||||||
bottom: -7px;
|
|
||||||
border-bottom: 0;
|
|
||||||
border-top: 7px solid rgba(0, 0, 0, 0.15);
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-top:after {
|
|
||||||
bottom: -6px;
|
|
||||||
border-bottom: 0;
|
|
||||||
border-top: 6px solid #ffffff;
|
|
||||||
}
|
|
||||||
.datepicker > div {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.datepicker table {
|
|
||||||
margin: 0;
|
|
||||||
-webkit-touch-callout: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-khtml-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
.datepicker table tr td,
|
|
||||||
.datepicker table tr th {
|
|
||||||
text-align: center;
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
.table-striped .datepicker table tr td,
|
|
||||||
.table-striped .datepicker table tr th {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.old,
|
|
||||||
.datepicker table tr td.new {
|
|
||||||
color: #999999;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.day:hover,
|
|
||||||
.datepicker table tr td.focused {
|
|
||||||
background: #eeeeee;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.disabled,
|
|
||||||
.datepicker table tr td.disabled:hover {
|
|
||||||
background: none;
|
|
||||||
color: #999999;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.highlighted {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #d9edf7;
|
|
||||||
border-color: #85c5e5;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.highlighted:focus,
|
|
||||||
.datepicker table tr td.highlighted.focus {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #afd9ee;
|
|
||||||
border-color: #298fc2;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.highlighted:hover {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #afd9ee;
|
|
||||||
border-color: #52addb;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.highlighted:active,
|
|
||||||
.datepicker table tr td.highlighted.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.highlighted {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #afd9ee;
|
|
||||||
border-color: #52addb;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.highlighted:active:hover,
|
|
||||||
.datepicker table tr td.highlighted.active:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.highlighted:hover,
|
|
||||||
.datepicker table tr td.highlighted:active:focus,
|
|
||||||
.datepicker table tr td.highlighted.active:focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.highlighted:focus,
|
|
||||||
.datepicker table tr td.highlighted:active.focus,
|
|
||||||
.datepicker table tr td.highlighted.active.focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.highlighted.focus {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #91cbe8;
|
|
||||||
border-color: #298fc2;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.highlighted:active,
|
|
||||||
.datepicker table tr td.highlighted.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.highlighted {
|
|
||||||
background-image: none;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.highlighted.disabled:hover,
|
|
||||||
.datepicker table tr td.highlighted[disabled]:hover,
|
|
||||||
fieldset[disabled] .datepicker table tr td.highlighted:hover,
|
|
||||||
.datepicker table tr td.highlighted.disabled:focus,
|
|
||||||
.datepicker table tr td.highlighted[disabled]:focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.highlighted:focus,
|
|
||||||
.datepicker table tr td.highlighted.disabled.focus,
|
|
||||||
.datepicker table tr td.highlighted[disabled].focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.highlighted.focus {
|
|
||||||
background-color: #d9edf7;
|
|
||||||
border-color: #85c5e5;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.highlighted.focused {
|
|
||||||
background: #afd9ee;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.highlighted.disabled,
|
|
||||||
.datepicker table tr td.highlighted.disabled:active {
|
|
||||||
background: #d9edf7;
|
|
||||||
color: #999999;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.today {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #ffdb99;
|
|
||||||
border-color: #ffb733;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.today:focus,
|
|
||||||
.datepicker table tr td.today.focus {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #ffc966;
|
|
||||||
border-color: #b37400;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.today:hover {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #ffc966;
|
|
||||||
border-color: #f59e00;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.today:active,
|
|
||||||
.datepicker table tr td.today.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.today {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #ffc966;
|
|
||||||
border-color: #f59e00;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.today:active:hover,
|
|
||||||
.datepicker table tr td.today.active:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.today:hover,
|
|
||||||
.datepicker table tr td.today:active:focus,
|
|
||||||
.datepicker table tr td.today.active:focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.today:focus,
|
|
||||||
.datepicker table tr td.today:active.focus,
|
|
||||||
.datepicker table tr td.today.active.focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.today.focus {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #ffbc42;
|
|
||||||
border-color: #b37400;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.today:active,
|
|
||||||
.datepicker table tr td.today.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.today {
|
|
||||||
background-image: none;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.today.disabled:hover,
|
|
||||||
.datepicker table tr td.today[disabled]:hover,
|
|
||||||
fieldset[disabled] .datepicker table tr td.today:hover,
|
|
||||||
.datepicker table tr td.today.disabled:focus,
|
|
||||||
.datepicker table tr td.today[disabled]:focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.today:focus,
|
|
||||||
.datepicker table tr td.today.disabled.focus,
|
|
||||||
.datepicker table tr td.today[disabled].focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.today.focus {
|
|
||||||
background-color: #ffdb99;
|
|
||||||
border-color: #ffb733;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.today.focused {
|
|
||||||
background: #ffc966;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.today.disabled,
|
|
||||||
.datepicker table tr td.today.disabled:active {
|
|
||||||
background: #ffdb99;
|
|
||||||
color: #999999;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #eeeeee;
|
|
||||||
border-color: #bbbbbb;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range:focus,
|
|
||||||
.datepicker table tr td.range.focus {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #d5d5d5;
|
|
||||||
border-color: #7c7c7c;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range:hover {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #d5d5d5;
|
|
||||||
border-color: #9d9d9d;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range:active,
|
|
||||||
.datepicker table tr td.range.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #d5d5d5;
|
|
||||||
border-color: #9d9d9d;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range:active:hover,
|
|
||||||
.datepicker table tr td.range.active:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range:hover,
|
|
||||||
.datepicker table tr td.range:active:focus,
|
|
||||||
.datepicker table tr td.range.active:focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range:focus,
|
|
||||||
.datepicker table tr td.range:active.focus,
|
|
||||||
.datepicker table tr td.range.active.focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range.focus {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #c3c3c3;
|
|
||||||
border-color: #7c7c7c;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range:active,
|
|
||||||
.datepicker table tr td.range.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range {
|
|
||||||
background-image: none;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.disabled:hover,
|
|
||||||
.datepicker table tr td.range[disabled]:hover,
|
|
||||||
fieldset[disabled] .datepicker table tr td.range:hover,
|
|
||||||
.datepicker table tr td.range.disabled:focus,
|
|
||||||
.datepicker table tr td.range[disabled]:focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.range:focus,
|
|
||||||
.datepicker table tr td.range.disabled.focus,
|
|
||||||
.datepicker table tr td.range[disabled].focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.range.focus {
|
|
||||||
background-color: #eeeeee;
|
|
||||||
border-color: #bbbbbb;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.focused {
|
|
||||||
background: #d5d5d5;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.disabled,
|
|
||||||
.datepicker table tr td.range.disabled:active {
|
|
||||||
background: #eeeeee;
|
|
||||||
color: #999999;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.highlighted {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #e4eef3;
|
|
||||||
border-color: #9dc1d3;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.highlighted:focus,
|
|
||||||
.datepicker table tr td.range.highlighted.focus {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #c1d7e3;
|
|
||||||
border-color: #4b88a6;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.highlighted:hover {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #c1d7e3;
|
|
||||||
border-color: #73a6c0;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.highlighted:active,
|
|
||||||
.datepicker table tr td.range.highlighted.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range.highlighted {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #c1d7e3;
|
|
||||||
border-color: #73a6c0;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.highlighted:active:hover,
|
|
||||||
.datepicker table tr td.range.highlighted.active:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range.highlighted:hover,
|
|
||||||
.datepicker table tr td.range.highlighted:active:focus,
|
|
||||||
.datepicker table tr td.range.highlighted.active:focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range.highlighted:focus,
|
|
||||||
.datepicker table tr td.range.highlighted:active.focus,
|
|
||||||
.datepicker table tr td.range.highlighted.active.focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range.highlighted.focus {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #a8c8d8;
|
|
||||||
border-color: #4b88a6;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.highlighted:active,
|
|
||||||
.datepicker table tr td.range.highlighted.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range.highlighted {
|
|
||||||
background-image: none;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.highlighted.disabled:hover,
|
|
||||||
.datepicker table tr td.range.highlighted[disabled]:hover,
|
|
||||||
fieldset[disabled] .datepicker table tr td.range.highlighted:hover,
|
|
||||||
.datepicker table tr td.range.highlighted.disabled:focus,
|
|
||||||
.datepicker table tr td.range.highlighted[disabled]:focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.range.highlighted:focus,
|
|
||||||
.datepicker table tr td.range.highlighted.disabled.focus,
|
|
||||||
.datepicker table tr td.range.highlighted[disabled].focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.range.highlighted.focus {
|
|
||||||
background-color: #e4eef3;
|
|
||||||
border-color: #9dc1d3;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.highlighted.focused {
|
|
||||||
background: #c1d7e3;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.highlighted.disabled,
|
|
||||||
.datepicker table tr td.range.highlighted.disabled:active {
|
|
||||||
background: #e4eef3;
|
|
||||||
color: #999999;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.today {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #f7ca77;
|
|
||||||
border-color: #f1a417;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.today:focus,
|
|
||||||
.datepicker table tr td.range.today.focus {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #f4b747;
|
|
||||||
border-color: #815608;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.today:hover {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #f4b747;
|
|
||||||
border-color: #bf800c;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.today:active,
|
|
||||||
.datepicker table tr td.range.today.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range.today {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #f4b747;
|
|
||||||
border-color: #bf800c;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.today:active:hover,
|
|
||||||
.datepicker table tr td.range.today.active:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range.today:hover,
|
|
||||||
.datepicker table tr td.range.today:active:focus,
|
|
||||||
.datepicker table tr td.range.today.active:focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range.today:focus,
|
|
||||||
.datepicker table tr td.range.today:active.focus,
|
|
||||||
.datepicker table tr td.range.today.active.focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range.today.focus {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #f2aa25;
|
|
||||||
border-color: #815608;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.today:active,
|
|
||||||
.datepicker table tr td.range.today.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range.today {
|
|
||||||
background-image: none;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.today.disabled:hover,
|
|
||||||
.datepicker table tr td.range.today[disabled]:hover,
|
|
||||||
fieldset[disabled] .datepicker table tr td.range.today:hover,
|
|
||||||
.datepicker table tr td.range.today.disabled:focus,
|
|
||||||
.datepicker table tr td.range.today[disabled]:focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.range.today:focus,
|
|
||||||
.datepicker table tr td.range.today.disabled.focus,
|
|
||||||
.datepicker table tr td.range.today[disabled].focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.range.today.focus {
|
|
||||||
background-color: #f7ca77;
|
|
||||||
border-color: #f1a417;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.today.disabled,
|
|
||||||
.datepicker table tr td.range.today.disabled:active {
|
|
||||||
background: #f7ca77;
|
|
||||||
color: #999999;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.selected,
|
|
||||||
.datepicker table tr td.selected.highlighted {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #999999;
|
|
||||||
border-color: #555555;
|
|
||||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
.datepicker table tr td.selected:focus,
|
|
||||||
.datepicker table tr td.selected.highlighted:focus,
|
|
||||||
.datepicker table tr td.selected.focus,
|
|
||||||
.datepicker table tr td.selected.highlighted.focus {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #808080;
|
|
||||||
border-color: #161616;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.selected:hover,
|
|
||||||
.datepicker table tr td.selected.highlighted:hover {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #808080;
|
|
||||||
border-color: #373737;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.selected:active,
|
|
||||||
.datepicker table tr td.selected.highlighted:active,
|
|
||||||
.datepicker table tr td.selected.active,
|
|
||||||
.datepicker table tr td.selected.highlighted.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.selected,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.selected.highlighted {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #808080;
|
|
||||||
border-color: #373737;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.selected:active:hover,
|
|
||||||
.datepicker table tr td.selected.highlighted:active:hover,
|
|
||||||
.datepicker table tr td.selected.active:hover,
|
|
||||||
.datepicker table tr td.selected.highlighted.active:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.selected:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.selected.highlighted:hover,
|
|
||||||
.datepicker table tr td.selected:active:focus,
|
|
||||||
.datepicker table tr td.selected.highlighted:active:focus,
|
|
||||||
.datepicker table tr td.selected.active:focus,
|
|
||||||
.datepicker table tr td.selected.highlighted.active:focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.selected:focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.selected.highlighted:focus,
|
|
||||||
.datepicker table tr td.selected:active.focus,
|
|
||||||
.datepicker table tr td.selected.highlighted:active.focus,
|
|
||||||
.datepicker table tr td.selected.active.focus,
|
|
||||||
.datepicker table tr td.selected.highlighted.active.focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.selected.focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.selected.highlighted.focus {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #6e6e6e;
|
|
||||||
border-color: #161616;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.selected:active,
|
|
||||||
.datepicker table tr td.selected.highlighted:active,
|
|
||||||
.datepicker table tr td.selected.active,
|
|
||||||
.datepicker table tr td.selected.highlighted.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.selected,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.selected.highlighted {
|
|
||||||
background-image: none;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.selected.disabled:hover,
|
|
||||||
.datepicker table tr td.selected.highlighted.disabled:hover,
|
|
||||||
.datepicker table tr td.selected[disabled]:hover,
|
|
||||||
.datepicker table tr td.selected.highlighted[disabled]:hover,
|
|
||||||
fieldset[disabled] .datepicker table tr td.selected:hover,
|
|
||||||
fieldset[disabled] .datepicker table tr td.selected.highlighted:hover,
|
|
||||||
.datepicker table tr td.selected.disabled:focus,
|
|
||||||
.datepicker table tr td.selected.highlighted.disabled:focus,
|
|
||||||
.datepicker table tr td.selected[disabled]:focus,
|
|
||||||
.datepicker table tr td.selected.highlighted[disabled]:focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.selected:focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.selected.highlighted:focus,
|
|
||||||
.datepicker table tr td.selected.disabled.focus,
|
|
||||||
.datepicker table tr td.selected.highlighted.disabled.focus,
|
|
||||||
.datepicker table tr td.selected[disabled].focus,
|
|
||||||
.datepicker table tr td.selected.highlighted[disabled].focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.selected.focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.selected.highlighted.focus {
|
|
||||||
background-color: #999999;
|
|
||||||
border-color: #555555;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.active,
|
|
||||||
.datepicker table tr td.active.highlighted {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #428bca;
|
|
||||||
border-color: #357ebd;
|
|
||||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
.datepicker table tr td.active:focus,
|
|
||||||
.datepicker table tr td.active.highlighted:focus,
|
|
||||||
.datepicker table tr td.active.focus,
|
|
||||||
.datepicker table tr td.active.highlighted.focus {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #3071a9;
|
|
||||||
border-color: #193c5a;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.active:hover,
|
|
||||||
.datepicker table tr td.active.highlighted:hover {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #3071a9;
|
|
||||||
border-color: #285e8e;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.active:active,
|
|
||||||
.datepicker table tr td.active.highlighted:active,
|
|
||||||
.datepicker table tr td.active.active,
|
|
||||||
.datepicker table tr td.active.highlighted.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.active.highlighted {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #3071a9;
|
|
||||||
border-color: #285e8e;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.active:active:hover,
|
|
||||||
.datepicker table tr td.active.highlighted:active:hover,
|
|
||||||
.datepicker table tr td.active.active:hover,
|
|
||||||
.datepicker table tr td.active.highlighted.active:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.active:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.active.highlighted:hover,
|
|
||||||
.datepicker table tr td.active:active:focus,
|
|
||||||
.datepicker table tr td.active.highlighted:active:focus,
|
|
||||||
.datepicker table tr td.active.active:focus,
|
|
||||||
.datepicker table tr td.active.highlighted.active:focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.active:focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.active.highlighted:focus,
|
|
||||||
.datepicker table tr td.active:active.focus,
|
|
||||||
.datepicker table tr td.active.highlighted:active.focus,
|
|
||||||
.datepicker table tr td.active.active.focus,
|
|
||||||
.datepicker table tr td.active.highlighted.active.focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.active.focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.active.highlighted.focus {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #285e8e;
|
|
||||||
border-color: #193c5a;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.active:active,
|
|
||||||
.datepicker table tr td.active.highlighted:active,
|
|
||||||
.datepicker table tr td.active.active,
|
|
||||||
.datepicker table tr td.active.highlighted.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.active.highlighted {
|
|
||||||
background-image: none;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.active.disabled:hover,
|
|
||||||
.datepicker table tr td.active.highlighted.disabled:hover,
|
|
||||||
.datepicker table tr td.active[disabled]:hover,
|
|
||||||
.datepicker table tr td.active.highlighted[disabled]:hover,
|
|
||||||
fieldset[disabled] .datepicker table tr td.active:hover,
|
|
||||||
fieldset[disabled] .datepicker table tr td.active.highlighted:hover,
|
|
||||||
.datepicker table tr td.active.disabled:focus,
|
|
||||||
.datepicker table tr td.active.highlighted.disabled:focus,
|
|
||||||
.datepicker table tr td.active[disabled]:focus,
|
|
||||||
.datepicker table tr td.active.highlighted[disabled]:focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.active:focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.active.highlighted:focus,
|
|
||||||
.datepicker table tr td.active.disabled.focus,
|
|
||||||
.datepicker table tr td.active.highlighted.disabled.focus,
|
|
||||||
.datepicker table tr td.active[disabled].focus,
|
|
||||||
.datepicker table tr td.active.highlighted[disabled].focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.active.focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.active.highlighted.focus {
|
|
||||||
background-color: #428bca;
|
|
||||||
border-color: #357ebd;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span {
|
|
||||||
display: block;
|
|
||||||
width: 23%;
|
|
||||||
height: 54px;
|
|
||||||
line-height: 54px;
|
|
||||||
float: left;
|
|
||||||
margin: 1%;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span:hover {
|
|
||||||
background: #eeeeee;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span.disabled,
|
|
||||||
.datepicker table tr td span.disabled:hover {
|
|
||||||
background: none;
|
|
||||||
color: #999999;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span.active,
|
|
||||||
.datepicker table tr td span.active:hover,
|
|
||||||
.datepicker table tr td span.active.disabled,
|
|
||||||
.datepicker table tr td span.active.disabled:hover {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #428bca;
|
|
||||||
border-color: #357ebd;
|
|
||||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
.datepicker table tr td span.active:focus,
|
|
||||||
.datepicker table tr td span.active:hover:focus,
|
|
||||||
.datepicker table tr td span.active.disabled:focus,
|
|
||||||
.datepicker table tr td span.active.disabled:hover:focus,
|
|
||||||
.datepicker table tr td span.active.focus,
|
|
||||||
.datepicker table tr td span.active:hover.focus,
|
|
||||||
.datepicker table tr td span.active.disabled.focus,
|
|
||||||
.datepicker table tr td span.active.disabled:hover.focus {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #3071a9;
|
|
||||||
border-color: #193c5a;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span.active:hover,
|
|
||||||
.datepicker table tr td span.active:hover:hover,
|
|
||||||
.datepicker table tr td span.active.disabled:hover,
|
|
||||||
.datepicker table tr td span.active.disabled:hover:hover {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #3071a9;
|
|
||||||
border-color: #285e8e;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span.active:active,
|
|
||||||
.datepicker table tr td span.active:hover:active,
|
|
||||||
.datepicker table tr td span.active.disabled:active,
|
|
||||||
.datepicker table tr td span.active.disabled:hover:active,
|
|
||||||
.datepicker table tr td span.active.active,
|
|
||||||
.datepicker table tr td span.active:hover.active,
|
|
||||||
.datepicker table tr td span.active.disabled.active,
|
|
||||||
.datepicker table tr td span.active.disabled:hover.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active.disabled,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #3071a9;
|
|
||||||
border-color: #285e8e;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span.active:active:hover,
|
|
||||||
.datepicker table tr td span.active:hover:active:hover,
|
|
||||||
.datepicker table tr td span.active.disabled:active:hover,
|
|
||||||
.datepicker table tr td span.active.disabled:hover:active:hover,
|
|
||||||
.datepicker table tr td span.active.active:hover,
|
|
||||||
.datepicker table tr td span.active:hover.active:hover,
|
|
||||||
.datepicker table tr td span.active.disabled.active:hover,
|
|
||||||
.datepicker table tr td span.active.disabled:hover.active:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active:hover:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover:hover,
|
|
||||||
.datepicker table tr td span.active:active:focus,
|
|
||||||
.datepicker table tr td span.active:hover:active:focus,
|
|
||||||
.datepicker table tr td span.active.disabled:active:focus,
|
|
||||||
.datepicker table tr td span.active.disabled:hover:active:focus,
|
|
||||||
.datepicker table tr td span.active.active:focus,
|
|
||||||
.datepicker table tr td span.active:hover.active:focus,
|
|
||||||
.datepicker table tr td span.active.disabled.active:focus,
|
|
||||||
.datepicker table tr td span.active.disabled:hover.active:focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active:focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active:hover:focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active.disabled:focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover:focus,
|
|
||||||
.datepicker table tr td span.active:active.focus,
|
|
||||||
.datepicker table tr td span.active:hover:active.focus,
|
|
||||||
.datepicker table tr td span.active.disabled:active.focus,
|
|
||||||
.datepicker table tr td span.active.disabled:hover:active.focus,
|
|
||||||
.datepicker table tr td span.active.active.focus,
|
|
||||||
.datepicker table tr td span.active:hover.active.focus,
|
|
||||||
.datepicker table tr td span.active.disabled.active.focus,
|
|
||||||
.datepicker table tr td span.active.disabled:hover.active.focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active.focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active:hover.focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active.disabled.focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover.focus {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #285e8e;
|
|
||||||
border-color: #193c5a;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span.active:active,
|
|
||||||
.datepicker table tr td span.active:hover:active,
|
|
||||||
.datepicker table tr td span.active.disabled:active,
|
|
||||||
.datepicker table tr td span.active.disabled:hover:active,
|
|
||||||
.datepicker table tr td span.active.active,
|
|
||||||
.datepicker table tr td span.active:hover.active,
|
|
||||||
.datepicker table tr td span.active.disabled.active,
|
|
||||||
.datepicker table tr td span.active.disabled:hover.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active.disabled,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover {
|
|
||||||
background-image: none;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span.active.disabled:hover,
|
|
||||||
.datepicker table tr td span.active:hover.disabled:hover,
|
|
||||||
.datepicker table tr td span.active.disabled.disabled:hover,
|
|
||||||
.datepicker table tr td span.active.disabled:hover.disabled:hover,
|
|
||||||
.datepicker table tr td span.active[disabled]:hover,
|
|
||||||
.datepicker table tr td span.active:hover[disabled]:hover,
|
|
||||||
.datepicker table tr td span.active.disabled[disabled]:hover,
|
|
||||||
.datepicker table tr td span.active.disabled:hover[disabled]:hover,
|
|
||||||
fieldset[disabled] .datepicker table tr td span.active:hover,
|
|
||||||
fieldset[disabled] .datepicker table tr td span.active:hover:hover,
|
|
||||||
fieldset[disabled] .datepicker table tr td span.active.disabled:hover,
|
|
||||||
fieldset[disabled] .datepicker table tr td span.active.disabled:hover:hover,
|
|
||||||
.datepicker table tr td span.active.disabled:focus,
|
|
||||||
.datepicker table tr td span.active:hover.disabled:focus,
|
|
||||||
.datepicker table tr td span.active.disabled.disabled:focus,
|
|
||||||
.datepicker table tr td span.active.disabled:hover.disabled:focus,
|
|
||||||
.datepicker table tr td span.active[disabled]:focus,
|
|
||||||
.datepicker table tr td span.active:hover[disabled]:focus,
|
|
||||||
.datepicker table tr td span.active.disabled[disabled]:focus,
|
|
||||||
.datepicker table tr td span.active.disabled:hover[disabled]:focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td span.active:focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td span.active:hover:focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td span.active.disabled:focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td span.active.disabled:hover:focus,
|
|
||||||
.datepicker table tr td span.active.disabled.focus,
|
|
||||||
.datepicker table tr td span.active:hover.disabled.focus,
|
|
||||||
.datepicker table tr td span.active.disabled.disabled.focus,
|
|
||||||
.datepicker table tr td span.active.disabled:hover.disabled.focus,
|
|
||||||
.datepicker table tr td span.active[disabled].focus,
|
|
||||||
.datepicker table tr td span.active:hover[disabled].focus,
|
|
||||||
.datepicker table tr td span.active.disabled[disabled].focus,
|
|
||||||
.datepicker table tr td span.active.disabled:hover[disabled].focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td span.active.focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td span.active:hover.focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td span.active.disabled.focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td span.active.disabled:hover.focus {
|
|
||||||
background-color: #428bca;
|
|
||||||
border-color: #357ebd;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span.old,
|
|
||||||
.datepicker table tr td span.new {
|
|
||||||
color: #999999;
|
|
||||||
}
|
|
||||||
.datepicker .datepicker-switch {
|
|
||||||
width: 145px;
|
|
||||||
}
|
|
||||||
.datepicker .datepicker-switch,
|
|
||||||
.datepicker .prev,
|
|
||||||
.datepicker .next,
|
|
||||||
.datepicker tfoot tr th {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.datepicker .datepicker-switch:hover,
|
|
||||||
.datepicker .prev:hover,
|
|
||||||
.datepicker .next:hover,
|
|
||||||
.datepicker tfoot tr th:hover {
|
|
||||||
background: #eeeeee;
|
|
||||||
}
|
|
||||||
.datepicker .cw {
|
|
||||||
font-size: 10px;
|
|
||||||
width: 12px;
|
|
||||||
padding: 0 2px 0 5px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
.input-group.date .input-group-addon {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.input-daterange {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.input-daterange input {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.input-daterange input:first-child {
|
|
||||||
border-radius: 3px 0 0 3px;
|
|
||||||
}
|
|
||||||
.input-daterange input:last-child {
|
|
||||||
border-radius: 0 3px 3px 0;
|
|
||||||
}
|
|
||||||
.input-daterange .input-group-addon {
|
|
||||||
width: auto;
|
|
||||||
min-width: 16px;
|
|
||||||
padding: 4px 5px;
|
|
||||||
font-weight: normal;
|
|
||||||
line-height: 1.42857143;
|
|
||||||
text-align: center;
|
|
||||||
text-shadow: 0 1px 0 #fff;
|
|
||||||
vertical-align: middle;
|
|
||||||
background-color: #eeeeee;
|
|
||||||
border: solid #cccccc;
|
|
||||||
border-width: 1px 0;
|
|
||||||
margin-left: -5px;
|
|
||||||
margin-right: -5px;
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
|
@ -1,822 +0,0 @@
|
||||||
/*!
|
|
||||||
* Datepicker for Bootstrap v1.5.1 (https://github.com/eternicode/bootstrap-datepicker)
|
|
||||||
*
|
|
||||||
* Copyright 2012 Stefan Petre
|
|
||||||
* Improvements by Andrew Rowls
|
|
||||||
* Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
|
||||||
*/
|
|
||||||
.datepicker {
|
|
||||||
border-radius: 4px;
|
|
||||||
direction: ltr;
|
|
||||||
}
|
|
||||||
.datepicker-inline {
|
|
||||||
width: 220px;
|
|
||||||
}
|
|
||||||
.datepicker.datepicker-rtl {
|
|
||||||
direction: rtl;
|
|
||||||
}
|
|
||||||
.datepicker.datepicker-rtl table tr td span {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown {
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown:before {
|
|
||||||
content: '';
|
|
||||||
display: inline-block;
|
|
||||||
border-left: 7px solid transparent;
|
|
||||||
border-right: 7px solid transparent;
|
|
||||||
border-bottom: 7px solid rgba(0, 0, 0, 0.15);
|
|
||||||
border-top: 0;
|
|
||||||
border-bottom-color: rgba(0, 0, 0, 0.2);
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown:after {
|
|
||||||
content: '';
|
|
||||||
display: inline-block;
|
|
||||||
border-left: 6px solid transparent;
|
|
||||||
border-right: 6px solid transparent;
|
|
||||||
border-bottom: 6px solid #ffffff;
|
|
||||||
border-top: 0;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-left:before {
|
|
||||||
left: 6px;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-left:after {
|
|
||||||
left: 7px;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-right:before {
|
|
||||||
right: 6px;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-right:after {
|
|
||||||
right: 7px;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-bottom:before {
|
|
||||||
top: -7px;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-bottom:after {
|
|
||||||
top: -6px;
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-top:before {
|
|
||||||
bottom: -7px;
|
|
||||||
border-bottom: 0;
|
|
||||||
border-top: 7px solid rgba(0, 0, 0, 0.15);
|
|
||||||
}
|
|
||||||
.datepicker-dropdown.datepicker-orient-top:after {
|
|
||||||
bottom: -6px;
|
|
||||||
border-bottom: 0;
|
|
||||||
border-top: 6px solid #ffffff;
|
|
||||||
}
|
|
||||||
.datepicker > div {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.datepicker table {
|
|
||||||
margin: 0;
|
|
||||||
-webkit-touch-callout: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-khtml-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
.datepicker table tr td,
|
|
||||||
.datepicker table tr th {
|
|
||||||
text-align: center;
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
.table-striped .datepicker table tr td,
|
|
||||||
.table-striped .datepicker table tr th {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.old,
|
|
||||||
.datepicker table tr td.new {
|
|
||||||
color: #999999;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.day:hover,
|
|
||||||
.datepicker table tr td.focused {
|
|
||||||
background: #eeeeee;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.disabled,
|
|
||||||
.datepicker table tr td.disabled:hover {
|
|
||||||
background: none;
|
|
||||||
color: #999999;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.highlighted {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #d9edf7;
|
|
||||||
border-color: #85c5e5;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.highlighted:focus,
|
|
||||||
.datepicker table tr td.highlighted.focus {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #afd9ee;
|
|
||||||
border-color: #298fc2;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.highlighted:hover {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #afd9ee;
|
|
||||||
border-color: #52addb;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.highlighted:active,
|
|
||||||
.datepicker table tr td.highlighted.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.highlighted {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #afd9ee;
|
|
||||||
border-color: #52addb;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.highlighted:active:hover,
|
|
||||||
.datepicker table tr td.highlighted.active:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.highlighted:hover,
|
|
||||||
.datepicker table tr td.highlighted:active:focus,
|
|
||||||
.datepicker table tr td.highlighted.active:focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.highlighted:focus,
|
|
||||||
.datepicker table tr td.highlighted:active.focus,
|
|
||||||
.datepicker table tr td.highlighted.active.focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.highlighted.focus {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #91cbe8;
|
|
||||||
border-color: #298fc2;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.highlighted:active,
|
|
||||||
.datepicker table tr td.highlighted.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.highlighted {
|
|
||||||
background-image: none;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.highlighted.disabled:hover,
|
|
||||||
.datepicker table tr td.highlighted[disabled]:hover,
|
|
||||||
fieldset[disabled] .datepicker table tr td.highlighted:hover,
|
|
||||||
.datepicker table tr td.highlighted.disabled:focus,
|
|
||||||
.datepicker table tr td.highlighted[disabled]:focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.highlighted:focus,
|
|
||||||
.datepicker table tr td.highlighted.disabled.focus,
|
|
||||||
.datepicker table tr td.highlighted[disabled].focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.highlighted.focus {
|
|
||||||
background-color: #d9edf7;
|
|
||||||
border-color: #85c5e5;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.highlighted.focused {
|
|
||||||
background: #afd9ee;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.highlighted.disabled,
|
|
||||||
.datepicker table tr td.highlighted.disabled:active {
|
|
||||||
background: #d9edf7;
|
|
||||||
color: #999999;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.today {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #ffdb99;
|
|
||||||
border-color: #ffb733;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.today:focus,
|
|
||||||
.datepicker table tr td.today.focus {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #ffc966;
|
|
||||||
border-color: #b37400;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.today:hover {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #ffc966;
|
|
||||||
border-color: #f59e00;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.today:active,
|
|
||||||
.datepicker table tr td.today.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.today {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #ffc966;
|
|
||||||
border-color: #f59e00;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.today:active:hover,
|
|
||||||
.datepicker table tr td.today.active:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.today:hover,
|
|
||||||
.datepicker table tr td.today:active:focus,
|
|
||||||
.datepicker table tr td.today.active:focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.today:focus,
|
|
||||||
.datepicker table tr td.today:active.focus,
|
|
||||||
.datepicker table tr td.today.active.focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.today.focus {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #ffbc42;
|
|
||||||
border-color: #b37400;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.today:active,
|
|
||||||
.datepicker table tr td.today.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.today {
|
|
||||||
background-image: none;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.today.disabled:hover,
|
|
||||||
.datepicker table tr td.today[disabled]:hover,
|
|
||||||
fieldset[disabled] .datepicker table tr td.today:hover,
|
|
||||||
.datepicker table tr td.today.disabled:focus,
|
|
||||||
.datepicker table tr td.today[disabled]:focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.today:focus,
|
|
||||||
.datepicker table tr td.today.disabled.focus,
|
|
||||||
.datepicker table tr td.today[disabled].focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.today.focus {
|
|
||||||
background-color: #ffdb99;
|
|
||||||
border-color: #ffb733;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.today.focused {
|
|
||||||
background: #ffc966;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.today.disabled,
|
|
||||||
.datepicker table tr td.today.disabled:active {
|
|
||||||
background: #ffdb99;
|
|
||||||
color: #999999;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #eeeeee;
|
|
||||||
border-color: #bbbbbb;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range:focus,
|
|
||||||
.datepicker table tr td.range.focus {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #d5d5d5;
|
|
||||||
border-color: #7c7c7c;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range:hover {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #d5d5d5;
|
|
||||||
border-color: #9d9d9d;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range:active,
|
|
||||||
.datepicker table tr td.range.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #d5d5d5;
|
|
||||||
border-color: #9d9d9d;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range:active:hover,
|
|
||||||
.datepicker table tr td.range.active:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range:hover,
|
|
||||||
.datepicker table tr td.range:active:focus,
|
|
||||||
.datepicker table tr td.range.active:focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range:focus,
|
|
||||||
.datepicker table tr td.range:active.focus,
|
|
||||||
.datepicker table tr td.range.active.focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range.focus {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #c3c3c3;
|
|
||||||
border-color: #7c7c7c;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range:active,
|
|
||||||
.datepicker table tr td.range.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range {
|
|
||||||
background-image: none;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.disabled:hover,
|
|
||||||
.datepicker table tr td.range[disabled]:hover,
|
|
||||||
fieldset[disabled] .datepicker table tr td.range:hover,
|
|
||||||
.datepicker table tr td.range.disabled:focus,
|
|
||||||
.datepicker table tr td.range[disabled]:focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.range:focus,
|
|
||||||
.datepicker table tr td.range.disabled.focus,
|
|
||||||
.datepicker table tr td.range[disabled].focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.range.focus {
|
|
||||||
background-color: #eeeeee;
|
|
||||||
border-color: #bbbbbb;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.focused {
|
|
||||||
background: #d5d5d5;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.disabled,
|
|
||||||
.datepicker table tr td.range.disabled:active {
|
|
||||||
background: #eeeeee;
|
|
||||||
color: #999999;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.highlighted {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #e4eef3;
|
|
||||||
border-color: #9dc1d3;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.highlighted:focus,
|
|
||||||
.datepicker table tr td.range.highlighted.focus {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #c1d7e3;
|
|
||||||
border-color: #4b88a6;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.highlighted:hover {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #c1d7e3;
|
|
||||||
border-color: #73a6c0;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.highlighted:active,
|
|
||||||
.datepicker table tr td.range.highlighted.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range.highlighted {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #c1d7e3;
|
|
||||||
border-color: #73a6c0;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.highlighted:active:hover,
|
|
||||||
.datepicker table tr td.range.highlighted.active:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range.highlighted:hover,
|
|
||||||
.datepicker table tr td.range.highlighted:active:focus,
|
|
||||||
.datepicker table tr td.range.highlighted.active:focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range.highlighted:focus,
|
|
||||||
.datepicker table tr td.range.highlighted:active.focus,
|
|
||||||
.datepicker table tr td.range.highlighted.active.focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range.highlighted.focus {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #a8c8d8;
|
|
||||||
border-color: #4b88a6;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.highlighted:active,
|
|
||||||
.datepicker table tr td.range.highlighted.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range.highlighted {
|
|
||||||
background-image: none;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.highlighted.disabled:hover,
|
|
||||||
.datepicker table tr td.range.highlighted[disabled]:hover,
|
|
||||||
fieldset[disabled] .datepicker table tr td.range.highlighted:hover,
|
|
||||||
.datepicker table tr td.range.highlighted.disabled:focus,
|
|
||||||
.datepicker table tr td.range.highlighted[disabled]:focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.range.highlighted:focus,
|
|
||||||
.datepicker table tr td.range.highlighted.disabled.focus,
|
|
||||||
.datepicker table tr td.range.highlighted[disabled].focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.range.highlighted.focus {
|
|
||||||
background-color: #e4eef3;
|
|
||||||
border-color: #9dc1d3;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.highlighted.focused {
|
|
||||||
background: #c1d7e3;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.highlighted.disabled,
|
|
||||||
.datepicker table tr td.range.highlighted.disabled:active {
|
|
||||||
background: #e4eef3;
|
|
||||||
color: #999999;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.today {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #f7ca77;
|
|
||||||
border-color: #f1a417;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.today:focus,
|
|
||||||
.datepicker table tr td.range.today.focus {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #f4b747;
|
|
||||||
border-color: #815608;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.today:hover {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #f4b747;
|
|
||||||
border-color: #bf800c;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.today:active,
|
|
||||||
.datepicker table tr td.range.today.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range.today {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #f4b747;
|
|
||||||
border-color: #bf800c;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.today:active:hover,
|
|
||||||
.datepicker table tr td.range.today.active:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range.today:hover,
|
|
||||||
.datepicker table tr td.range.today:active:focus,
|
|
||||||
.datepicker table tr td.range.today.active:focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range.today:focus,
|
|
||||||
.datepicker table tr td.range.today:active.focus,
|
|
||||||
.datepicker table tr td.range.today.active.focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range.today.focus {
|
|
||||||
color: #000000;
|
|
||||||
background-color: #f2aa25;
|
|
||||||
border-color: #815608;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.today:active,
|
|
||||||
.datepicker table tr td.range.today.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.range.today {
|
|
||||||
background-image: none;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.today.disabled:hover,
|
|
||||||
.datepicker table tr td.range.today[disabled]:hover,
|
|
||||||
fieldset[disabled] .datepicker table tr td.range.today:hover,
|
|
||||||
.datepicker table tr td.range.today.disabled:focus,
|
|
||||||
.datepicker table tr td.range.today[disabled]:focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.range.today:focus,
|
|
||||||
.datepicker table tr td.range.today.disabled.focus,
|
|
||||||
.datepicker table tr td.range.today[disabled].focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.range.today.focus {
|
|
||||||
background-color: #f7ca77;
|
|
||||||
border-color: #f1a417;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.range.today.disabled,
|
|
||||||
.datepicker table tr td.range.today.disabled:active {
|
|
||||||
background: #f7ca77;
|
|
||||||
color: #999999;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.selected,
|
|
||||||
.datepicker table tr td.selected.highlighted {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #999999;
|
|
||||||
border-color: #555555;
|
|
||||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
.datepicker table tr td.selected:focus,
|
|
||||||
.datepicker table tr td.selected.highlighted:focus,
|
|
||||||
.datepicker table tr td.selected.focus,
|
|
||||||
.datepicker table tr td.selected.highlighted.focus {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #808080;
|
|
||||||
border-color: #161616;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.selected:hover,
|
|
||||||
.datepicker table tr td.selected.highlighted:hover {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #808080;
|
|
||||||
border-color: #373737;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.selected:active,
|
|
||||||
.datepicker table tr td.selected.highlighted:active,
|
|
||||||
.datepicker table tr td.selected.active,
|
|
||||||
.datepicker table tr td.selected.highlighted.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.selected,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.selected.highlighted {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #808080;
|
|
||||||
border-color: #373737;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.selected:active:hover,
|
|
||||||
.datepicker table tr td.selected.highlighted:active:hover,
|
|
||||||
.datepicker table tr td.selected.active:hover,
|
|
||||||
.datepicker table tr td.selected.highlighted.active:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.selected:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.selected.highlighted:hover,
|
|
||||||
.datepicker table tr td.selected:active:focus,
|
|
||||||
.datepicker table tr td.selected.highlighted:active:focus,
|
|
||||||
.datepicker table tr td.selected.active:focus,
|
|
||||||
.datepicker table tr td.selected.highlighted.active:focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.selected:focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.selected.highlighted:focus,
|
|
||||||
.datepicker table tr td.selected:active.focus,
|
|
||||||
.datepicker table tr td.selected.highlighted:active.focus,
|
|
||||||
.datepicker table tr td.selected.active.focus,
|
|
||||||
.datepicker table tr td.selected.highlighted.active.focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.selected.focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.selected.highlighted.focus {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #6e6e6e;
|
|
||||||
border-color: #161616;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.selected:active,
|
|
||||||
.datepicker table tr td.selected.highlighted:active,
|
|
||||||
.datepicker table tr td.selected.active,
|
|
||||||
.datepicker table tr td.selected.highlighted.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.selected,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.selected.highlighted {
|
|
||||||
background-image: none;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.selected.disabled:hover,
|
|
||||||
.datepicker table tr td.selected.highlighted.disabled:hover,
|
|
||||||
.datepicker table tr td.selected[disabled]:hover,
|
|
||||||
.datepicker table tr td.selected.highlighted[disabled]:hover,
|
|
||||||
fieldset[disabled] .datepicker table tr td.selected:hover,
|
|
||||||
fieldset[disabled] .datepicker table tr td.selected.highlighted:hover,
|
|
||||||
.datepicker table tr td.selected.disabled:focus,
|
|
||||||
.datepicker table tr td.selected.highlighted.disabled:focus,
|
|
||||||
.datepicker table tr td.selected[disabled]:focus,
|
|
||||||
.datepicker table tr td.selected.highlighted[disabled]:focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.selected:focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.selected.highlighted:focus,
|
|
||||||
.datepicker table tr td.selected.disabled.focus,
|
|
||||||
.datepicker table tr td.selected.highlighted.disabled.focus,
|
|
||||||
.datepicker table tr td.selected[disabled].focus,
|
|
||||||
.datepicker table tr td.selected.highlighted[disabled].focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.selected.focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.selected.highlighted.focus {
|
|
||||||
background-color: #999999;
|
|
||||||
border-color: #555555;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.active,
|
|
||||||
.datepicker table tr td.active.highlighted {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #428bca;
|
|
||||||
border-color: #357ebd;
|
|
||||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
.datepicker table tr td.active:focus,
|
|
||||||
.datepicker table tr td.active.highlighted:focus,
|
|
||||||
.datepicker table tr td.active.focus,
|
|
||||||
.datepicker table tr td.active.highlighted.focus {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #3071a9;
|
|
||||||
border-color: #193c5a;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.active:hover,
|
|
||||||
.datepicker table tr td.active.highlighted:hover {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #3071a9;
|
|
||||||
border-color: #285e8e;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.active:active,
|
|
||||||
.datepicker table tr td.active.highlighted:active,
|
|
||||||
.datepicker table tr td.active.active,
|
|
||||||
.datepicker table tr td.active.highlighted.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.active.highlighted {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #3071a9;
|
|
||||||
border-color: #285e8e;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.active:active:hover,
|
|
||||||
.datepicker table tr td.active.highlighted:active:hover,
|
|
||||||
.datepicker table tr td.active.active:hover,
|
|
||||||
.datepicker table tr td.active.highlighted.active:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.active:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.active.highlighted:hover,
|
|
||||||
.datepicker table tr td.active:active:focus,
|
|
||||||
.datepicker table tr td.active.highlighted:active:focus,
|
|
||||||
.datepicker table tr td.active.active:focus,
|
|
||||||
.datepicker table tr td.active.highlighted.active:focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.active:focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.active.highlighted:focus,
|
|
||||||
.datepicker table tr td.active:active.focus,
|
|
||||||
.datepicker table tr td.active.highlighted:active.focus,
|
|
||||||
.datepicker table tr td.active.active.focus,
|
|
||||||
.datepicker table tr td.active.highlighted.active.focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.active.focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.active.highlighted.focus {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #285e8e;
|
|
||||||
border-color: #193c5a;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.active:active,
|
|
||||||
.datepicker table tr td.active.highlighted:active,
|
|
||||||
.datepicker table tr td.active.active,
|
|
||||||
.datepicker table tr td.active.highlighted.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td.active.highlighted {
|
|
||||||
background-image: none;
|
|
||||||
}
|
|
||||||
.datepicker table tr td.active.disabled:hover,
|
|
||||||
.datepicker table tr td.active.highlighted.disabled:hover,
|
|
||||||
.datepicker table tr td.active[disabled]:hover,
|
|
||||||
.datepicker table tr td.active.highlighted[disabled]:hover,
|
|
||||||
fieldset[disabled] .datepicker table tr td.active:hover,
|
|
||||||
fieldset[disabled] .datepicker table tr td.active.highlighted:hover,
|
|
||||||
.datepicker table tr td.active.disabled:focus,
|
|
||||||
.datepicker table tr td.active.highlighted.disabled:focus,
|
|
||||||
.datepicker table tr td.active[disabled]:focus,
|
|
||||||
.datepicker table tr td.active.highlighted[disabled]:focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.active:focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.active.highlighted:focus,
|
|
||||||
.datepicker table tr td.active.disabled.focus,
|
|
||||||
.datepicker table tr td.active.highlighted.disabled.focus,
|
|
||||||
.datepicker table tr td.active[disabled].focus,
|
|
||||||
.datepicker table tr td.active.highlighted[disabled].focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.active.focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td.active.highlighted.focus {
|
|
||||||
background-color: #428bca;
|
|
||||||
border-color: #357ebd;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span {
|
|
||||||
display: block;
|
|
||||||
width: 23%;
|
|
||||||
height: 54px;
|
|
||||||
line-height: 54px;
|
|
||||||
float: left;
|
|
||||||
margin: 1%;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span:hover {
|
|
||||||
background: #eeeeee;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span.disabled,
|
|
||||||
.datepicker table tr td span.disabled:hover {
|
|
||||||
background: none;
|
|
||||||
color: #999999;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span.active,
|
|
||||||
.datepicker table tr td span.active:hover,
|
|
||||||
.datepicker table tr td span.active.disabled,
|
|
||||||
.datepicker table tr td span.active.disabled:hover {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #428bca;
|
|
||||||
border-color: #357ebd;
|
|
||||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
.datepicker table tr td span.active:focus,
|
|
||||||
.datepicker table tr td span.active:hover:focus,
|
|
||||||
.datepicker table tr td span.active.disabled:focus,
|
|
||||||
.datepicker table tr td span.active.disabled:hover:focus,
|
|
||||||
.datepicker table tr td span.active.focus,
|
|
||||||
.datepicker table tr td span.active:hover.focus,
|
|
||||||
.datepicker table tr td span.active.disabled.focus,
|
|
||||||
.datepicker table tr td span.active.disabled:hover.focus {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #3071a9;
|
|
||||||
border-color: #193c5a;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span.active:hover,
|
|
||||||
.datepicker table tr td span.active:hover:hover,
|
|
||||||
.datepicker table tr td span.active.disabled:hover,
|
|
||||||
.datepicker table tr td span.active.disabled:hover:hover {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #3071a9;
|
|
||||||
border-color: #285e8e;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span.active:active,
|
|
||||||
.datepicker table tr td span.active:hover:active,
|
|
||||||
.datepicker table tr td span.active.disabled:active,
|
|
||||||
.datepicker table tr td span.active.disabled:hover:active,
|
|
||||||
.datepicker table tr td span.active.active,
|
|
||||||
.datepicker table tr td span.active:hover.active,
|
|
||||||
.datepicker table tr td span.active.disabled.active,
|
|
||||||
.datepicker table tr td span.active.disabled:hover.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active.disabled,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #3071a9;
|
|
||||||
border-color: #285e8e;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span.active:active:hover,
|
|
||||||
.datepicker table tr td span.active:hover:active:hover,
|
|
||||||
.datepicker table tr td span.active.disabled:active:hover,
|
|
||||||
.datepicker table tr td span.active.disabled:hover:active:hover,
|
|
||||||
.datepicker table tr td span.active.active:hover,
|
|
||||||
.datepicker table tr td span.active:hover.active:hover,
|
|
||||||
.datepicker table tr td span.active.disabled.active:hover,
|
|
||||||
.datepicker table tr td span.active.disabled:hover.active:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active:hover:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover:hover,
|
|
||||||
.datepicker table tr td span.active:active:focus,
|
|
||||||
.datepicker table tr td span.active:hover:active:focus,
|
|
||||||
.datepicker table tr td span.active.disabled:active:focus,
|
|
||||||
.datepicker table tr td span.active.disabled:hover:active:focus,
|
|
||||||
.datepicker table tr td span.active.active:focus,
|
|
||||||
.datepicker table tr td span.active:hover.active:focus,
|
|
||||||
.datepicker table tr td span.active.disabled.active:focus,
|
|
||||||
.datepicker table tr td span.active.disabled:hover.active:focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active:focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active:hover:focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active.disabled:focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover:focus,
|
|
||||||
.datepicker table tr td span.active:active.focus,
|
|
||||||
.datepicker table tr td span.active:hover:active.focus,
|
|
||||||
.datepicker table tr td span.active.disabled:active.focus,
|
|
||||||
.datepicker table tr td span.active.disabled:hover:active.focus,
|
|
||||||
.datepicker table tr td span.active.active.focus,
|
|
||||||
.datepicker table tr td span.active:hover.active.focus,
|
|
||||||
.datepicker table tr td span.active.disabled.active.focus,
|
|
||||||
.datepicker table tr td span.active.disabled:hover.active.focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active.focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active:hover.focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active.disabled.focus,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover.focus {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #285e8e;
|
|
||||||
border-color: #193c5a;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span.active:active,
|
|
||||||
.datepicker table tr td span.active:hover:active,
|
|
||||||
.datepicker table tr td span.active.disabled:active,
|
|
||||||
.datepicker table tr td span.active.disabled:hover:active,
|
|
||||||
.datepicker table tr td span.active.active,
|
|
||||||
.datepicker table tr td span.active:hover.active,
|
|
||||||
.datepicker table tr td span.active.disabled.active,
|
|
||||||
.datepicker table tr td span.active.disabled:hover.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active:hover,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active.disabled,
|
|
||||||
.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover {
|
|
||||||
background-image: none;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span.active.disabled:hover,
|
|
||||||
.datepicker table tr td span.active:hover.disabled:hover,
|
|
||||||
.datepicker table tr td span.active.disabled.disabled:hover,
|
|
||||||
.datepicker table tr td span.active.disabled:hover.disabled:hover,
|
|
||||||
.datepicker table tr td span.active[disabled]:hover,
|
|
||||||
.datepicker table tr td span.active:hover[disabled]:hover,
|
|
||||||
.datepicker table tr td span.active.disabled[disabled]:hover,
|
|
||||||
.datepicker table tr td span.active.disabled:hover[disabled]:hover,
|
|
||||||
fieldset[disabled] .datepicker table tr td span.active:hover,
|
|
||||||
fieldset[disabled] .datepicker table tr td span.active:hover:hover,
|
|
||||||
fieldset[disabled] .datepicker table tr td span.active.disabled:hover,
|
|
||||||
fieldset[disabled] .datepicker table tr td span.active.disabled:hover:hover,
|
|
||||||
.datepicker table tr td span.active.disabled:focus,
|
|
||||||
.datepicker table tr td span.active:hover.disabled:focus,
|
|
||||||
.datepicker table tr td span.active.disabled.disabled:focus,
|
|
||||||
.datepicker table tr td span.active.disabled:hover.disabled:focus,
|
|
||||||
.datepicker table tr td span.active[disabled]:focus,
|
|
||||||
.datepicker table tr td span.active:hover[disabled]:focus,
|
|
||||||
.datepicker table tr td span.active.disabled[disabled]:focus,
|
|
||||||
.datepicker table tr td span.active.disabled:hover[disabled]:focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td span.active:focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td span.active:hover:focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td span.active.disabled:focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td span.active.disabled:hover:focus,
|
|
||||||
.datepicker table tr td span.active.disabled.focus,
|
|
||||||
.datepicker table tr td span.active:hover.disabled.focus,
|
|
||||||
.datepicker table tr td span.active.disabled.disabled.focus,
|
|
||||||
.datepicker table tr td span.active.disabled:hover.disabled.focus,
|
|
||||||
.datepicker table tr td span.active[disabled].focus,
|
|
||||||
.datepicker table tr td span.active:hover[disabled].focus,
|
|
||||||
.datepicker table tr td span.active.disabled[disabled].focus,
|
|
||||||
.datepicker table tr td span.active.disabled:hover[disabled].focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td span.active.focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td span.active:hover.focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td span.active.disabled.focus,
|
|
||||||
fieldset[disabled] .datepicker table tr td span.active.disabled:hover.focus {
|
|
||||||
background-color: #428bca;
|
|
||||||
border-color: #357ebd;
|
|
||||||
}
|
|
||||||
.datepicker table tr td span.old,
|
|
||||||
.datepicker table tr td span.new {
|
|
||||||
color: #999999;
|
|
||||||
}
|
|
||||||
.datepicker .datepicker-switch {
|
|
||||||
width: 145px;
|
|
||||||
}
|
|
||||||
.datepicker .datepicker-switch,
|
|
||||||
.datepicker .prev,
|
|
||||||
.datepicker .next,
|
|
||||||
.datepicker tfoot tr th {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.datepicker .datepicker-switch:hover,
|
|
||||||
.datepicker .prev:hover,
|
|
||||||
.datepicker .next:hover,
|
|
||||||
.datepicker tfoot tr th:hover {
|
|
||||||
background: #eeeeee;
|
|
||||||
}
|
|
||||||
.datepicker .cw {
|
|
||||||
font-size: 10px;
|
|
||||||
width: 12px;
|
|
||||||
padding: 0 2px 0 5px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
.input-group.date .input-group-addon {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.input-daterange {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.input-daterange input {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.input-daterange input:first-child {
|
|
||||||
border-radius: 3px 0 0 3px;
|
|
||||||
}
|
|
||||||
.input-daterange input:last-child {
|
|
||||||
border-radius: 0 3px 3px 0;
|
|
||||||
}
|
|
||||||
.input-daterange .input-group-addon {
|
|
||||||
width: auto;
|
|
||||||
min-width: 16px;
|
|
||||||
padding: 4px 5px;
|
|
||||||
font-weight: normal;
|
|
||||||
line-height: 1.42857143;
|
|
||||||
text-align: center;
|
|
||||||
text-shadow: 0 1px 0 #fff;
|
|
||||||
vertical-align: middle;
|
|
||||||
background-color: #eeeeee;
|
|
||||||
border: solid #cccccc;
|
|
||||||
border-width: 1px 0;
|
|
||||||
margin-left: -5px;
|
|
||||||
margin-right: -5px;
|
|
||||||
}
|
|
||||||
.datepicker.dropdown-menu {
|
|
||||||
position: absolute;
|
|
||||||
top: 100%;
|
|
||||||
left: 0;
|
|
||||||
z-index: 1000;
|
|
||||||
float: left;
|
|
||||||
display: none;
|
|
||||||
min-width: 160px;
|
|
||||||
list-style: none;
|
|
||||||
background-color: #ffffff;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
|
||||||
border-radius: 5px;
|
|
||||||
-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
|
||||||
-moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
|
||||||
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
|
||||||
-webkit-background-clip: padding-box;
|
|
||||||
-moz-background-clip: padding;
|
|
||||||
background-clip: padding-box;
|
|
||||||
*border-right-width: 2px;
|
|
||||||
*border-bottom-width: 2px;
|
|
||||||
color: #333333;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.42857143;
|
|
||||||
}
|
|
||||||
.datepicker.dropdown-menu th,
|
|
||||||
.datepicker.datepicker-inline th,
|
|
||||||
.datepicker.dropdown-menu td,
|
|
||||||
.datepicker.datepicker-inline td {
|
|
||||||
padding: 0px 5px;
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
1918
obsolete/public/datepicker/js/bootstrap-datepicker.js
vendored
1918
obsolete/public/datepicker/js/bootstrap-datepicker.js
vendored
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.ar={days:["الأحد","الاثنين","الثلاثاء","الأربعاء","الخميس","الجمعة","السبت","الأحد"],daysShort:["أحد","اثنين","ثلاثاء","أربعاء","خميس","جمعة","سبت","أحد"],daysMin:["ح","ن","ث","ع","خ","ج","س","ح"],months:["يناير","فبراير","مارس","أبريل","مايو","يونيو","يوليو","أغسطس","سبتمبر","أكتوبر","نوفمبر","ديسمبر"],monthsShort:["يناير","فبراير","مارس","أبريل","مايو","يونيو","يوليو","أغسطس","سبتمبر","أكتوبر","نوفمبر","ديسمبر"],today:"هذا اليوم",rtl:!0}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.az={days:["Bazar","Bazar ertəsi","Çərşənbə axşamı","Çərşənbə","Cümə axşamı","Cümə","Şənbə"],daysShort:["B.","B.e","Ç.a","Ç.","C.a","C.","Ş."],daysMin:["B.","B.e","Ç.a","Ç.","C.a","C.","Ş."],months:["Yanvar","Fevral","Mart","Aprel","May","İyun","İyul","Avqust","Sentyabr","Oktyabr","Noyabr","Dekabr"],monthsShort:["Yan","Fev","Mar","Apr","May","İyun","İyul","Avq","Sen","Okt","Noy","Dek"],today:"Bu gün",weekStart:1}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.bg={days:["Неделя","Понеделник","Вторник","Сряда","Четвъртък","Петък","Събота"],daysShort:["Нед","Пон","Вто","Сря","Чет","Пет","Съб"],daysMin:["Н","П","В","С","Ч","П","С"],months:["Януари","Февруари","Март","Април","Май","Юни","Юли","Август","Септември","Октомври","Ноември","Декември"],monthsShort:["Ян","Фев","Мар","Апр","Май","Юни","Юли","Авг","Сеп","Окт","Ное","Дек"],today:"днес"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.bs={days:["Nedjelja","Ponedjeljak","Utorak","Srijeda","Četvrtak","Petak","Subota"],daysShort:["Ned","Pon","Uto","Sri","Čet","Pet","Sub"],daysMin:["N","Po","U","Sr","Č","Pe","Su"],months:["Januar","Februar","Mart","April","Maj","Juni","Juli","August","Septembar","Oktobar","Novembar","Decembar"],monthsShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],today:"Danas",weekStart:1,format:"dd.mm.yyyy"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.ca={days:["Diumenge","Dilluns","Dimarts","Dimecres","Dijous","Divendres","Dissabte"],daysShort:["Diu","Dil","Dmt","Dmc","Dij","Div","Dis"],daysMin:["dg","dl","dt","dc","dj","dv","ds"],months:["Gener","Febrer","Març","Abril","Maig","Juny","Juliol","Agost","Setembre","Octubre","Novembre","Desembre"],monthsShort:["Gen","Feb","Mar","Abr","Mai","Jun","Jul","Ago","Set","Oct","Nov","Des"],today:"Avui",monthsTitle:"Mesos",clear:"Esborrar",weekStart:1,format:"dd/mm/yyyy"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.cs={days:["Neděle","Pondělí","Úterý","Středa","Čtvrtek","Pátek","Sobota"],daysShort:["Ned","Pon","Úte","Stř","Čtv","Pát","Sob"],daysMin:["Ne","Po","Út","St","Čt","Pá","So"],months:["Leden","Únor","Březen","Duben","Květen","Červen","Červenec","Srpen","Září","Říjen","Listopad","Prosinec"],monthsShort:["Led","Úno","Bře","Dub","Kvě","Čer","Čnc","Srp","Zář","Říj","Lis","Pro"],today:"Dnes",clear:"Vymazat",weekStart:1,format:"dd.m.yyyy"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.cy={days:["Sul","Llun","Mawrth","Mercher","Iau","Gwener","Sadwrn"],daysShort:["Sul","Llu","Maw","Mer","Iau","Gwe","Sad"],daysMin:["Su","Ll","Ma","Me","Ia","Gwe","Sa"],months:["Ionawr","Chewfror","Mawrth","Ebrill","Mai","Mehefin","Gorfennaf","Awst","Medi","Hydref","Tachwedd","Rhagfyr"],monthsShort:["Ion","Chw","Maw","Ebr","Mai","Meh","Gor","Aws","Med","Hyd","Tach","Rha"],today:"Heddiw"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.da={days:["søndag","mandag","tirsdag","onsdag","torsdag","fredag","lørdag"],daysShort:["søn","man","tir","ons","tor","fre","lør"],daysMin:["sø","ma","ti","on","to","fr","lø"],months:["januar","februar","marts","april","maj","juni","juli","august","september","oktober","november","december"],monthsShort:["jan","feb","mar","apr","maj","jun","jul","aug","sep","okt","nov","dec"],today:"I Dag",clear:"Nulstil"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.de={days:["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"],daysShort:["Son","Mon","Die","Mit","Don","Fre","Sam"],daysMin:["So","Mo","Di","Mi","Do","Fr","Sa"],months:["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],monthsShort:["Jan","Feb","Mär","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez"],today:"Heute",monthsTitle:"Monate",clear:"Löschen",weekStart:1,format:"dd.mm.yyyy"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.el={days:["Κυριακή","Δευτέρα","Τρίτη","Τετάρτη","Πέμπτη","Παρασκευή","Σάββατο"],daysShort:["Κυρ","Δευ","Τρι","Τετ","Πεμ","Παρ","Σαβ"],daysMin:["Κυ","Δε","Τρ","Τε","Πε","Πα","Σα"],months:["Ιανουάριος","Φεβρουάριος","Μάρτιος","Απρίλιος","Μάιος","Ιούνιος","Ιούλιος","Αύγουστος","Σεπτέμβριος","Οκτώβριος","Νοέμβριος","Δεκέμβριος"],monthsShort:["Ιαν","Φεβ","Μαρ","Απρ","Μάι","Ιουν","Ιουλ","Αυγ","Σεπ","Οκτ","Νοε","Δεκ"],today:"Σήμερα",clear:"Καθαρισμός",weekStart:1,format:"d/m/yyyy"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates["en-GB"]={days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],daysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],daysMin:["Su","Mo","Tu","We","Th","Fr","Sa"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],monthsShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],today:"Today",monthsTitle:"Months",clear:"Clear",weekStart:1,format:"dd/mm/yyyy"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.eo={days:["dimanĉo","lundo","mardo","merkredo","ĵaŭdo","vendredo","sabato"],daysShort:["dim.","lun.","mar.","mer.","ĵaŭ.","ven.","sam."],daysMin:["d","l","ma","me","ĵ","v","s"],months:["januaro","februaro","marto","aprilo","majo","junio","julio","aŭgusto","septembro","oktobro","novembro","decembro"],monthsShort:["jan.","feb.","mar.","apr.","majo","jun.","jul.","aŭg.","sep.","okt.","nov.","dec."],today:"Hodiaŭ",clear:"Nuligi",weekStart:1,format:"yyyy-mm-dd"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.es={days:["Domingo","Lunes","Martes","Miércoles","Jueves","Viernes","Sábado"],daysShort:["Dom","Lun","Mar","Mié","Jue","Vie","Sáb"],daysMin:["Do","Lu","Ma","Mi","Ju","Vi","Sa"],months:["Enero","Febrero","Marzo","Abril","Mayo","Junio","Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre"],monthsShort:["Ene","Feb","Mar","Abr","May","Jun","Jul","Ago","Sep","Oct","Nov","Dic"],today:"Hoy",monthsTitle:"Meses",clear:"Borrar",weekStart:1,format:"dd/mm/yyyy"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.et={days:["Pühapäev","Esmaspäev","Teisipäev","Kolmapäev","Neljapäev","Reede","Laupäev"],daysShort:["Pühap","Esmasp","Teisip","Kolmap","Neljap","Reede","Laup"],daysMin:["P","E","T","K","N","R","L"],months:["Jaanuar","Veebruar","Märts","Aprill","Mai","Juuni","Juuli","August","September","Oktoober","November","Detsember"],monthsShort:["Jaan","Veebr","Märts","Apr","Mai","Juuni","Juuli","Aug","Sept","Okt","Nov","Dets"],today:"Täna",clear:"Tühjenda",weekStart:1,format:"dd.mm.yyyy"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.eu={days:["Igandea","Astelehena","Asteartea","Asteazkena","Osteguna","Ostirala","Larunbata"],daysShort:["Ig","Al","Ar","Az","Og","Ol","Lr"],daysMin:["Ig","Al","Ar","Az","Og","Ol","Lr"],months:["Urtarrila","Otsaila","Martxoa","Apirila","Maiatza","Ekaina","Uztaila","Abuztua","Iraila","Urria","Azaroa","Abendua"],monthsShort:["Urt","Ots","Mar","Api","Mai","Eka","Uzt","Abu","Ira","Urr","Aza","Abe"],today:"Gaur"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.fa={days:["یکشنبه","دوشنبه","سهشنبه","چهارشنبه","پنجشنبه","جمعه","شنبه","یکشنبه"],daysShort:["یک","دو","سه","چهار","پنج","جمعه","شنبه","یک"],daysMin:["ی","د","س","چ","پ","ج","ش","ی"],months:["ژانویه","فوریه","مارس","آوریل","مه","ژوئن","ژوئیه","اوت","سپتامبر","اکتبر","نوامبر","دسامبر"],monthsShort:["ژان","فور","مار","آور","مه","ژون","ژوی","اوت","سپت","اکت","نوا","دسا"],today:"امروز",clear:"پاک کن",weekStart:1,format:"yyyy/mm/dd"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.fi={days:["sunnuntai","maanantai","tiistai","keskiviikko","torstai","perjantai","lauantai"],daysShort:["sun","maa","tii","kes","tor","per","lau"],daysMin:["su","ma","ti","ke","to","pe","la"],months:["tammikuu","helmikuu","maaliskuu","huhtikuu","toukokuu","kesäkuu","heinäkuu","elokuu","syyskuu","lokakuu","marraskuu","joulukuu"],monthsShort:["tam","hel","maa","huh","tou","kes","hei","elo","syy","lok","mar","jou"],today:"tänään",clear:"Tyhjennä",weekStart:1,format:"d.m.yyyy"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.fo={days:["Sunnudagur","Mánadagur","Týsdagur","Mikudagur","Hósdagur","Fríggjadagur","Leygardagur"],daysShort:["Sun","Mán","Týs","Mik","Hós","Frí","Ley"],daysMin:["Su","Má","Tý","Mi","Hó","Fr","Le"],months:["Januar","Februar","Marts","Apríl","Mei","Juni","Juli","August","Septembur","Oktobur","Novembur","Desembur"],monthsShort:["Jan","Feb","Mar","Apr","Mei","Jun","Jul","Aug","Sep","Okt","Nov","Des"],today:"Í Dag",clear:"Reinsa"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.fr={days:["Dimanche","Lundi","Mardi","Mercredi","Jeudi","Vendredi","Samedi"],daysShort:["Dim","Lun","Mar","Mer","Jeu","Ven","Sam"],daysMin:["D","L","Ma","Me","J","V","S"],months:["Janvier","Février","Mars","Avril","Mai","Juin","Juillet","Août","Septembre","Octobre","Novembre","Décembre"],monthsShort:["Jan","Fév","Mar","Avr","Mai","Jui","Jul","Aou","Sep","Oct","Nov","Déc"],today:"Aujourd'hui",monthsTitle:"Mois",clear:"Effacer",weekStart:1,format:"dd.mm.yyyy"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.fr={days:["dimanche","lundi","mardi","mercredi","jeudi","vendredi","samedi"],daysShort:["dim.","lun.","mar.","mer.","jeu.","ven.","sam."],daysMin:["d","l","ma","me","j","v","s"],months:["janvier","février","mars","avril","mai","juin","juillet","août","septembre","octobre","novembre","décembre"],monthsShort:["janv.","févr.","mars","avril","mai","juin","juil.","août","sept.","oct.","nov.","déc."],today:"Aujourd'hui",monthsTitle:"Mois",clear:"Effacer",weekStart:1,format:"dd/mm/yyyy"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.gl={days:["Domingo","Luns","Martes","Mércores","Xoves","Venres","Sábado"],daysShort:["Dom","Lun","Mar","Mér","Xov","Ven","Sáb"],daysMin:["Do","Lu","Ma","Me","Xo","Ve","Sa"],months:["Xaneiro","Febreiro","Marzo","Abril","Maio","Xuño","Xullo","Agosto","Setembro","Outubro","Novembro","Decembro"],monthsShort:["Xan","Feb","Mar","Abr","Mai","Xun","Xul","Ago","Sep","Out","Nov","Dec"],today:"Hoxe",clear:"Limpar",weekStart:1,format:"dd/mm/yyyy"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.he={days:["ראשון","שני","שלישי","רביעי","חמישי","שישי","שבת","ראשון"],daysShort:["א","ב","ג","ד","ה","ו","ש","א"],daysMin:["א","ב","ג","ד","ה","ו","ש","א"],months:["ינואר","פברואר","מרץ","אפריל","מאי","יוני","יולי","אוגוסט","ספטמבר","אוקטובר","נובמבר","דצמבר"],monthsShort:["ינו","פבר","מרץ","אפר","מאי","יונ","יול","אוג","ספט","אוק","נוב","דצמ"],today:"היום",rtl:!0}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.hr={days:["Nedjelja","Ponedjeljak","Utorak","Srijeda","Četvrtak","Petak","Subota"],daysShort:["Ned","Pon","Uto","Sri","Čet","Pet","Sub"],daysMin:["Ne","Po","Ut","Sr","Če","Pe","Su"],months:["Siječanj","Veljača","Ožujak","Travanj","Svibanj","Lipanj","Srpanj","Kolovoz","Rujan","Listopad","Studeni","Prosinac"],monthsShort:["Sij","Velj","Ožu","Tra","Svi","Lip","Srp","Kol","Ruj","Lis","Stu","Pro"],today:"Danas"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.hu={days:["vasárnap","hétfő","kedd","szerda","csütörtök","péntek","szombat"],daysShort:["vas","hét","ked","sze","csü","pén","szo"],daysMin:["V","H","K","Sze","Cs","P","Szo"],months:["január","február","március","április","május","június","július","augusztus","szeptember","október","november","december"],monthsShort:["jan","feb","már","ápr","máj","jún","júl","aug","sze","okt","nov","dec"],today:"ma",weekStart:1,clear:"töröl",titleFormat:"yyyy. MM",format:"yyyy.mm.dd"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.hy={days:["Կիրակի","Երկուշաբթի","Երեքշաբթի","Չորեքշաբթի","Հինգշաբթի","Ուրբաթ","Շաբաթ"],daysShort:["Կրկ","Երկ","Երք","Չրք","Հնգ","Ուր","Շբթ"],daysMin:["Կրկ","Երկ","Երք","Չրք","Հնգ","Ուր","Շբթ"],months:["Հունվար","Փետրվար","Մարտ","Ապրիլ","Մայիս","Հունիս","Հուլիս","Օգոստոս","Սեպտեմբեր","Հոկտեմբեր","Նոյեմբեր","Դեկտեմբեր"],monthsShort:["Հուն","Փետ","Մար","Ապր","Մայ","Հնս","Հլս","Օգս","Սեպ","Հոկ","Նմբ","Դեկ"],today:"Այսօր",clear:"Ջնջել",format:"dd.mm.yyyy",weekStart:1}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.id={days:["Minggu","Senin","Selasa","Rabu","Kamis","Jumat","Sabtu"],daysShort:["Mgu","Sen","Sel","Rab","Kam","Jum","Sab"],daysMin:["Mg","Sn","Sl","Ra","Ka","Ju","Sa"],months:["Januari","Februari","Maret","April","Mei","Juni","Juli","Agustus","September","Oktober","November","Desember"],monthsShort:["Jan","Feb","Mar","Apr","Mei","Jun","Jul","Ags","Sep","Okt","Nov","Des"],today:"Hari Ini",clear:"Kosongkan"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.is={days:["Sunnudagur","Mánudagur","Þriðjudagur","Miðvikudagur","Fimmtudagur","Föstudagur","Laugardagur"],daysShort:["Sun","Mán","Þri","Mið","Fim","Fös","Lau"],daysMin:["Su","Má","Þr","Mi","Fi","Fö","La"],months:["Janúar","Febrúar","Mars","Apríl","Maí","Júní","Júlí","Ágúst","September","Október","Nóvember","Desember"],monthsShort:["Jan","Feb","Mar","Apr","Maí","Jún","Júl","Ágú","Sep","Okt","Nóv","Des"],today:"Í Dag"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.it={days:["Domenica","Lunedì","Martedì","Mercoledì","Giovedì","Venerdì","Sabato"],daysShort:["Dom","Lun","Mar","Mer","Gio","Ven","Sab"],daysMin:["Do","Lu","Ma","Me","Gi","Ve","Sa"],months:["Gennaio","Febbraio","Marzo","Aprile","Maggio","Giugno","Luglio","Agosto","Settembre","Ottobre","Novembre","Dicembre"],monthsShort:["Gen","Feb","Mar","Apr","Mag","Giu","Lug","Ago","Set","Ott","Nov","Dic"],today:"Oggi",clear:"Cancella",weekStart:1,format:"dd.mm.yyyy"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.it={days:["Domenica","Lunedì","Martedì","Mercoledì","Giovedì","Venerdì","Sabato"],daysShort:["Dom","Lun","Mar","Mer","Gio","Ven","Sab"],daysMin:["Do","Lu","Ma","Me","Gi","Ve","Sa"],months:["Gennaio","Febbraio","Marzo","Aprile","Maggio","Giugno","Luglio","Agosto","Settembre","Ottobre","Novembre","Dicembre"],monthsShort:["Gen","Feb","Mar","Apr","Mag","Giu","Lug","Ago","Set","Ott","Nov","Dic"],today:"Oggi",monthsTitle:"Mesi",clear:"Cancella",weekStart:1,format:"dd/mm/yyyy"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.ja={days:["日曜","月曜","火曜","水曜","木曜","金曜","土曜"],daysShort:["日","月","火","水","木","金","土"],daysMin:["日","月","火","水","木","金","土"],months:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],monthsShort:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],today:"今日",format:"yyyy/mm/dd",titleFormat:"yyyy年mm月",clear:"クリア"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.ka={days:["კვირა","ორშაბათი","სამშაბათი","ოთხშაბათი","ხუთშაბათი","პარასკევი","შაბათი"],daysShort:["კვი","ორშ","სამ","ოთხ","ხუთ","პარ","შაბ"],daysMin:["კვ","ორ","სა","ოთ","ხუ","პა","შა"],months:["იანვარი","თებერვალი","მარტი","აპრილი","მაისი","ივნისი","ივლისი","აგვისტო","სექტემბერი","ოქტომები","ნოემბერი","დეკემბერი"],monthsShort:["იან","თებ","მარ","აპრ","მაი","ივნ","ივლ","აგვ","სექ","ოქტ","ნოე","დეკ"],today:"დღეს",clear:"გასუფთავება",weekStart:1,format:"dd.mm.yyyy"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.kh={days:["អាទិត្យ","ចន្ទ","អង្គារ","ពុធ","ព្រហស្បតិ៍","សុក្រ","សៅរ៍","អាទិត្យ"],daysShort:["អា.ទិ","ចន្ទ","អង្គារ","ពុធ","ព្រ.ហ","សុក្រ","សៅរ៍","អា.ទិ"],daysMin:["អា.ទិ","ចន្ទ","អង្គារ","ពុធ","ព្រ.ហ","សុក្រ","សៅរ៍","អា.ទិ"],months:["មករា","កុម្ភះ","មិនា","មេសា","ឧសភា","មិថុនា","កក្កដា","សីហា","កញ្ញា","តុលា","វិច្ឆិកា","ធ្នូ"],monthsShort:["មករា","កុម្ភះ","មិនា","មេសា","ឧសភា","មិថុនា","កក្កដា","សីហា","កញ្ញា","តុលា","វិច្ឆិកា","ធ្នូ"],today:"ថ្ងៃនេះ",clear:"សំអាត"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.kk={days:["Жексенбі","Дүйсенбі","Сейсенбі","Сәрсенбі","Бейсенбі","Жұма","Сенбі"],daysShort:["Жек","Дүй","Сей","Сәр","Бей","Жұм","Сен"],daysMin:["Жк","Дс","Сс","Ср","Бс","Жм","Сн"],months:["Қаңтар","Ақпан","Наурыз","Сәуір","Мамыр","Маусым","Шілде","Тамыз","Қыркүйек","Қазан","Қараша","Желтоқсан"],monthsShort:["Қаң","Ақп","Нау","Сәу","Мамыр","Мау","Шлд","Тмз","Қыр","Қзн","Қар","Жел"],today:"Бүгін",weekStart:1}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.ko={days:["일요일","월요일","화요일","수요일","목요일","금요일","토요일"],daysShort:["일","월","화","수","목","금","토"],daysMin:["일","월","화","수","목","금","토"],months:["1월","2월","3월","4월","5월","6월","7월","8월","9월","10월","11월","12월"],monthsShort:["1월","2월","3월","4월","5월","6월","7월","8월","9월","10월","11월","12월"],today:"오늘",clear:"삭제",format:"YYYY-MM-DD",titleFormat:"yyyy년mm월",weekStart:0}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.kr={days:["일요일","월요일","화요일","수요일","목요일","금요일","토요일"],daysShort:["일","월","화","수","목","금","토"],daysMin:["일","월","화","수","목","금","토"],months:["1월","2월","3월","4월","5월","6월","7월","8월","9월","10월","11월","12월"],monthsShort:["1월","2월","3월","4월","5월","6월","7월","8월","9월","10월","11월","12월"]}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.lt={days:["Sekmadienis","Pirmadienis","Antradienis","Trečiadienis","Ketvirtadienis","Penktadienis","Šeštadienis"],daysShort:["S","Pr","A","T","K","Pn","Š"],daysMin:["Sk","Pr","An","Tr","Ke","Pn","Št"],months:["Sausis","Vasaris","Kovas","Balandis","Gegužė","Birželis","Liepa","Rugpjūtis","Rugsėjis","Spalis","Lapkritis","Gruodis"],monthsShort:["Sau","Vas","Kov","Bal","Geg","Bir","Lie","Rugp","Rugs","Spa","Lap","Gru"],today:"Šiandien",monthsTitle:"Mėnesiai",clear:"Išvalyti",weekStart:1,format:"yyyy-mm-dd"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.lv={days:["Svētdiena","Pirmdiena","Otrdiena","Trešdiena","Ceturtdiena","Piektdiena","Sestdiena"],daysShort:["Sv","P","O","T","C","Pk","S"],daysMin:["Sv","Pr","Ot","Tr","Ce","Pk","Se"],months:["Janvāris","Februāris","Marts","Aprīlis","Maijs","Jūnijs","Jūlijs","Augusts","Septembris","Oktobris","Novembris","Decembris"],monthsShort:["Jan","Feb","Mar","Apr","Mai","Jūn","Jūl","Aug","Sep","Okt","Nov","Dec"],today:"Šodien",weekStart:1}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.me={days:["Nedjelja","Ponedjeljak","Utorak","Srijeda","Četvrtak","Petak","Subota"],daysShort:["Ned","Pon","Uto","Sri","Čet","Pet","Sub"],daysMin:["Ne","Po","Ut","Sr","Če","Pe","Su"],months:["Januar","Februar","Mart","April","Maj","Jun","Jul","Avgust","Septembar","Oktobar","Novembar","Decembar"],monthsShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Avg","Sep","Okt","Nov","Dec"],today:"Danas",weekStart:1,clear:"Izbriši",format:"dd.mm.yyyy"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.mk={days:["Недела","Понеделник","Вторник","Среда","Четврток","Петок","Сабота"],daysShort:["Нед","Пон","Вто","Сре","Чет","Пет","Саб"],daysMin:["Не","По","Вт","Ср","Че","Пе","Са"],months:["Јануари","Февруари","Март","Април","Мај","Јуни","Јули","Август","Септември","Октомври","Ноември","Декември"],monthsShort:["Јан","Фев","Мар","Апр","Мај","Јун","Јул","Авг","Сеп","Окт","Ное","Дек"],today:"Денес",format:"dd.mm.yyyy"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.mn={days:["Ням","Даваа","Мягмар","Лхагва","Пүрэв","Баасан","Бямба"],daysShort:["Ням","Дав","Мяг","Лха","Пүр","Баа","Бям"],daysMin:["Ня","Да","Мя","Лх","Пү","Ба","Бя"],months:["Хулгана","Үхэр","Бар","Туулай","Луу","Могой","Морь","Хонь","Бич","Тахиа","Нохой","Гахай"],monthsShort:["Хул","Үхэ","Бар","Туу","Луу","Мог","Мор","Хон","Бич","Тах","Нох","Гах"],today:"Өнөөдөр",clear:"Тодорхой",format:"yyyy.mm.dd",weekStart:1}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.ms={days:["Ahad","Isnin","Selasa","Rabu","Khamis","Jumaat","Sabtu"],daysShort:["Aha","Isn","Sel","Rab","Kha","Jum","Sab"],daysMin:["Ah","Is","Se","Ra","Kh","Ju","Sa"],months:["Januari","Februari","Mac","April","Mei","Jun","Julai","Ogos","September","Oktober","November","Disember"],monthsShort:["Jan","Feb","Mar","Apr","Mei","Jun","Jul","Ogo","Sep","Okt","Nov","Dis"],today:"Hari Ini",clear:"Bersihkan"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.nb={days:["Søndag","Mandag","Tirsdag","Onsdag","Torsdag","Fredag","Lørdag"],daysShort:["Søn","Man","Tir","Ons","Tor","Fre","Lør"],daysMin:["Sø","Ma","Ti","On","To","Fr","Lø"],months:["Januar","Februar","Mars","April","Mai","Juni","Juli","August","September","Oktober","November","Desember"],monthsShort:["Jan","Feb","Mar","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Des"],today:"I Dag",format:"dd.mm.yyyy"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates["nl-BE"]={days:["zondag","maandag","dinsdag","woensdag","donderdag","vrijdag","zaterdag"],daysShort:["zo","ma","di","wo","do","vr","za"],daysMin:["zo","ma","di","wo","do","vr","za"],months:["januari","februari","maart","april","mei","juni","juli","augustus","september","oktober","november","december"],monthsShort:["jan","feb","mrt","apr","mei","jun","jul","aug","sep","okt","nov","dec"],today:"Vandaag",monthsTitle:"Maanden",clear:"Leegmaken",weekStart:1,format:"dd/mm/yyyy"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.nl={days:["zondag","maandag","dinsdag","woensdag","donderdag","vrijdag","zaterdag"],daysShort:["zo","ma","di","wo","do","vr","za"],daysMin:["zo","ma","di","wo","do","vr","za"],months:["januari","februari","maart","april","mei","juni","juli","augustus","september","oktober","november","december"],monthsShort:["jan","feb","mrt","apr","mei","jun","jul","aug","sep","okt","nov","dec"],today:"Vandaag",monthsTitle:"Maanden",clear:"Wissen",weekStart:1,format:"dd-mm-yyyy"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.no={days:["Søndag","Mandag","Tirsdag","Onsdag","Torsdag","Fredag","Lørdag"],daysShort:["Søn","Man","Tir","Ons","Tor","Fre","Lør"],daysMin:["Sø","Ma","Ti","On","To","Fr","Lø"],months:["Januar","Februar","Mars","April","Mai","Juni","Juli","August","September","Oktober","November","Desember"],monthsShort:["Jan","Feb","Mar","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Des"],today:"I dag",clear:"Nullstill",weekStart:1,format:"dd.mm.yyyy"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates.pl={days:["niedziela","poniedziałek","wtorek","środa","czwartek","piątek","sobota"],daysShort:["niedz.","pon.","wt.","śr.","czw.","piąt.","sob."],daysMin:["ndz.","pn.","wt.","śr.","czw.","pt.","sob."],months:["styczeń","luty","marzec","kwiecień","maj","czerwiec","lipiec","sierpień","wrzesień","październik","listopad","grudzień"],monthsShort:["sty.","lut.","mar.","kwi.","maj","cze.","lip.","sie.","wrz.","paź.","lis.","gru."],today:"dzisiaj",weekStart:1,clear:"wyczyść",format:"dd.mm.yyyy"}}(jQuery);
|
|
|
@ -1 +0,0 @@
|
||||||
!function(a){a.fn.datepicker.dates["pt-BR"]={days:["Domingo","Segunda","Terça","Quarta","Quinta","Sexta","Sábado"],daysShort:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],daysMin:["Do","Se","Te","Qu","Qu","Se","Sa"],months:["Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"],monthsShort:["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"],today:"Hoje",monthsTitle:"Meses",clear:"Limpar",format:"dd/mm/yyyy"}}(jQuery);
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue