diff --git a/backend/controllers/SiteController.php b/backend/controllers/SiteController.php
index 6d0a165..19fb2a2 100755
--- a/backend/controllers/SiteController.php
+++ b/backend/controllers/SiteController.php
@@ -6,6 +6,7 @@ use yii\web\Controller;
use yii\filters\VerbFilter;
use yii\filters\AccessControl;
use common\models\LoginForm;
+use yii\helpers\Url;
/**
* Site controller
@@ -18,20 +19,6 @@ class SiteController extends Controller
public function behaviors()
{
return [
- 'access' => [
- 'class' => AccessControl::className(),
- 'rules' => [
- [
- 'actions' => ['login', 'error'],
- 'allow' => true,
- ],
- [
- 'actions' => ['logout', 'index'],
- 'allow' => true,
- 'roles' => ['@'],
- ],
- ],
- ],
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
@@ -76,10 +63,14 @@ class SiteController extends Controller
$model = new LoginForm();
if ($model->load(Yii::$app->request->post()) && $model->login()) {
- return $this->goBack();
+ if (\Yii::$app->user->can('secure')) {
+ return $this->goBack();
+ } else {
+ Yii::$app->user->logout();
+ return $this->redirect(Url::to('/card/user-card'));
+ }
} else {
$model->password = '';
-
return $this->render('login', [
'model' => $model,
]);
diff --git a/backend/modules/accesses/controllers/AccessesController.php b/backend/modules/accesses/controllers/AccessesController.php
index 966c5c5..513f530 100755
--- a/backend/modules/accesses/controllers/AccessesController.php
+++ b/backend/modules/accesses/controllers/AccessesController.php
@@ -8,6 +8,7 @@ use common\models\UserCardAccesses;
use Yii;
use common\models\Accesses;
use app\modules\accesses\models\AccessesSearch;
+use yii\filters\AccessControl;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;
@@ -29,6 +30,15 @@ class AccessesController extends Controller
'delete' => ['POST'],
],
],
+ 'access' => [
+ 'class' => AccessControl::className(),
+ 'rules' => [
+ [
+ 'allow' => true,
+ 'roles' => ['admin'],
+ ],
+ ],
+ ],
];
}
diff --git a/backend/modules/balance/controllers/BalanceController.php b/backend/modules/balance/controllers/BalanceController.php
index ed760b5..56332c7 100755
--- a/backend/modules/balance/controllers/BalanceController.php
+++ b/backend/modules/balance/controllers/BalanceController.php
@@ -10,12 +10,39 @@ use common\models\FieldsValueNew;
use DateTime;
use Yii;
use yii\data\ActiveDataProvider;
+use yii\filters\AccessControl;
+use yii\filters\VerbFilter;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\db\Query;
class BalanceController extends Controller
{
+ /**
+ * {@inheritdoc}
+ */
+ public function behaviors()
+ {
+ return [
+ 'verbs' => [
+ 'class' => VerbFilter::className(),
+ 'actions' => [
+ 'delete' => ['POST'],
+ ],
+ ],
+ 'access' => [
+ 'class' => AccessControl::className(),
+ 'rules' => [
+ [
+ 'allow' => true,
+ 'roles' => ['admin'],
+ ],
+ ],
+ ],
+ ];
+ }
+
+
public function actionIndex()
{
$searchModel = new BalanceSearch();
diff --git a/backend/modules/calendar/controllers/CalendarController.php b/backend/modules/calendar/controllers/CalendarController.php
index 99dbbaf..86ae7e3 100644
--- a/backend/modules/calendar/controllers/CalendarController.php
+++ b/backend/modules/calendar/controllers/CalendarController.php
@@ -6,6 +6,8 @@ use backend\modules\card\models\UserCardSearch;
use common\classes\Debug;
use Yii;
use yii\data\ArrayDataProvider;
+use yii\filters\AccessControl;
+use yii\filters\VerbFilter;
use yii\web\Controller;
/**
@@ -13,6 +15,30 @@ use yii\web\Controller;
*/
class CalendarController extends Controller
{
+ /**
+ * {@inheritdoc}
+ */
+ public function behaviors()
+ {
+ return [
+ 'verbs' => [
+ 'class' => VerbFilter::className(),
+ 'actions' => [
+ 'delete' => ['POST'],
+ ],
+ ],
+ 'access' => [
+ 'class' => AccessControl::className(),
+ 'rules' => [
+ [
+ 'allow' => true,
+ 'roles' => ['admin'],
+ ],
+ ],
+ ],
+ ];
+ }
+
/**
* Renders the index view for the module
* @return string
diff --git a/backend/modules/card/controllers/UserCardController.php b/backend/modules/card/controllers/UserCardController.php
index a8b3909..e748257 100755
--- a/backend/modules/card/controllers/UserCardController.php
+++ b/backend/modules/card/controllers/UserCardController.php
@@ -14,6 +14,7 @@ use backend\modules\card\models\UserCard;
use backend\modules\card\models\UserCardSearch;
use yii\data\ActiveDataProvider;
use yii\db\Expression;
+use yii\filters\AccessControl;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;
@@ -35,6 +36,15 @@ class UserCardController extends Controller
'delete' => ['POST'],
],
],
+ 'access' => [
+ 'class' => AccessControl::className(),
+ 'rules' => [
+ [
+ 'allow' => true,
+ 'roles' => ['admin'],
+ ],
+ ],
+ ],
];
}
diff --git a/backend/modules/company/controllers/CompanyController.php b/backend/modules/company/controllers/CompanyController.php
index 2ee9ff7..62587e7 100755
--- a/backend/modules/company/controllers/CompanyController.php
+++ b/backend/modules/company/controllers/CompanyController.php
@@ -7,6 +7,7 @@ use Yii;
use backend\modules\company\models\Company;
use backend\modules\company\models\CompanySearch;
use yii\data\ActiveDataProvider;
+use yii\filters\AccessControl;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;
@@ -28,6 +29,15 @@ class CompanyController extends Controller
'delete' => ['POST'],
],
],
+ 'access' => [
+ 'class' => AccessControl::className(),
+ 'rules' => [
+ [
+ 'allow' => true,
+ 'roles' => ['admin'],
+ ],
+ ],
+ ],
];
}
diff --git a/backend/modules/hh/controllers/HhController.php b/backend/modules/hh/controllers/HhController.php
index 971fd5e..e3d90fb 100755
--- a/backend/modules/hh/controllers/HhController.php
+++ b/backend/modules/hh/controllers/HhController.php
@@ -9,6 +9,7 @@ use Yii;
use backend\modules\hh\models\Hh;
use backend\modules\hh\models\HhSearch;
use yii\data\ActiveDataProvider;
+use yii\filters\AccessControl;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;
@@ -30,6 +31,15 @@ class HhController extends Controller
'delete' => ['POST'],
],
],
+ 'access' => [
+ 'class' => AccessControl::className(),
+ 'rules' => [
+ [
+ 'allow' => true,
+ 'roles' => ['admin'],
+ ],
+ ],
+ ],
];
}
diff --git a/backend/modules/hh/controllers/HhJobController.php b/backend/modules/hh/controllers/HhJobController.php
index be5cf6e..92d065d 100755
--- a/backend/modules/hh/controllers/HhJobController.php
+++ b/backend/modules/hh/controllers/HhJobController.php
@@ -6,6 +6,7 @@ use common\models\Hh;
use Yii;
use backend\modules\hh\models\HhJob;
use backend\modules\hh\models\HhJobSearch;
+use yii\filters\AccessControl;
use yii\helpers\ArrayHelper;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
@@ -28,6 +29,15 @@ class HhJobController extends Controller
'delete' => ['POST'],
],
],
+ 'access' => [
+ 'class' => AccessControl::className(),
+ 'rules' => [
+ [
+ 'allow' => true,
+ 'roles' => ['admin'],
+ ],
+ ],
+ ],
];
}
diff --git a/backend/modules/holiday/controllers/HolidayController.php b/backend/modules/holiday/controllers/HolidayController.php
index a2afc39..908a606 100755
--- a/backend/modules/holiday/controllers/HolidayController.php
+++ b/backend/modules/holiday/controllers/HolidayController.php
@@ -6,6 +6,7 @@ use backend\modules\holiday\models\Holiday;
use backend\modules\holiday\models\HolidaySearch;
use common\classes\Debug;
use Yii;
+use yii\filters\AccessControl;
use yii\filters\VerbFilter;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
@@ -24,6 +25,15 @@ class HolidayController extends Controller
'delete' => ['POST'],
],
],
+ 'access' => [
+ 'class' => AccessControl::className(),
+ 'rules' => [
+ [
+ 'allow' => true,
+ 'roles' => ['admin'],
+ ],
+ ],
+ ],
];
}
diff --git a/backend/modules/notes/controllers/NotesController.php b/backend/modules/notes/controllers/NotesController.php
index d42033e..74bff12 100755
--- a/backend/modules/notes/controllers/NotesController.php
+++ b/backend/modules/notes/controllers/NotesController.php
@@ -7,6 +7,8 @@ use Yii;
use backend\modules\notes\models\Note;
use common\models\FieldsValueNew;
use yii\data\ActiveDataProvider;
+use yii\filters\AccessControl;
+use yii\filters\VerbFilter;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
@@ -15,6 +17,30 @@ use yii\web\NotFoundHttpException;
*/
class NotesController extends Controller
{
+ /**
+ * {@inheritdoc}
+ */
+ public function behaviors()
+ {
+ return [
+ 'verbs' => [
+ 'class' => VerbFilter::className(),
+ 'actions' => [
+ 'delete' => ['POST'],
+ ],
+ ],
+ 'access' => [
+ 'class' => AccessControl::className(),
+ 'rules' => [
+ [
+ 'allow' => true,
+ 'roles' => ['admin'],
+ ],
+ ],
+ ],
+ ];
+ }
+
/**
* Renders the index view for the module
* @return string
diff --git a/backend/modules/project/controllers/ProjectController.php b/backend/modules/project/controllers/ProjectController.php
index 1db532d..1cdd5c7 100755
--- a/backend/modules/project/controllers/ProjectController.php
+++ b/backend/modules/project/controllers/ProjectController.php
@@ -11,6 +11,7 @@ use Yii;
use backend\modules\project\models\Project;
use backend\modules\project\models\ProjectSearch;
use yii\data\ActiveDataProvider;
+use yii\filters\AccessControl;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;
@@ -32,6 +33,15 @@ class ProjectController extends Controller
'delete' => ['POST'],
],
],
+ 'access' => [
+ 'class' => AccessControl::className(),
+ 'rules' => [
+ [
+ 'allow' => true,
+ 'roles' => ['admin'],
+ ],
+ ],
+ ],
];
}
diff --git a/backend/modules/settings/controllers/AdditionalFieldsController.php b/backend/modules/settings/controllers/AdditionalFieldsController.php
index aac3dce..526880e 100755
--- a/backend/modules/settings/controllers/AdditionalFieldsController.php
+++ b/backend/modules/settings/controllers/AdditionalFieldsController.php
@@ -7,6 +7,7 @@ use common\models\UseField;
use Yii;
use backend\modules\settings\models\AdditionalFields;
use backend\modules\settings\models\AdditionalFieldsSearch;
+use yii\filters\AccessControl;
use yii\helpers\ArrayHelper;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
@@ -29,6 +30,15 @@ class AdditionalFieldsController extends Controller
'delete' => ['POST'],
],
],
+ 'access' => [
+ 'class' => AccessControl::className(),
+ 'rules' => [
+ [
+ 'allow' => true,
+ 'roles' => ['admin'],
+ ],
+ ],
+ ],
];
}
diff --git a/backend/modules/settings/controllers/PositionController.php b/backend/modules/settings/controllers/PositionController.php
index 0582978..c1093f6 100755
--- a/backend/modules/settings/controllers/PositionController.php
+++ b/backend/modules/settings/controllers/PositionController.php
@@ -5,6 +5,7 @@ namespace backend\modules\settings\controllers;
use Yii;
use backend\modules\settings\models\Position;
use backend\modules\settings\models\PositionSearch;
+use yii\filters\AccessControl;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;
@@ -26,6 +27,15 @@ class PositionController extends Controller
'delete' => ['POST'],
],
],
+ 'access' => [
+ 'class' => AccessControl::className(),
+ 'rules' => [
+ [
+ 'allow' => true,
+ 'roles' => ['admin'],
+ ],
+ ],
+ ],
];
}
diff --git a/backend/modules/settings/controllers/SkillController.php b/backend/modules/settings/controllers/SkillController.php
index 5bced31..3786a54 100755
--- a/backend/modules/settings/controllers/SkillController.php
+++ b/backend/modules/settings/controllers/SkillController.php
@@ -5,6 +5,7 @@ namespace backend\modules\settings\controllers;
use Yii;
use backend\modules\settings\models\Skill;
use backend\modules\settings\models\SkillSearch;
+use yii\filters\AccessControl;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;
@@ -26,6 +27,15 @@ class SkillController extends Controller
'delete' => ['POST'],
],
],
+ 'access' => [
+ 'class' => AccessControl::className(),
+ 'rules' => [
+ [
+ 'allow' => true,
+ 'roles' => ['admin'],
+ ],
+ ],
+ ],
];
}
diff --git a/backend/modules/settings/controllers/StatusController.php b/backend/modules/settings/controllers/StatusController.php
index f2da4c4..f184091 100755
--- a/backend/modules/settings/controllers/StatusController.php
+++ b/backend/modules/settings/controllers/StatusController.php
@@ -6,6 +6,7 @@ use common\models\UseStatus;
use Yii;
use backend\modules\settings\models\Status;
use backend\modules\settings\models\StatusSearch;
+use yii\filters\AccessControl;
use yii\helpers\ArrayHelper;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
@@ -28,6 +29,15 @@ class StatusController extends Controller
'delete' => ['POST'],
],
],
+ 'access' => [
+ 'class' => AccessControl::className(),
+ 'rules' => [
+ [
+ 'allow' => true,
+ 'roles' => ['admin'],
+ ],
+ ],
+ ],
];
}
diff --git a/common/config/main.php b/common/config/main.php
index 88ac995..0cf53ca 100755
--- a/common/config/main.php
+++ b/common/config/main.php
@@ -9,6 +9,12 @@ return [
'cache' => [
'class' => 'yii\caching\FileCache',
],
+ 'authManager' => [
+ 'class' => 'yii\rbac\DbManager',
+// 'itemFile' => '@common/components/rbac/items.php',
+// 'assignmentFile' => '@common/components/rbac/assignments.php',
+// 'ruleFile' => '@common/components/rbac/rules.php'
+ ],
],
'controllerMap' => [
'elfinder' => [
diff --git a/common/models/AuthAssignment.php b/common/models/AuthAssignment.php
new file mode 100644
index 0000000..2f61253
--- /dev/null
+++ b/common/models/AuthAssignment.php
@@ -0,0 +1,59 @@
+ 64],
+ [['item_name', 'user_id'], 'unique', 'targetAttribute' => ['item_name', 'user_id']],
+ [['item_name'], 'exist', 'skipOnError' => true, 'targetClass' => AuthItem::className(), 'targetAttribute' => ['item_name' => 'name']],
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'item_name' => 'Item Name',
+ 'user_id' => 'User ID',
+ 'created_at' => 'Created At',
+ ];
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getItemName()
+ {
+ return $this->hasOne(AuthItem::className(), ['name' => 'item_name']);
+ }
+}
diff --git a/common/models/UserCard.php b/common/models/UserCard.php
index d2f3555..abc862c 100755
--- a/common/models/UserCard.php
+++ b/common/models/UserCard.php
@@ -6,6 +6,7 @@ use common\classes\Debug;
use Yii;
use yii\behaviors\TimestampBehavior;
use yii\db\Expression;
+use yii\filters\AccessControl;
use yii\helpers\ArrayHelper;
/**
diff --git a/composer.json b/composer.json
index be9888b..4cc4779 100755
--- a/composer.json
+++ b/composer.json
@@ -27,7 +27,7 @@
"nkovacs/yii2-datetimepicker": "*",
"mirocow/yii2-eav": "*",
"kartik-v/yii2-widget-fileinput": "^1.0",
- "2amigos/yii2-file-upload-widget": "~1.0"
+ "2amigos/yii2-file-upload-widget": "~1.0",
},
"require-dev": {
"yiisoft/yii2-debug": "~2.0.0",
diff --git a/console/config/main.php b/console/config/main.php
index a67be5c..16d8dcf 100755
--- a/console/config/main.php
+++ b/console/config/main.php
@@ -22,6 +22,12 @@ return [
],
],
'components' => [
+ 'user' => [
+ 'identityClass' => 'common\models\User',
+ 'class' => 'yii\web\User',
+ 'enableSession' => false,
+ 'enableAutoLogin' => false,
+ ],
'log' => [
'targets' => [
[
diff --git a/console/controllers/RbacController.php b/console/controllers/RbacController.php
new file mode 100644
index 0000000..3e0fe75
--- /dev/null
+++ b/console/controllers/RbacController.php
@@ -0,0 +1,36 @@
+authManager;
+
+ $secure = $auth->createPermission('secure');
+ $secure->description = 'Admin panel';
+ $auth->add($secure);
+
+ $front = $auth->createPermission('front');
+ $front->description = 'Frontend';
+ $auth->add($front);
+
+ $user = $auth->createRole('user');
+ $auth->add($user);
+ $auth->addChild($user, $front);
+
+ $admin = $auth->createRole('admin');
+ $auth->add($admin);
+ $auth->addChild($admin, $secure);
+ $auth->addChild($admin, $user);
+
+ $auth->assign($user, 2);
+ $auth->assign($admin, 1);
+ }
+}
\ No newline at end of file
diff --git a/frontend/models/SignupForm.php b/frontend/models/SignupForm.php
index a66a875..5eec1cc 100755
--- a/frontend/models/SignupForm.php
+++ b/frontend/models/SignupForm.php
@@ -1,6 +1,8 @@
email = $this->email;
$user->setPassword($this->password);
$user->generateAuthKey();
+ $user->save();
+
+ $auth = Yii::$app->authManager;
+ $authorRole = $auth->getRole('user');
+ $auth->assign($authorRole, $user->id);
return $user->save() ? $user : null;
}
diff --git a/node_modules/sticky-table-headers/composer.json b/node_modules/sticky-table-headers/composer.json
new file mode 100644
index 0000000..390ac5a
--- /dev/null
+++ b/node_modules/sticky-table-headers/composer.json
@@ -0,0 +1,20 @@
+{
+ "name": "jmosbech/sticky-table-headers",
+ "description": "jQuery sticky table headers plugin",
+ "keywords": [
+ "jquery",
+ "sticky",
+ "table",
+ "headers"
+ ],
+ "homepage": "https://github.com/jmosbech/StickyTableHeaders",
+ "authors": [
+ {
+ "name": "Jonas Mosbech"
+ }
+ ],
+ "support": {
+ "issues": "https://github.com/jmosbech/StickyTableHeaders/issues"
+ },
+ "license": "MIT"
+}
diff --git a/node_modules/sticky-table-headers/js/jquery.stickytableheaders.js b/node_modules/sticky-table-headers/js/jquery.stickytableheaders.js
new file mode 100644
index 0000000..4d742f1
--- /dev/null
+++ b/node_modules/sticky-table-headers/js/jquery.stickytableheaders.js
@@ -0,0 +1,325 @@
+/*! Copyright (c) Jonas Mosbech - https://github.com/jmosbech/StickyTableHeaders
+ MIT license info: https://github.com/jmosbech/StickyTableHeaders/blob/master/license.txt */
+
+;(function ($, window, undefined) {
+ 'use strict';
+
+ var name = 'stickyTableHeaders',
+ id = 0,
+ defaults = {
+ fixedOffset: 0,
+ leftOffset: 0,
+ marginTop: 0,
+ objDocument: document,
+ objHead: 'head',
+ objWindow: window,
+ scrollableArea: window,
+ cacheHeaderHeight: false,
+ zIndex: 3
+ };
+
+ function Plugin (el, options) {
+ // To avoid scope issues, use 'base' instead of 'this'
+ // to reference this class from internal events and functions.
+ var base = this;
+
+ // Access to jQuery and DOM versions of element
+ base.$el = $(el);
+ base.el = el;
+ base.id = id++;
+
+ // Listen for destroyed, call teardown
+ base.$el.bind('destroyed',
+ $.proxy(base.teardown, base));
+
+ // Cache DOM refs for performance reasons
+ base.$clonedHeader = null;
+ base.$originalHeader = null;
+
+ // Cache header height for performance reasons
+ base.cachedHeaderHeight = null;
+
+ // Keep track of state
+ base.isSticky = false;
+ base.hasBeenSticky = false;
+ base.leftOffset = null;
+ base.topOffset = null;
+
+ base.init = function () {
+ base.setOptions(options);
+
+ base.$el.each(function () {
+ var $this = $(this);
+
+ // remove padding on
to fix issue #7
+ $this.css('padding', 0);
+
+ base.$originalHeader = $('thead:first', this);
+ base.$clonedHeader = base.$originalHeader.clone();
+ $this.trigger('clonedHeader.' + name, [base.$clonedHeader]);
+
+ base.$clonedHeader.addClass('tableFloatingHeader');
+ base.$clonedHeader.css({display: 'none', opacity: 0.0});
+
+ base.$originalHeader.addClass('tableFloatingHeaderOriginal');
+
+ base.$originalHeader.after(base.$clonedHeader);
+
+ base.$printStyle = $('');
+ base.$head.append(base.$printStyle);
+ });
+
+ base.$clonedHeader.find("input, select").attr("disabled", true);
+
+ base.updateWidth();
+ base.toggleHeaders();
+ base.bind();
+ };
+
+ base.destroy = function (){
+ base.$el.unbind('destroyed', base.teardown);
+ base.teardown();
+ };
+
+ base.teardown = function(){
+ if (base.isSticky) {
+ base.$originalHeader.css('position', 'static');
+ }
+ $.removeData(base.el, 'plugin_' + name);
+ base.unbind();
+
+ base.$clonedHeader.remove();
+ base.$originalHeader.removeClass('tableFloatingHeaderOriginal');
+ base.$originalHeader.css('visibility', 'visible');
+ base.$printStyle.remove();
+
+ base.el = null;
+ base.$el = null;
+ };
+
+ base.bind = function(){
+ base.$scrollableArea.on('scroll.' + name, base.toggleHeaders);
+ if (!base.isWindowScrolling) {
+ base.$window.on('scroll.' + name + base.id, base.setPositionValues);
+ base.$window.on('resize.' + name + base.id, base.toggleHeaders);
+ }
+ base.$scrollableArea.on('resize.' + name, base.toggleHeaders);
+ base.$scrollableArea.on('resize.' + name, base.updateWidth);
+ };
+
+ base.unbind = function(){
+ // unbind window events by specifying handle so we don't remove too much
+ base.$scrollableArea.off('.' + name, base.toggleHeaders);
+ if (!base.isWindowScrolling) {
+ base.$window.off('.' + name + base.id, base.setPositionValues);
+ base.$window.off('.' + name + base.id, base.toggleHeaders);
+ }
+ base.$scrollableArea.off('.' + name, base.updateWidth);
+ };
+
+ // We debounce the functions bound to the scroll and resize events
+ base.debounce = function (fn, delay) {
+ var timer = null;
+ return function () {
+ var context = this, args = arguments;
+ clearTimeout(timer);
+ timer = setTimeout(function () {
+ fn.apply(context, args);
+ }, delay);
+ };
+ };
+
+ base.toggleHeaders = base.debounce(function () {
+ if (base.$el) {
+ base.$el.each(function () {
+ var $this = $(this),
+ newLeft,
+ newTopOffset = base.isWindowScrolling ? (
+ isNaN(base.options.fixedOffset) ?
+ base.options.fixedOffset.outerHeight() :
+ base.options.fixedOffset
+ ) :
+ base.$scrollableArea.offset().top + (!isNaN(base.options.fixedOffset) ? base.options.fixedOffset : 0),
+ offset = $this.offset(),
+
+ scrollTop = base.$scrollableArea.scrollTop() + newTopOffset,
+ scrollLeft = base.$scrollableArea.scrollLeft(),
+
+ headerHeight,
+
+ scrolledPastTop = base.isWindowScrolling ?
+ scrollTop > offset.top :
+ newTopOffset > offset.top,
+ notScrolledPastBottom;
+
+ if (scrolledPastTop) {
+ headerHeight = base.options.cacheHeaderHeight ? base.cachedHeaderHeight : base.$clonedHeader.height();
+ notScrolledPastBottom = (base.isWindowScrolling ? scrollTop : 0) <
+ (offset.top + $this.height() - headerHeight - (base.isWindowScrolling ? 0 : newTopOffset));
+ }
+
+ if (scrolledPastTop && notScrolledPastBottom) {
+ newLeft = offset.left - scrollLeft + base.options.leftOffset;
+ base.$originalHeader.css({
+ 'position': 'fixed',
+ 'margin-top': base.options.marginTop,
+ 'top': 0,
+ 'left': newLeft,
+ 'z-index': base.options.zIndex
+ });
+ base.leftOffset = newLeft;
+ base.topOffset = newTopOffset;
+ base.$clonedHeader.css('display', '');
+ if (!base.isSticky) {
+ base.isSticky = true;
+ // make sure the width is correct: the user might have resized the browser while in static mode
+ base.updateWidth();
+ $this.trigger('enabledStickiness.' + name);
+ }
+ base.setPositionValues();
+ } else if (base.isSticky) {
+ base.$originalHeader.css('position', 'static');
+ base.$clonedHeader.css('display', 'none');
+ base.isSticky = false;
+ base.resetWidth($('td,th', base.$clonedHeader), $('td,th', base.$originalHeader));
+ $this.trigger('disabledStickiness.' + name);
+ }
+ });
+ }
+ }, 0);
+
+ base.setPositionValues = base.debounce(function () {
+ var winScrollTop = base.$window.scrollTop(),
+ winScrollLeft = base.$window.scrollLeft();
+ if (!base.isSticky ||
+ winScrollTop < 0 || winScrollTop + base.$window.height() > base.$document.height() ||
+ winScrollLeft < 0 || winScrollLeft + base.$window.width() > base.$document.width()) {
+ return;
+ }
+ base.$originalHeader.css({
+ 'top': base.topOffset - (base.isWindowScrolling ? 0 : winScrollTop),
+ 'left': base.leftOffset - (base.isWindowScrolling ? 0 : winScrollLeft)
+ });
+ }, 0);
+
+ base.updateWidth = base.debounce(function () {
+ if (!base.isSticky) {
+ return;
+ }
+ // Copy cell widths from clone
+ if (!base.$originalHeaderCells) {
+ base.$originalHeaderCells = $('th,td', base.$originalHeader);
+ }
+ if (!base.$clonedHeaderCells) {
+ base.$clonedHeaderCells = $('th,td', base.$clonedHeader);
+ }
+ var cellWidths = base.getWidth(base.$clonedHeaderCells);
+ base.setWidth(cellWidths, base.$clonedHeaderCells, base.$originalHeaderCells);
+
+ // Copy row width from whole table
+ base.$originalHeader.css('width', base.$clonedHeader.width());
+
+ // If we're caching the height, we need to update the cached value when the width changes
+ if (base.options.cacheHeaderHeight) {
+ base.cachedHeaderHeight = base.$clonedHeader.height();
+ }
+ }, 0);
+
+ base.getWidth = function ($clonedHeaders) {
+ var widths = [];
+ $clonedHeaders.each(function (index) {
+ var width, $this = $(this);
+
+ if ($this.css('box-sizing') === 'border-box') {
+ var boundingClientRect = $this[0].getBoundingClientRect();
+ if(boundingClientRect.width) {
+ width = boundingClientRect.width; // #39: border-box bug
+ } else {
+ width = boundingClientRect.right - boundingClientRect.left; // ie8 bug: getBoundingClientRect() does not have a width property
+ }
+ } else {
+ var $origTh = $('th', base.$originalHeader);
+ if ($origTh.css('border-collapse') === 'collapse') {
+ if (window.getComputedStyle) {
+ width = parseFloat(window.getComputedStyle(this, null).width);
+ } else {
+ // ie8 only
+ var leftPadding = parseFloat($this.css('padding-left'));
+ var rightPadding = parseFloat($this.css('padding-right'));
+ // Needs more investigation - this is assuming constant border around this cell and it's neighbours.
+ var border = parseFloat($this.css('border-width'));
+ width = $this.outerWidth() - leftPadding - rightPadding - border;
+ }
+ } else {
+ width = $this.width();
+ }
+ }
+
+ widths[index] = width;
+ });
+ return widths;
+ };
+
+ base.setWidth = function (widths, $clonedHeaders, $origHeaders) {
+ $clonedHeaders.each(function (index) {
+ var width = widths[index];
+ $origHeaders.eq(index).css({
+ 'min-width': width,
+ 'max-width': width
+ });
+ });
+ };
+
+ base.resetWidth = function ($clonedHeaders, $origHeaders) {
+ $clonedHeaders.each(function (index) {
+ var $this = $(this);
+ $origHeaders.eq(index).css({
+ 'min-width': $this.css('min-width'),
+ 'max-width': $this.css('max-width')
+ });
+ });
+ };
+
+ base.setOptions = function (options) {
+ base.options = $.extend({}, defaults, options);
+ base.$window = $(base.options.objWindow);
+ base.$head = $(base.options.objHead);
+ base.$document = $(base.options.objDocument);
+ base.$scrollableArea = $(base.options.scrollableArea);
+ base.isWindowScrolling = base.$scrollableArea[0] === base.$window[0];
+ };
+
+ base.updateOptions = function (options) {
+ base.setOptions(options);
+ // scrollableArea might have changed
+ base.unbind();
+ base.bind();
+ base.updateWidth();
+ base.toggleHeaders();
+ };
+
+ // Run initializer
+ base.init();
+ }
+
+ // A plugin wrapper around the constructor,
+ // preventing against multiple instantiations
+ $.fn[name] = function ( options ) {
+ return this.each(function () {
+ var instance = $.data(this, 'plugin_' + name);
+ if (instance) {
+ if (typeof options === 'string') {
+ instance[options].apply(instance);
+ } else {
+ instance.updateOptions(options);
+ }
+ } else if(options !== 'destroy') {
+ $.data(this, 'plugin_' + name, new Plugin( this, options ));
+ }
+ });
+ };
+
+})(jQuery, window);
diff --git a/node_modules/sticky-table-headers/js/jquery.stickytableheaders.min.js b/node_modules/sticky-table-headers/js/jquery.stickytableheaders.min.js
new file mode 100644
index 0000000..6579c01
--- /dev/null
+++ b/node_modules/sticky-table-headers/js/jquery.stickytableheaders.min.js
@@ -0,0 +1,6 @@
+/*!
+ * StickyTableHeaders 0.1.24 (2018-01-14 23:29)
+ * MIT licensed
+ * Copyright (C) Jonas Mosbech - https://github.com/jmosbech/StickyTableHeaders
+ */
+!function(e,i,t){"use strict";var o="stickyTableHeaders",n=0,d={fixedOffset:0,leftOffset:0,marginTop:0,objDocument:document,objHead:"head",objWindow:i,scrollableArea:i,cacheHeaderHeight:!1,zIndex:3};e.fn[o]=function(t){return this.each(function(){var l=e.data(this,"plugin_"+o);l?"string"==typeof t?l[t].apply(l):l.updateOptions(t):"destroy"!==t&&e.data(this,"plugin_"+o,new function(t,l){var a=this;a.$el=e(t),a.el=t,a.id=n++,a.$el.bind("destroyed",e.proxy(a.teardown,a)),a.$clonedHeader=null,a.$originalHeader=null,a.cachedHeaderHeight=null,a.isSticky=!1,a.hasBeenSticky=!1,a.leftOffset=null,a.topOffset=null,a.init=function(){a.setOptions(l),a.$el.each(function(){var i=e(this);i.css("padding",0),a.$originalHeader=e("thead:first",this),a.$clonedHeader=a.$originalHeader.clone(),i.trigger("clonedHeader."+o,[a.$clonedHeader]),a.$clonedHeader.addClass("tableFloatingHeader"),a.$clonedHeader.css({display:"none",opacity:0}),a.$originalHeader.addClass("tableFloatingHeaderOriginal"),a.$originalHeader.after(a.$clonedHeader),a.$printStyle=e(''),a.$head.append(a.$printStyle)}),a.$clonedHeader.find("input, select").attr("disabled",!0),a.updateWidth(),a.toggleHeaders(),a.bind()},a.destroy=function(){a.$el.unbind("destroyed",a.teardown),a.teardown()},a.teardown=function(){a.isSticky&&a.$originalHeader.css("position","static"),e.removeData(a.el,"plugin_"+o),a.unbind(),a.$clonedHeader.remove(),a.$originalHeader.removeClass("tableFloatingHeaderOriginal"),a.$originalHeader.css("visibility","visible"),a.$printStyle.remove(),a.el=null,a.$el=null},a.bind=function(){a.$scrollableArea.on("scroll."+o,a.toggleHeaders),a.isWindowScrolling||(a.$window.on("scroll."+o+a.id,a.setPositionValues),a.$window.on("resize."+o+a.id,a.toggleHeaders)),a.$scrollableArea.on("resize."+o,a.toggleHeaders),a.$scrollableArea.on("resize."+o,a.updateWidth)},a.unbind=function(){a.$scrollableArea.off("."+o,a.toggleHeaders),a.isWindowScrolling||(a.$window.off("."+o+a.id,a.setPositionValues),a.$window.off("."+o+a.id,a.toggleHeaders)),a.$scrollableArea.off("."+o,a.updateWidth)},a.debounce=function(e,i){var t=null;return function(){var o=this,n=arguments;clearTimeout(t),t=setTimeout(function(){e.apply(o,n)},i)}},a.toggleHeaders=a.debounce(function(){a.$el&&a.$el.each(function(){var i,t,n,d=e(this),l=a.isWindowScrolling?isNaN(a.options.fixedOffset)?a.options.fixedOffset.outerHeight():a.options.fixedOffset:a.$scrollableArea.offset().top+(isNaN(a.options.fixedOffset)?0:a.options.fixedOffset),s=d.offset(),r=a.$scrollableArea.scrollTop()+l,c=a.$scrollableArea.scrollLeft(),f=a.isWindowScrolling?r>s.top:l>s.top;f&&(t=a.options.cacheHeaderHeight?a.cachedHeaderHeight:a.$clonedHeader.height(),n=(a.isWindowScrolling?r:0)a.$document.height()||i<0||i+a.$window.width()>a.$document.width()||a.$originalHeader.css({top:a.topOffset-(a.isWindowScrolling?0:e),left:a.leftOffset-(a.isWindowScrolling?0:i)})},0),a.updateWidth=a.debounce(function(){if(a.isSticky){a.$originalHeaderCells||(a.$originalHeaderCells=e("th,td",a.$originalHeader)),a.$clonedHeaderCells||(a.$clonedHeaderCells=e("th,td",a.$clonedHeader));var i=a.getWidth(a.$clonedHeaderCells);a.setWidth(i,a.$clonedHeaderCells,a.$originalHeaderCells),a.$originalHeader.css("width",a.$clonedHeader.width()),a.options.cacheHeaderHeight&&(a.cachedHeaderHeight=a.$clonedHeader.height())}},0),a.getWidth=function(t){var o=[];return t.each(function(t){var n,d=e(this);if("border-box"===d.css("box-sizing")){var l=d[0].getBoundingClientRect();n=l.width?l.width:l.right-l.left}else if("collapse"===e("th",a.$originalHeader).css("border-collapse"))if(i.getComputedStyle)n=parseFloat(i.getComputedStyle(this,null).width);else{var s=parseFloat(d.css("padding-left")),r=parseFloat(d.css("padding-right")),c=parseFloat(d.css("border-width"));n=d.outerWidth()-s-r-c}else n=d.width();o[t]=n}),o},a.setWidth=function(e,i,t){i.each(function(i){var o=e[i];t.eq(i).css({"min-width":o,"max-width":o})})},a.resetWidth=function(i,t){i.each(function(i){var o=e(this);t.eq(i).css({"min-width":o.css("min-width"),"max-width":o.css("max-width")})})},a.setOptions=function(i){a.options=e.extend({},d,i),a.$window=e(a.options.objWindow),a.$head=e(a.options.objHead),a.$document=e(a.options.objDocument),a.$scrollableArea=e(a.options.scrollableArea),a.isWindowScrolling=a.$scrollableArea[0]===a.$window[0]},a.updateOptions=function(e){a.setOptions(e),a.unbind(),a.bind(),a.updateWidth(),a.toggleHeaders()},a.init()}(this,t))})}}(jQuery,window);
\ No newline at end of file
diff --git a/node_modules/sticky-table-headers/license.txt b/node_modules/sticky-table-headers/license.txt
new file mode 100644
index 0000000..a9da71c
--- /dev/null
+++ b/node_modules/sticky-table-headers/license.txt
@@ -0,0 +1,20 @@
+Copyright (c) 2011 Jonas Mosbech
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/node_modules/sticky-table-headers/package.json b/node_modules/sticky-table-headers/package.json
new file mode 100644
index 0000000..1c3e293
--- /dev/null
+++ b/node_modules/sticky-table-headers/package.json
@@ -0,0 +1,63 @@
+{
+ "_from": "sticky-table-headers",
+ "_id": "sticky-table-headers@0.1.24",
+ "_inBundle": false,
+ "_integrity": "sha1-euofB5HKOgKwhgzYhe11ehrL84E=",
+ "_location": "/sticky-table-headers",
+ "_phantomChildren": {},
+ "_requested": {
+ "type": "tag",
+ "registry": true,
+ "raw": "sticky-table-headers",
+ "name": "sticky-table-headers",
+ "escapedName": "sticky-table-headers",
+ "rawSpec": "",
+ "saveSpec": null,
+ "fetchSpec": "latest"
+ },
+ "_requiredBy": [
+ "#USER",
+ "/"
+ ],
+ "_resolved": "https://registry.npmjs.org/sticky-table-headers/-/sticky-table-headers-0.1.24.tgz",
+ "_shasum": "7aea1f0791ca3a02b0860cd885ed757a1acbf381",
+ "_spec": "sticky-table-headers",
+ "_where": "/var/www/domains/guild",
+ "author": {
+ "name": "Jonas Mosbech",
+ "email": "https://github.com/jmosbech"
+ },
+ "bugs": {
+ "url": "https://github.com/jmosbech/StickyTableHeaders/issues"
+ },
+ "bundleDependencies": false,
+ "deprecated": false,
+ "description": "jQuery sticky table headers plugin",
+ "devDependencies": {
+ "grunt": "~1.0.1",
+ "grunt-bower-version": "^0.1.1",
+ "grunt-contrib-jshint": "~1.1.0",
+ "grunt-contrib-uglify": "~3.3.0"
+ },
+ "homepage": "https://github.com/jmosbech/StickyTableHeaders#readme",
+ "keywords": [
+ "jquery",
+ "sticky",
+ "table",
+ "headers",
+ "jquery-plugin",
+ "ecosystem:jquery"
+ ],
+ "license": "MIT",
+ "main": "js/jquery.stickytableheaders.js",
+ "name": "sticky-table-headers",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/jmosbech/StickyTableHeaders.git"
+ },
+ "scripts": {
+ "build": "grunt"
+ },
+ "unpkg": "js/jquery.stickytableheaders.min.js",
+ "version": "0.1.24"
+}
diff --git a/node_modules/sticky-table-headers/readme.md b/node_modules/sticky-table-headers/readme.md
new file mode 100644
index 0000000..0a91908
--- /dev/null
+++ b/node_modules/sticky-table-headers/readme.md
@@ -0,0 +1,119 @@
+StickyTableHeaders
+==================
+So what's it good for? Well, let's say you want to display a long list of fairly uniform tabluar data, like [stock exchange listings](http://online.barrons.com/public/page/majormarket-nysecomposite-A.html) or [sport statistics](https://sports.yahoo.com/nfl/stats/weekly/?sortStatId=PASSING_YARDS&selectedTable=7) but you don't want your users to get lost in the data as they scroll down on the page.
+
+StickyTableHeaders to the rescue: By applying the StickyTableHeaders jQuery plugin to the table, the column headers will stick to the top of the viewport as you scroll down.
+
+Go ahead and [try out a demo](http://jsfiddle.net/jmosbech/stFcx/).
+
+The code is based on [this proof of concept](http://stackoverflow.com/questions/1030043/html-table-headers-always-visible-at-top-of-window-when-viewing-a-large-table/1041566#1041566).
+
+Installation
+------------
+The best way to install is using [npm](https://www.npmjs.com/):
+
+```bash
+npm install sticky-table-headers
+```
+
+or [Bower](http://bower.io/):
+
+```bash
+bower install StickyTableHeaders
+```
+
+or by loading it directly from the [unpkg CDN](https://unpkg.com/sticky-table-headers):
+
+```
+
+```
+
+Usage
+-----
+Initializing the plugin is pretty straight forward:
+
+```js
+$('table').stickyTableHeaders();
+```
+
+### Tear down
+To remove the plugin:
+
+```js
+$('table').stickyTableHeaders('destroy');
+```
+
+### Trigger an update manually
+```js
+$(window).trigger('resize.stickyTableHeaders');
+```
+
+### Options
+You can initialize the plugin with an options map to tweak the behavior. The following options are supported:
+
+#### `fixedOffset`
+A number or jQuery object specifying how much the sticky header should be offset from the top of the page:
+
+```js
+$('table').stickyTableHeaders({fixedOffset: $('#header')});
+```
+
+#### `scrollableArea`
+A DOM element or jQuery object. Allows you to overwrite which surrounding element is scrolling. Defaults to `window`. [Check this demo for an example](https://github.com/jmosbech/StickyTableHeaders/tree/master/demo/scrollable-div.html):
+
+```js
+$('table').stickyTableHeaders({scrollableArea: $('.scrollable-area')});
+```
+
+#### `cacheHeaderHeight`
+Performance tweak: When set to `true` the plugin will only recalculate the height of the header cells when the width of the table changes.
+
+Default value: `false`
+
+```js
+$('table').stickyTableHeaders({cacheHeaderHeight: true});
+```
+
+#### z-index
+The plugin uses z-index to make the thead overlay the body. You can override the z-index value by passing in a `zIndex` option:
+
+```js
+$('table').stickyTableHeaders({zIndex: 999});
+```
+
+### Reinitialize
+As described in [pull request #33](https://github.com/jmosbech/StickyTableHeaders/pull/33) responsive pages might need to reinitialize the plugin when the user resizes his browser. This is can be done by calling the plugin with the new options:
+
+```js
+$('table').stickyTableHeaders({fixedOffset: [new-offset]});
+```
+
+### Events
+The plugin triggers the following events on the targeted `` element:
+
+ - `clonedHeader.stickyTableHeaders`: When the header clone is created.
+ - `enabledStickiness.stickyTableHeaders`: When the sticky header is enabled.
+ - `disabledStickiness.stickyTableHeaders`: When the sticky header is disabled.
+
+Confused?
+---------
+
+If any of this is confusing, please check out the [/demo](https://github.com/jmosbech/StickyTableHeaders/tree/master/demo) folder. There are a couple of examples in there. E.g. you can see how to use it with Twitter Bootstrap.
+
+Known Issues
+------------
+- Internet Explorer: You need to set the padding of the ``s explicitly in the css in order to make the plugin work
+- Internet Explorer: Adding horizontal margin to the table causes the header to be misaligned when scrolling. (Issue #10)
+- Using the plugin together with [tablesorter](http://tablesorter.com/docs/) breaks in Internet Explorer 8
+
+
+Browser Support
+---------------
+The plugin has been verified to work in:
+
+- Chrome 35
+- Firefox 29
+- Internet Explorer 8-11
+- Safari 5
+
+NOTE: It does not work in Internet Explorer 7 (but it degrades nicely)
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..e4ee320
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,11 @@
+{
+ "requires": true,
+ "lockfileVersion": 1,
+ "dependencies": {
+ "sticky-table-headers": {
+ "version": "0.1.24",
+ "resolved": "https://registry.npmjs.org/sticky-table-headers/-/sticky-table-headers-0.1.24.tgz",
+ "integrity": "sha1-euofB5HKOgKwhgzYhe11ehrL84E="
+ }
+ }
+}
|