diff --git a/app/config/testing/cms.php b/app/config/testing/cms.php index caa73b460..ed586eb9a 100644 --- a/app/config/testing/cms.php +++ b/app/config/testing/cms.php @@ -2,6 +2,17 @@ return array( + /* + |-------------------------------------------------------------------------- + | Specifies the default CMS theme + |-------------------------------------------------------------------------- + | + | This parameter value can be overridden by the CMS back-end settings. + | + */ + + 'activeTheme' => 'test', + /* |-------------------------------------------------------------------------- | Plugins directory @@ -66,14 +77,16 @@ return array( /* |-------------------------------------------------------------------------- - | Specifies the default CMS theme + | Determines if a friendly error page should be used. |-------------------------------------------------------------------------- | - | This parameter value can be overridden by the CMS back-end settings. + | If this value is set to true, a friendly error page is used when an + | exception is encountered. You must create a CMS page with route "/error" + | to set the contents of this page. Otherwise the default error page is shown. | */ - 'activeTheme' => 'test', + 'customErrorPage' => true, /* |-------------------------------------------------------------------------- @@ -88,7 +101,7 @@ return array( */ 'enableAssetCache' => false, - + /* |-------------------------------------------------------------------------- | Disables Twig caching for unit tests diff --git a/modules/backend/ServiceProvider.php b/modules/backend/ServiceProvider.php index 9f3dd231d..2c6f0f0ea 100644 --- a/modules/backend/ServiceProvider.php +++ b/modules/backend/ServiceProvider.php @@ -104,6 +104,15 @@ class ServiceProvider extends ModuleServiceProvider 'context' => 'mysettings', 'keywords' => 'backend::lang.myaccount.menu_keywords', ], + 'access_logs' => [ + 'label' => 'backend::lang.access_log.menu_label', + 'description' => 'backend::lang.access_log.menu_description', + 'category' => 'Logs', + 'icon' => 'icon-lock', + 'url' => Backend::url('backend/accesslogs'), + 'permissions' => ['backend.access_admin_logs'], + 'order' => 800 + ], ]); }); diff --git a/modules/backend/assets/css/october.css b/modules/backend/assets/css/october.css index 062c9b5af..36616a5ec 100644 --- a/modules/backend/assets/css/october.css +++ b/modules/backend/assets/css/october.css @@ -9178,151 +9178,151 @@ table.table.data .list-tree a.list-expand-collapse { table.table.data tr.list-tree-level-1 a.list-expand-collapse { left: 30px; } -table.table.data tr.list-tree-level-1 td.list-data-column-1 { +table.table.data tr.list-tree-level-1 td.list-cell-index-1 { padding-left: 35px; } table.table.data tr.list-tree-level-2 a.list-expand-collapse { left: 40px; } -table.table.data tr.list-tree-level-2 td.list-data-column-1 { +table.table.data tr.list-tree-level-2 td.list-cell-index-1 { padding-left: 45px; } table.table.data tr.list-tree-level-3 a.list-expand-collapse { left: 50px; } -table.table.data tr.list-tree-level-3 td.list-data-column-1 { +table.table.data tr.list-tree-level-3 td.list-cell-index-1 { padding-left: 55px; } table.table.data tr.list-tree-level-4 a.list-expand-collapse { left: 60px; } -table.table.data tr.list-tree-level-4 td.list-data-column-1 { +table.table.data tr.list-tree-level-4 td.list-cell-index-1 { padding-left: 65px; } table.table.data tr.list-tree-level-5 a.list-expand-collapse { left: 70px; } -table.table.data tr.list-tree-level-5 td.list-data-column-1 { +table.table.data tr.list-tree-level-5 td.list-cell-index-1 { padding-left: 75px; } table.table.data tr.list-tree-level-6 a.list-expand-collapse { left: 80px; } -table.table.data tr.list-tree-level-6 td.list-data-column-1 { +table.table.data tr.list-tree-level-6 td.list-cell-index-1 { padding-left: 85px; } table.table.data tr.list-tree-level-7 a.list-expand-collapse { left: 90px; } -table.table.data tr.list-tree-level-7 td.list-data-column-1 { +table.table.data tr.list-tree-level-7 td.list-cell-index-1 { padding-left: 95px; } table.table.data tr.list-tree-level-8 a.list-expand-collapse { left: 100px; } -table.table.data tr.list-tree-level-8 td.list-data-column-1 { +table.table.data tr.list-tree-level-8 td.list-cell-index-1 { padding-left: 105px; } table.table.data tr.list-tree-level-9 a.list-expand-collapse { left: 110px; } -table.table.data tr.list-tree-level-9 td.list-data-column-1 { +table.table.data tr.list-tree-level-9 td.list-cell-index-1 { padding-left: 115px; } table.table.data tr.list-tree-level-10 a.list-expand-collapse { left: 120px; } -table.table.data tr.list-tree-level-10 td.list-data-column-1 { +table.table.data tr.list-tree-level-10 td.list-cell-index-1 { padding-left: 125px; } table.table.data tr.list-tree-level-11 a.list-expand-collapse { left: 130px; } -table.table.data tr.list-tree-level-11 td.list-data-column-1 { +table.table.data tr.list-tree-level-11 td.list-cell-index-1 { padding-left: 135px; } table.table.data tr.list-tree-level-12 a.list-expand-collapse { left: 140px; } -table.table.data tr.list-tree-level-12 td.list-data-column-1 { +table.table.data tr.list-tree-level-12 td.list-cell-index-1 { padding-left: 145px; } table.table.data tr.list-tree-level-13 a.list-expand-collapse { left: 150px; } -table.table.data tr.list-tree-level-13 td.list-data-column-1 { +table.table.data tr.list-tree-level-13 td.list-cell-index-1 { padding-left: 155px; } table.table.data tr.list-tree-level-14 a.list-expand-collapse { left: 160px; } -table.table.data tr.list-tree-level-14 td.list-data-column-1 { +table.table.data tr.list-tree-level-14 td.list-cell-index-1 { padding-left: 165px; } table.table.data tr.list-tree-level-15 a.list-expand-collapse { left: 170px; } -table.table.data tr.list-tree-level-15 td.list-data-column-1 { +table.table.data tr.list-tree-level-15 td.list-cell-index-1 { padding-left: 175px; } table.table.data tr.list-tree-level-16 a.list-expand-collapse { left: 180px; } -table.table.data tr.list-tree-level-16 td.list-data-column-1 { +table.table.data tr.list-tree-level-16 td.list-cell-index-1 { padding-left: 185px; } table.table.data tr.list-tree-level-17 a.list-expand-collapse { left: 190px; } -table.table.data tr.list-tree-level-17 td.list-data-column-1 { +table.table.data tr.list-tree-level-17 td.list-cell-index-1 { padding-left: 195px; } table.table.data tr.list-tree-level-18 a.list-expand-collapse { left: 200px; } -table.table.data tr.list-tree-level-18 td.list-data-column-1 { +table.table.data tr.list-tree-level-18 td.list-cell-index-1 { padding-left: 205px; } table.table.data tr.list-tree-level-19 a.list-expand-collapse { left: 210px; } -table.table.data tr.list-tree-level-19 td.list-data-column-1 { +table.table.data tr.list-tree-level-19 td.list-cell-index-1 { padding-left: 215px; } table.table.data tr.list-tree-level-20 a.list-expand-collapse { left: 220px; } -table.table.data tr.list-tree-level-20 td.list-data-column-1 { +table.table.data tr.list-tree-level-20 td.list-cell-index-1 { padding-left: 225px; } table.table.data tr.list-tree-level-21 a.list-expand-collapse { left: 230px; } -table.table.data tr.list-tree-level-21 td.list-data-column-1 { +table.table.data tr.list-tree-level-21 td.list-cell-index-1 { padding-left: 235px; } table.table.data tr.list-tree-level-22 a.list-expand-collapse { left: 240px; } -table.table.data tr.list-tree-level-22 td.list-data-column-1 { +table.table.data tr.list-tree-level-22 td.list-cell-index-1 { padding-left: 245px; } table.table.data tr.list-tree-level-23 a.list-expand-collapse { left: 250px; } -table.table.data tr.list-tree-level-23 td.list-data-column-1 { +table.table.data tr.list-tree-level-23 td.list-cell-index-1 { padding-left: 255px; } table.table.data tr.list-tree-level-24 a.list-expand-collapse { left: 260px; } -table.table.data tr.list-tree-level-24 td.list-data-column-1 { +table.table.data tr.list-tree-level-24 td.list-cell-index-1 { padding-left: 265px; } table.table.data tr.list-tree-level-25 a.list-expand-collapse { left: 270px; } -table.table.data tr.list-tree-level-25 td.list-data-column-1 { +table.table.data tr.list-tree-level-25 td.list-cell-index-1 { padding-left: 275px; } .list-preview { @@ -9499,6 +9499,13 @@ table.table.data tr.list-tree-level-25 td.list-data-column-1 { .control-simplelist ul { padding-left: 15px; } +.control-simplelist.form-control ul { + margin-bottom: 0; +} +.control-simplelist.form-control li { + padding-top: 5px; + padding-bottom: 5px; +} .control-simplelist.with-icons ul, .control-simplelist.with-checkboxes ul, .control-simplelist.is-selectable ul { diff --git a/modules/backend/assets/less/controls/lists.less b/modules/backend/assets/less/controls/lists.less index 4648b493b..5c0e9db14 100644 --- a/modules/backend/assets/less/controls/lists.less +++ b/modules/backend/assets/less/controls/lists.less @@ -273,7 +273,7 @@ table.table.data { .makeTreeLevel(@count) when (@count < 26) { tr.list-tree-level-@{count} { a.list-expand-collapse { left: 20px + (10 * @count); } - td.list-data-column-1 { padding-left: 25px + (10 * @count); } + td.list-cell-index-1 { padding-left: 25px + (10 * @count); } } .makeTreeLevel(@count + 1); } diff --git a/modules/backend/assets/less/controls/simplelist.less b/modules/backend/assets/less/controls/simplelist.less index f2b8ecf3e..b8cb4f136 100644 --- a/modules/backend/assets/less/controls/simplelist.less +++ b/modules/backend/assets/less/controls/simplelist.less @@ -33,6 +33,14 @@ ul { padding-left: 15px; } + &.form-control { + ul { margin-bottom: 0; } + li { + padding-top: 5px; + padding-bottom: 5px; + } + } + &.with-icons, &.with-checkboxes, &.is-selectable { ul { list-style-type: none; diff --git a/modules/backend/behaviors/FormController.php b/modules/backend/behaviors/FormController.php index 589d626fb..02cc43ba1 100644 --- a/modules/backend/behaviors/FormController.php +++ b/modules/backend/behaviors/FormController.php @@ -25,7 +25,7 @@ class FormController extends ControllerBehavior /** * @var Backend\Classes\WidgetBase Reference to the widget object. */ - private $formWidget; + protected $formWidget; /** * {@inheritDoc} @@ -47,7 +47,7 @@ class FormController extends ControllerBehavior /** * @var array List of prepared models that require saving. */ - private $modelsToSave = []; + protected $modelsToSave = []; /** * Behavior constructor @@ -349,7 +349,7 @@ class FormController extends ControllerBehavior * @param array $extras Any extra params to include in the language string variables * @return string The translated string. */ - private function getLang($name, $default = null, $extras = []) + protected function getLang($name, $default = null, $extras = []) { $name = $this->getConfig($name, $default); $vars = [ @@ -567,7 +567,7 @@ class FormController extends ControllerBehavior // Internals // - private function prepareModelsToSave($model, $saveData) + protected function prepareModelsToSave($model, $saveData) { $this->modelsToSave = []; $this->setModelAttributes($model, $saveData); @@ -580,7 +580,7 @@ class FormController extends ControllerBehavior * @param Model $model Model to save to * @return array The collection of models to save. */ - private function setModelAttributes($model, $saveData) + protected function setModelAttributes($model, $saveData) { $this->modelsToSave[] = $model; diff --git a/modules/backend/behaviors/ListController.php b/modules/backend/behaviors/ListController.php index 77896c924..444a0f2fe 100644 --- a/modules/backend/behaviors/ListController.php +++ b/modules/backend/behaviors/ListController.php @@ -19,22 +19,22 @@ class ListController extends ControllerBehavior /** * @var array List definitions, keys for alias and value for configuration. */ - private $listDefinitions; + protected $listDefinitions; /** * @var string The primary list alias to use. Default: list */ - private $primaryDefinition; + protected $primaryDefinition; /** * @var Backend\Classes\WidgetBase Reference to the list widget object. */ - private $listWidgets = []; + protected $listWidgets = []; /** * @var WidgetBase Reference to the toolbar widget objects. */ - private $toolbarWidgets = []; + protected $toolbarWidgets = []; /** * {@inheritDoc} diff --git a/modules/backend/behaviors/RelationController.php b/modules/backend/behaviors/RelationController.php index ec7a7e062..0f479d814 100644 --- a/modules/backend/behaviors/RelationController.php +++ b/modules/backend/behaviors/RelationController.php @@ -26,22 +26,22 @@ class RelationController extends ControllerBehavior /** * @var Backend\Classes\WidgetBase Reference to the toolbar widget object. */ - private $toolbarWidget; + protected $toolbarWidget; /** * @var Backend\Classes\WidgetBase Reference to the widget used for viewing (list or form). */ - private $viewWidget; + protected $viewWidget; /** * @var Backend\Classes\WidgetBase Reference to the widget used for relation management. */ - private $manageWidget; + protected $manageWidget; /** * @var Backend\Classes\WidgetBase Reference to widget for relations with pivot data. */ - private $pivotWidget; + protected $pivotWidget; /** * {@inheritDoc} @@ -61,12 +61,12 @@ class RelationController extends ControllerBehavior /** * @var array Original configuration values */ - private $originalConfig; + protected $originalConfig; /** * @var bool Has the behavior been initialized. */ - private $initialized = false; + protected $initialized = false; /** * @var string Relationship type @@ -154,6 +154,9 @@ class RelationController extends ControllerBehavior */ public function initRelation($model, $field = null) { + if ($field == null) + $field = post(self::PARAM_FIELD); + $this->config = $this->originalConfig; $this->model = $model; $this->field = $field; @@ -323,7 +326,7 @@ class RelationController extends ControllerBehavior * @param string $field The relationship field. * @return string The active field name. */ - private function validateField($field = null) + protected function validateField($field = null) { $field = $field ?: post(self::PARAM_FIELD); @@ -403,7 +406,7 @@ class RelationController extends ControllerBehavior /** * Returns the existing record IDs for the relation. */ - private function findExistingRelationIds($checkIds = null) + protected function findExistingRelationIds($checkIds = null) { $results = $this->relationObject ->getBaseQuery() diff --git a/modules/backend/behaviors/UserPreferencesModel.php b/modules/backend/behaviors/UserPreferencesModel.php index bc186a983..c88e61afa 100644 --- a/modules/backend/behaviors/UserPreferencesModel.php +++ b/modules/backend/behaviors/UserPreferencesModel.php @@ -85,7 +85,7 @@ class UserPreferencesModel extends SettingsModel * Checks if a key is legitimate or should be added to * the field value collection */ - private function isKeyAllowed($key) + protected function isKeyAllowed($key) { /* * Let the core columns through diff --git a/modules/backend/classes/AuthManager.php b/modules/backend/classes/AuthManager.php index f115514be..41b992f76 100644 --- a/modules/backend/classes/AuthManager.php +++ b/modules/backend/classes/AuthManager.php @@ -37,17 +37,17 @@ class AuthManager extends RainAuthManager /** * @var array Cache of registration callbacks. */ - private $callbacks = []; + protected $callbacks = []; /** * @var array List of registered permissions. */ - private $permissions = []; + protected $permissions = []; /** * @var array Cache of registered permissions. */ - private $permissionCache = false; + protected $permissionCache = false; /** * Registers a callback function that defines authentication permissions. diff --git a/modules/backend/classes/BackendController.php b/modules/backend/classes/BackendController.php index ea5b2172c..a1a9b8147 100644 --- a/modules/backend/classes/BackendController.php +++ b/modules/backend/classes/BackendController.php @@ -75,7 +75,7 @@ class BackendController extends ControllerBase * @param string $action Specifies a method name to execute. * @return ControllerBase Returns the backend controller object */ - private function findController($controller, $action, $dirPrefix = null) + protected function findController($controller, $action, $dirPrefix = null) { /* * Workaround: Composer does not support case insensitivity. diff --git a/modules/backend/classes/FormField.php b/modules/backend/classes/FormField.php index e14b2f481..267b78bc0 100644 --- a/modules/backend/classes/FormField.php +++ b/modules/backend/classes/FormField.php @@ -53,12 +53,12 @@ class FormField public $options; /** - * @var string Specifies a side. Possible values: auto, left, right, full + * @var string Specifies a side. Possible values: auto, left, right, full. */ public $span = 'full'; /** - * @var string Specifies a size. Possible values: tiny, small, large, huge, giant + * @var string Specifies a size. Possible values: tiny, small, large, huge, giant. */ public $size = 'large'; @@ -88,12 +88,12 @@ class FormField public $comment; /** - * @var string Specifies the comment position + * @var string Specifies the comment position. */ public $commentPosition = 'below'; /** - * @var string Specifies if the comment is in HTML format + * @var string Specifies if the comment is in HTML format. */ public $commentHtml = false; diff --git a/modules/backend/classes/ListColumn.php b/modules/backend/classes/ListColumn.php index a4ee78683..ea9d14346 100644 --- a/modules/backend/classes/ListColumn.php +++ b/modules/backend/classes/ListColumn.php @@ -51,15 +51,25 @@ class ListColumn public $relation; /** - * @var string Specify a CSS class to attach to the list row element. + * @var string Specify a CSS class to attach to the list cell element. */ public $cssClass; /** - * @var string Specify a format or style for the column value, such as a Date + * @var string Specify a format or style for the column value, such as a Date. */ public $format; + /** + * @var string Specifies a path for partial-type fields. + */ + public $path; + + /** + * @var array Raw field configuration. + */ + public $config; + /** * Constructor */ @@ -75,19 +85,30 @@ class ListColumn * - number - numeric column, aligned right * @param string $type Specifies a render mode as described above */ - public function displayAs($type) + public function displayAs($type, $config) { - $this->type = $type; + $this->type = strtolower($type) ?: $this->type; + $this->config = $this->evalConfig($config); return $this; } /** - * Specifies CSS classes to apply to the table row element. + * Process options and apply them to this object. + * @param array $config + * @return array */ - public function cssClass($class) + protected function evalConfig($config) { - $this->cssClass = $class; - return $this; + if (isset($config['cssClass'])) $this->cssClass = $config['cssClass']; + if (isset($config['searchable'])) $this->searchable = $config['searchable']; + if (isset($config['sortable'])) $this->sortable = $config['sortable']; + if (isset($config['invisible'])) $this->invisible = $config['invisible']; + if (isset($config['select'])) $this->sqlSelect = $config['select']; + if (isset($config['relation'])) $this->relation = $config['relation']; + if (isset($config['format'])) $this->format = $config['format']; + if (isset($config['path'])) $this->path = $config['path']; + + return $config; } } \ No newline at end of file diff --git a/modules/backend/classes/NavigationManager.php b/modules/backend/classes/NavigationManager.php index 340ef76ce..8b21c68f4 100644 --- a/modules/backend/classes/NavigationManager.php +++ b/modules/backend/classes/NavigationManager.php @@ -17,18 +17,18 @@ class NavigationManager /** * @var array Cache of registration callbacks. */ - private $callbacks = []; + protected $callbacks = []; /** * @var array List of registered items. */ - private $items; + protected $items; - private $contextSidenavPartials = []; + protected $contextSidenavPartials = []; - private $contextOwner; - private $contextMainMenuItemCode; - private $contextSideMenuItemCode; + protected $contextOwner; + protected $contextMainMenuItemCode; + protected $contextSideMenuItemCode; static $mainItemDefaults = [ 'code' => null, @@ -419,7 +419,7 @@ class NavigationManager * @param array $items A collection of menu items * @return array The filtered menu items */ - private function filterItemPermissions($user, array $items) + protected function filterItemPermissions($user, array $items) { $items = array_filter($items, function($item) use ($user) { if (!$item->permissions || !count($item->permissions)) @@ -436,7 +436,7 @@ class NavigationManager * @param object $item * @return string */ - private function makeItemKey($owner, $code) + protected function makeItemKey($owner, $code) { return strtoupper($owner).'.'.strtoupper($code); } diff --git a/modules/backend/classes/WidgetManager.php b/modules/backend/classes/WidgetManager.php index 59fb88e9f..007ad3571 100644 --- a/modules/backend/classes/WidgetManager.php +++ b/modules/backend/classes/WidgetManager.php @@ -27,7 +27,7 @@ class WidgetManager /** * @var array Cache of report widget registration callbacks. */ - private $formWidgetCallbacks = []; + protected $formWidgetCallbacks = []; /** * @var array An array of report widgets. @@ -42,7 +42,7 @@ class WidgetManager /** * @var array Cache of report widget registration callbacks. */ - private $reportWidgetCallbacks = []; + protected $reportWidgetCallbacks = []; /** * @var array An array where keys are aliases and values are class names. diff --git a/modules/backend/controllers/AccessLogs.php b/modules/backend/controllers/AccessLogs.php new file mode 100644 index 000000000..19961285b --- /dev/null +++ b/modules/backend/controllers/AccessLogs.php @@ -0,0 +1,41 @@ + post('password') ], true); + // Log the sign in event + AccessLog::add($user); + // Load version updates VersionManager::instance()->updateAll(); diff --git a/modules/backend/controllers/accesslogs/_hint.htm b/modules/backend/controllers/accesslogs/_hint.htm new file mode 100644 index 000000000..66c293f29 --- /dev/null +++ b/modules/backend/controllers/accesslogs/_hint.htm @@ -0,0 +1,4 @@ + +

+ 60])) ?> +

\ No newline at end of file diff --git a/modules/backend/controllers/accesslogs/_list_toolbar.htm b/modules/backend/controllers/accesslogs/_list_toolbar.htm new file mode 100644 index 000000000..449ba7d85 --- /dev/null +++ b/modules/backend/controllers/accesslogs/_list_toolbar.htm @@ -0,0 +1,3 @@ +
+ +
\ No newline at end of file diff --git a/modules/backend/controllers/accesslogs/config_list.yaml b/modules/backend/controllers/accesslogs/config_list.yaml new file mode 100644 index 000000000..7d59a4a22 --- /dev/null +++ b/modules/backend/controllers/accesslogs/config_list.yaml @@ -0,0 +1,14 @@ +# =================================== +# List Behavior Config +# =================================== + +title: backend::lang.access_log.menu_label +list: @/modules/backend/models/accesslog/columns.yaml +modelClass: Backend\Models\AccessLog +noRecordsMessage: backend::lang.list.no_records +showSetup: true + +toolbar: + buttons: list_toolbar + search: + prompt: backend::lang.list.search_prompt \ No newline at end of file diff --git a/modules/backend/controllers/accesslogs/index.htm b/modules/backend/controllers/accesslogs/index.htm new file mode 100644 index 000000000..1251e47e4 --- /dev/null +++ b/modules/backend/controllers/accesslogs/index.htm @@ -0,0 +1,5 @@ +
+ makeHintPartial('backend_accesslogs_hint', 'hint') ?> +
+ +listRender() ?> \ No newline at end of file diff --git a/modules/backend/controllers/users/create.htm b/modules/backend/controllers/users/create.htm index b473c3b5c..89e408a25 100644 --- a/modules/backend/controllers/users/create.htm +++ b/modules/backend/controllers/users/create.htm @@ -52,10 +52,10 @@ +
+ +
-
- -

fatalError) ?>

diff --git a/modules/backend/controllers/users/myaccount.htm b/modules/backend/controllers/users/myaccount.htm index d9f646246..900caefb1 100644 --- a/modules/backend/controllers/users/myaccount.htm +++ b/modules/backend/controllers/users/myaccount.htm @@ -42,10 +42,10 @@ +
+ +
-
- -

fatalError) ?>

diff --git a/modules/backend/controllers/users/update.htm b/modules/backend/controllers/users/update.htm index 7a0281ae2..fa37d400e 100644 --- a/modules/backend/controllers/users/update.htm +++ b/modules/backend/controllers/users/update.htm @@ -60,10 +60,10 @@ +
+ +
-
- -

fatalError) ?>

diff --git a/modules/backend/database/migrations/2014_10_01_000006_Db_Backend_Access_Log.php b/modules/backend/database/migrations/2014_10_01_000006_Db_Backend_Access_Log.php new file mode 100644 index 000000000..84980f211 --- /dev/null +++ b/modules/backend/database/migrations/2014_10_01_000006_Db_Backend_Access_Log.php @@ -0,0 +1,26 @@ +engine = 'InnoDB'; + $table->increments('id'); + $table->integer('user_id')->unsigned(); + $table->string('ip_address')->nullable(); + $table->timestamps(); + }); + } + + public function down() + { + Schema::dropIfExists('backend_access_log'); + } + +} diff --git a/modules/backend/formwidgets/DataGrid.php b/modules/backend/formwidgets/DataGrid.php index 1b405ca8c..42b6331bd 100644 --- a/modules/backend/formwidgets/DataGrid.php +++ b/modules/backend/formwidgets/DataGrid.php @@ -38,6 +38,14 @@ class DataGrid extends FormWidgetBase $this->grid->bindToController(); } + /** + * @return Backend\Widgets\Grid The grid to be displayed. + */ + public function getGrid() + { + return $this->grid; + } + /** * {@inheritDoc} */ @@ -74,19 +82,57 @@ class DataGrid extends FormWidgetBase $grid = new Grid($this->controller, $config); $grid->alias = $this->alias . 'Grid'; $grid->bindEvent('grid.autocomplete', [$this, 'getAutocompleteValues']); + $grid->bindEvent('grid.dataSource', [$this, 'getDataSourceValues']); return $grid; } + /** + * Looks at the model for getXXXAutocompleteValues or getGridAutocompleteValues methods + * to obtain values for autocomplete field types. + * @param string $field Grid field name + * @param string $value Current value + * @param string $data Data for the entire grid + * @return array + */ public function getAutocompleteValues($field, $value, $data) { - if (!$this->model->methodExists('getGridAutocompleteValues')) + $methodName = 'get'.studly_case($this->columnName).'AutocompleteValues'; + + if (!$this->model->methodExists($methodName) && !$this->model->methodExists('getGridAutocompleteValues')) throw new ApplicationException('Model :model does not contain a method getGridAutocompleteValues()'); - $result = $this->model->getGridAutocompleteValues($field, $value, $data); + if ($this->model->methodExists($methodName)) + $result = $this->model->$methodName($field, $value, $data); + else + $result = $this->model->getGridAutocompleteValues($this->columnName, $field, $value, $data); + if (!is_array($result)) $result = []; return $result; } -} \ No newline at end of file + + /** + * Looks at the model for getXXXDataSourceValues or getGridDataSourceValues methods + * to obtain the starting values for the grid. + * @return array + */ + public function getDataSourceValues() + { + $methodName = 'get'.studly_case($this->columnName).'DataSourceValues'; + + if (!$this->model->methodExists($methodName) && !$this->model->methodExists('getGridDataSourceValues')) + throw new ApplicationException('Model :model does not contain a method getGridDataSourceValues()'); + + if ($this->model->methodExists($methodName)) + $result = $this->model->$methodName(); + else + $result = $this->model->getGridDataSourceValues($this->columnName); + + if (!is_array($result)) + $result = []; + + return $result; + } +} diff --git a/modules/backend/formwidgets/FileUpload.php b/modules/backend/formwidgets/FileUpload.php index 22c127a17..e2cac70cd 100644 --- a/modules/backend/formwidgets/FileUpload.php +++ b/modules/backend/formwidgets/FileUpload.php @@ -68,10 +68,13 @@ class FileUpload extends FormWidgetBase $columnName = $this->columnName; $list = $this->model->$columnName()->withDeferred($this->sessionKey)->orderBy('sort_order')->get(); - // Set the thumb for each file + /* + * Set the thumb for each file + */ foreach ($list as $file) { $file->thumb = $file->getThumb($this->imageWidth, $this->imageHeight, ['mode' => 'crop']); } + return $list; } diff --git a/modules/backend/formwidgets/RecordFinder.php b/modules/backend/formwidgets/RecordFinder.php index 7b5346475..0f229a448 100644 --- a/modules/backend/formwidgets/RecordFinder.php +++ b/modules/backend/formwidgets/RecordFinder.php @@ -1,9 +1,11 @@ 'Language', 'locale_comment' => 'Select your desired locale for language use.', ], + 'access_log' => [ + 'hint' => 'This log displays a list of successful sign in attempts by administrators. Records are kept for a total of :days days.', + 'menu_label' => 'Access Log', + 'menu_description' => 'View a list of successful back-end user sign ins.', + 'created_at' => 'Date & Time', + 'login' => 'Login', + 'ip_address' => 'IP address', + 'first_name' => 'First name', + 'last_name' => 'Last name', + 'email' => 'Email', + ], ]; diff --git a/modules/backend/layouts/form-with-sidebar.htm b/modules/backend/layouts/form-with-sidebar.htm index 03c65cf88..fea3dc914 100644 --- a/modules/backend/layouts/form-with-sidebar.htm +++ b/modules/backend/layouts/form-with-sidebar.htm @@ -1,14 +1,13 @@
+ + +
+ +
+ +
- - - -
- -
- -
diff --git a/modules/backend/models/AccessLog.php b/modules/backend/models/AccessLog.php new file mode 100644 index 000000000..7143cb2d8 --- /dev/null +++ b/modules/backend/models/AccessLog.php @@ -0,0 +1,38 @@ + ['Backend\Models\User'] + ]; + + /** + * Creates a log record + * @param Backend\Models\User $user Admin user + * @return self + */ + public static function add($user) + { + $record = new static; + $record->user = $user; + $record->ip_address = Request::getClientIp(); + $record->save(); + + return $record; + } + +} \ No newline at end of file diff --git a/modules/backend/models/accesslog/columns.yaml b/modules/backend/models/accesslog/columns.yaml new file mode 100644 index 000000000..b4dfcbb22 --- /dev/null +++ b/modules/backend/models/accesslog/columns.yaml @@ -0,0 +1,36 @@ +# =================================== +# Column Definitions +# =================================== + +columns: + created_at: + label: backend::lang.access_log.created_at + searchable: yes + + login: + label: backend::lang.access_log.login + relation: user + select: @login + searchable: yes + + ip_address: + label: backend::lang.access_log.ip_address + searchable: yes + + first_name: + label: backend::lang.access_log.first_name + relation: user + select: @first_name + searchable: yes + + last_name: + label: backend::lang.access_log.last_name + relation: user + select: @first_name + searchable: yes + + email: + label: backend::lang.access_log.email + relation: user + select: @email + searchable: yes \ No newline at end of file diff --git a/modules/backend/traits/ViewMaker.php b/modules/backend/traits/ViewMaker.php index bf4235984..9511d2ad3 100644 --- a/modules/backend/traits/ViewMaker.php +++ b/modules/backend/traits/ViewMaker.php @@ -45,8 +45,8 @@ trait ViewMaker * Render a partial file contents located in the views folder. * @param string $partial The view to load. * @param array $params Parameter variables to pass to the view. - * @param bool $throwException Throw an exception if the partial is not found - * @return string The view contents. + * @param bool $throwException Throw an exception if the partial is not found. + * @return mixed Partial contents or false if not throwing an exception. */ public function makePartial($partial, $params = [], $throwException = true) { diff --git a/modules/backend/widgets/Form.php b/modules/backend/widgets/Form.php index 987e958d5..3eb2e8ffa 100644 --- a/modules/backend/widgets/Form.php +++ b/modules/backend/widgets/Form.php @@ -39,14 +39,14 @@ class Form extends WidgetBase /** * @var boolean Determines if field definitions have been created. */ - private $fieldsDefined = false; + protected $fieldsDefined = false; /** * @var array Collection of all fields used in this form. */ public $allFields = []; - /** + /** * @var array Collection of all form widgets used in this form. */ public $formWidgets = []; @@ -218,7 +218,7 @@ class Form extends WidgetBase /** * Prepares the list data */ - public function prepareVars() + protected function prepareVars() { $this->defineFormFields(); $this->vars['sessionKey'] = $this->getSessionKey(); @@ -517,7 +517,7 @@ class Form extends WidgetBase * @param string $fieldType * @return boolean */ - private function isFormWidget($fieldType) + protected function isFormWidget($fieldType) { if ($fieldType === null) return false; @@ -539,7 +539,7 @@ class Form extends WidgetBase /** * Makes a widget object from a form field object. */ - public function makeFormWidget($field) + protected function makeFormWidget($field) { if ($field->type != 'widget') return null; @@ -687,7 +687,7 @@ class Form extends WidgetBase /** * Looks at the model for defined options. */ - private function getOptionsFromModel($field, $fieldOptions) + protected function getOptionsFromModel($field, $fieldOptions) { /* * Advanced usage, supplied options are callable @@ -751,7 +751,7 @@ class Form extends WidgetBase * @param string $method * @return boolean */ - private function methodExists($object, $method) + protected function methodExists($object, $method) { if (method_exists($object, 'methodExists')) return $object->methodExists($method); diff --git a/modules/backend/widgets/Grid.php b/modules/backend/widgets/Grid.php index 11de95c86..b8d3cc05f 100644 --- a/modules/backend/widgets/Grid.php +++ b/modules/backend/widgets/Grid.php @@ -226,7 +226,7 @@ class Grid extends WidgetBase case 'currency': $item['type'] = 'numeric'; - $item['format'] = '$0,0.00'; + $item['format'] = isset($column['format']) ? $column['format'] : '$0,0.00'; break; case 'checkbox': @@ -258,4 +258,4 @@ class Grid extends WidgetBase $this->addJs('js/datagrid.js', 'core'); } -} \ No newline at end of file +} diff --git a/modules/backend/widgets/Lists.php b/modules/backend/widgets/Lists.php index 95c4b7118..24b1fde3a 100644 --- a/modules/backend/widgets/Lists.php +++ b/modules/backend/widgets/Lists.php @@ -249,7 +249,7 @@ class Lists extends WidgetBase * @param string $table * @return string */ - private function parseTableName($sql, $table) + protected function parseTableName($sql, $table) { return str_replace('@', $table.'.', $sql); } @@ -502,18 +502,10 @@ class Lists extends WidgetBase else $label = studly_case($name); - $column = new ListColumn($name, $label); + $columnType = isset($config['type']) ? $config['type'] : null; - /* - * Process options - */ - if (isset($config['type'])) $column->type = $config['type']; - if (isset($config['searchable'])) $column->searchable = $config['searchable']; - if (isset($config['sortable'])) $column->sortable = $config['sortable']; - if (isset($config['invisible'])) $column->invisible = $config['invisible']; - if (isset($config['select'])) $column->sqlSelect = $config['select']; - if (isset($config['relation'])) $column->relation = $config['relation']; - if (isset($config['format'])) $column->format = $config['format']; + $column = new ListColumn($name, $label); + $column->displayAs($columnType, $config); return $column; } @@ -606,6 +598,14 @@ class Lists extends WidgetBase // Value processing // + /** + * Process as boolean switch + */ + public function evalPartialTypeValue($value, $column) + { + return $this->controller->makePartial($column->path ?: $column->columnName, ['value' => $value, 'column' => $column]); + } + /** * Process as boolean switch */ @@ -679,7 +679,7 @@ class Lists extends WidgetBase /** * Validates a column type as a date */ - private function validateDateTimeValue($value, $column) + protected function validateDateTimeValue($value, $column) { if ($value instanceof DateTime) $value = Carbon::instance($value); diff --git a/modules/backend/widgets/Toolbar.php b/modules/backend/widgets/Toolbar.php index e2abfb0b2..eefd498ac 100644 --- a/modules/backend/widgets/Toolbar.php +++ b/modules/backend/widgets/Toolbar.php @@ -19,7 +19,7 @@ class Toolbar extends WidgetBase /** * @var WidgetBase Reference to the search widget object. */ - private $searchWidget; + protected $searchWidget; /** * @var string Name of partial containing control panel. diff --git a/modules/backend/widgets/grid/assets/js/datagrid.js b/modules/backend/widgets/grid/assets/js/datagrid.js index 6a12550bd..4bc799640 100644 --- a/modules/backend/widgets/grid/assets/js/datagrid.js +++ b/modules/backend/widgets/grid/assets/js/datagrid.js @@ -73,6 +73,12 @@ if (this.options.data) { handsontableOptions.data = this.options.data } + /* + * Data from an AJAX data source + */ + else if (this.options.sourceHandler) { + self.refreshDataSource() + } /* * Data from a data locker */ @@ -96,12 +102,6 @@ delete handsontableOptions.data } } - /* - * Data from an AJAX data source - */ - else if (this.options.sourceHandler) { - self.refreshDataSource() - } /* * Monitor for data changes @@ -454,4 +454,4 @@ Handsontable.PluginHooks.add('afterUpdateSettings', function () { init.call(this) }); -})(Handsontable, jQuery); \ No newline at end of file +})(Handsontable, jQuery); diff --git a/modules/backend/widgets/lists/partials/_list_body_row.htm b/modules/backend/widgets/lists/partials/_list_body_row.htm index 1e6cecee4..4c0525b28 100644 --- a/modules/backend/widgets/lists/partials/_list_body_row.htm +++ b/modules/backend/widgets/lists/partials/_list_body_row.htm @@ -19,7 +19,7 @@ $column): ?> - + getRecordUrl($record))): ?> getRecordOnClick($record) ?> href=""> getColumnValue($record, $column) ?> diff --git a/modules/backend/widgets/lists/partials/_list_head_row.htm b/modules/backend/widgets/lists/partials/_list_head_row.htm index c2781fa6b..afeb3b3bc 100644 --- a/modules/backend/widgets/lists/partials/_list_head_row.htm +++ b/modules/backend/widgets/lists/partials/_list_head_row.htm @@ -16,7 +16,7 @@ $column): ?> sortable): ?> - + - + getHeaderValue($column) ?> diff --git a/modules/cms/classes/CmsException.php b/modules/cms/classes/CmsException.php index e8066d626..3dec87b0f 100644 --- a/modules/cms/classes/CmsException.php +++ b/modules/cms/classes/CmsException.php @@ -20,7 +20,7 @@ class CmsException extends ApplicationException /** * @var Cms\Classes\CmsCompoundObject A reference to a CMS object used for masking errors. */ - private $compoundObject; + protected $compoundObject; /** * @var array Collection of error codes for each error distinction. diff --git a/modules/cms/classes/CmsObject.php b/modules/cms/classes/CmsObject.php index e5c43894f..ba64235a2 100644 --- a/modules/cms/classes/CmsObject.php +++ b/modules/cms/classes/CmsObject.php @@ -8,10 +8,10 @@ use Validator; use System\Classes\SystemException; use System\Classes\ApplicationException; use October\Rain\Support\ValidationException; -use Exception; -use RecursiveIteratorIterator; use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; use ArrayAccess; +use Exception; /** * This is a base class for all CMS objects - content files, pages, partials and layouts. diff --git a/modules/cms/classes/CodeParser.php b/modules/cms/classes/CodeParser.php index ca60a7131..866d02424 100644 --- a/modules/cms/classes/CodeParser.php +++ b/modules/cms/classes/CodeParser.php @@ -17,22 +17,22 @@ class CodeParser /** * @var \Cms\Classes\CmsCompoundObject A reference to the CMS object being parsed. */ - private $object; + protected $object; /** * @var string Contains a path to the CMS object's file being parsed. */ - private $filePath; + protected $filePath; /** * @var mixed The internal cache, keeps parsed object information during a request. */ - static private $cache = []; + static protected $cache = []; /** * @var string Key for the parsed PHP file information cache. */ - private $dataCacheKey = 'cms-php-file-data'; + protected $dataCacheKey = 'cms-php-file-data'; /** * Creates the class instance diff --git a/modules/cms/classes/ComponentManager.php b/modules/cms/classes/ComponentManager.php index 8674c7a33..ed0f3cfb8 100644 --- a/modules/cms/classes/ComponentManager.php +++ b/modules/cms/classes/ComponentManager.php @@ -18,7 +18,7 @@ class ComponentManager /** * @var array Cache of registration callbacks. */ - private $callbacks = []; + protected $callbacks = []; /** * @var array An array where keys are codes and values are class names. diff --git a/modules/cms/classes/Controller.php b/modules/cms/classes/Controller.php index 58a77e9e2..cce7b120b 100644 --- a/modules/cms/classes/Controller.php +++ b/modules/cms/classes/Controller.php @@ -16,9 +16,10 @@ use Twig_Environment; use Controller as BaseController; use Cms\Twig\Loader as TwigLoader; use Cms\Twig\Extension as CmsTwigExtension; -use System\Twig\Extension as SystemTwigExtension; use Cms\Classes\FileHelper as CmsFileHelper; +use System\Models\RequestLog; use System\Classes\ErrorHandler; +use System\Twig\Extension as SystemTwigExtension; use October\Rain\Support\Markdown; use October\Rain\Support\ValidationException; use Illuminate\Http\RedirectResponse; @@ -141,6 +142,9 @@ class Controller extends BaseController if (!$page) { $this->setStatusCode(404); + // Log the 404 request + RequestLog::add(); + if (!$page = $this->router->findByUrl('/404')) return Response::make(View::make('cms::404'), $this->statusCode); } @@ -535,8 +539,12 @@ class Controller extends BaseController /** * Renders a requested partial. * The framework uses this method internally. + * @param string $partial The view to load. + * @param array $params Parameter variables to pass to the view. + * @param bool $throwException Throw an exception if the partial is not found. + * @return mixed Partial contents or false if not throwing an exception. */ - public function renderPartial($name, $parameters = []) + public function renderPartial($name, $parameters = [], $throwException = true) { /* * Alias @ symbol for :: @@ -555,18 +563,26 @@ class Controller extends BaseController * Component alias not supplied */ if (!strlen($componentAlias)) { - if ($this->componentContext !== null) + if ($this->componentContext !== null) { $componentObj = $this->componentContext; - - elseif (($componentObj = $this->findComponentByPartial($partialName)) === null) - throw new CmsException(Lang::get('cms::lang.partial.not_found', ['name'=>$name])); + } + elseif (($componentObj = $this->findComponentByPartial($partialName)) === null) { + if ($throwException) + throw new CmsException(Lang::get('cms::lang.partial.not_found', ['name'=>$name])); + else + return false; + } } /* * Component alias is supplied */ else { - if (($componentObj = $this->findComponentByName($componentAlias)) === null) - throw new CmsException(Lang::get('cms::lang.component.not_found', ['name'=>$componentAlias])); + if (($componentObj = $this->findComponentByName($componentAlias)) === null) { + if ($throwException) + throw new CmsException(Lang::get('cms::lang.component.not_found', ['name'=>$componentAlias])); + else + return false; + } } $partial = null; @@ -587,8 +603,12 @@ class Controller extends BaseController $partial = ComponentPartial::loadCached($componentObj, $partialName); - if ($partial === null) - throw new CmsException(Lang::get('cms::lang.partial.not_found', ['name'=>$name])); + if ($partial === null) { + if ($throwException) + throw new CmsException(Lang::get('cms::lang.partial.not_found', ['name'=>$name])); + else + return false; + } /* * Set context for self access @@ -599,8 +619,12 @@ class Controller extends BaseController /* * Process theme partial */ - if (($partial = Partial::loadCached($this->theme, $name)) === null) - throw new CmsException(Lang::get('cms::lang.partial.not_found', ['name'=>$name])); + if (($partial = Partial::loadCached($this->theme, $name)) === null) { + if ($throwException) + throw new CmsException(Lang::get('cms::lang.partial.not_found', ['name'=>$name])); + else + return false; + } } CmsException::mask($partial, 400); @@ -665,7 +689,7 @@ class Controller extends BaseController return $result; } - return $this->renderPartial($name.'::default'); + return $this->renderPartial($name.'::default', [], false); } /** @@ -797,7 +821,7 @@ class Controller extends BaseController * Searches the layout and page components by an alias * @return ComponentBase The component object, if found */ - private function findComponentByName($name) + protected function findComponentByName($name) { if (isset($this->page->components[$name])) return $this->page->components[$name]; @@ -812,7 +836,7 @@ class Controller extends BaseController * Searches the layout and page components by an AJAX handler * @return ComponentBase The component object, if found */ - private function findComponentByHandler($handler) + protected function findComponentByHandler($handler) { foreach ($this->page->components as $component) { if (method_exists($component, $handler)) @@ -831,7 +855,7 @@ class Controller extends BaseController * Searches the layout and page components by a partial file * @return ComponentBase The component object, if found */ - private function findComponentByPartial($partial) + protected function findComponentByPartial($partial) { foreach ($this->page->components as $component) { $fileName = ComponentPartial::getFilePath($component, $partial); diff --git a/modules/cms/classes/Router.php b/modules/cms/classes/Router.php index 17964df10..d12582063 100644 --- a/modules/cms/classes/Router.php +++ b/modules/cms/classes/Router.php @@ -42,7 +42,7 @@ class Router /** * @var array A list of parameters names and values extracted from the URL pattern and URL string. */ - private $parameters = []; + protected $parameters = []; /** * @var array Contains the URL map - the list of page file names and corresponding URL patterns. diff --git a/modules/cms/classes/Theme.php b/modules/cms/classes/Theme.php index 440c98bdf..56b65e686 100644 --- a/modules/cms/classes/Theme.php +++ b/modules/cms/classes/Theme.php @@ -6,6 +6,7 @@ use Lang; use Cache; use Event; use Config; +use DbDongle; use October\Rain\Support\Yaml; use System\Models\Parameters; use System\Classes\SystemException; @@ -97,13 +98,15 @@ class Theme $paramKey = 'cms::theme.active'; $activeTheme = Config::get('cms.activeTheme'); - $dbResult = Parameters::findRecord($paramKey) - ->remember(1440, $paramKey) - ->pluck('value') - ; + if (DbDongle::hasDatabase()) { + $dbResult = Parameters::findRecord($paramKey) + ->remember(1440, $paramKey) + ->pluck('value') + ; - if ($dbResult !== null) - $activeTheme = $dbResult; + if ($dbResult !== null) + $activeTheme = $dbResult; + } $apiResult = Event::fire('cms.activeTheme', [], true); if ($apiResult !== null) diff --git a/modules/cms/controllers/Index.php b/modules/cms/controllers/Index.php index 578afcf85..9f4e17439 100644 --- a/modules/cms/controllers/Index.php +++ b/modules/cms/controllers/Index.php @@ -394,7 +394,7 @@ class Index extends Controller * @param string $markup The markup to convert to unix style endings * @return string */ - private function convertLineEndings($markup) + protected function convertLineEndings($markup) { $markup = str_replace("\r\n", "\n", $markup); $markup = str_replace("\r", "\n", $markup); diff --git a/modules/cms/twig/Extension.php b/modules/cms/twig/Extension.php index d9e390617..7d3ef5e81 100644 --- a/modules/cms/twig/Extension.php +++ b/modules/cms/twig/Extension.php @@ -22,7 +22,7 @@ class Extension extends Twig_Extension /** * @var \Cms\Classes\Controller A reference to the CMS controller. */ - private $controller; + protected $controller; /** * Creates the extension instance. diff --git a/modules/system/ServiceProvider.php b/modules/system/ServiceProvider.php index 05e103d38..97a4761dd 100644 --- a/modules/system/ServiceProvider.php +++ b/modules/system/ServiceProvider.php @@ -5,6 +5,7 @@ use Lang; use Event; use Config; use Backend; +use DbDongle; use BackendMenu; use BackendAuth; use Twig_Environment; @@ -16,6 +17,7 @@ use System\Classes\SettingsManager; use System\Twig\Engine as TwigEngine; use System\Twig\Loader as TwigLoader; use System\Twig\Extension as TwigExtension; +use System\Models\EventLog; use System\Models\MailSettings; use System\Models\MailTemplate; use Backend\Classes\WidgetManager; @@ -73,6 +75,16 @@ class ServiceProvider extends ModuleServiceProvider return $handler->handleException($exception, $httpCode, $isConsole); }); + /* + * Write all log events to the database + */ + Event::listen('illuminate.log', function($level, $message, $context){ + if (!DbDongle::hasDatabase()) + return; + + EventLog::add($message, $level); + }); + /* * Register basic Twig */ @@ -99,7 +111,7 @@ class ServiceProvider extends ModuleServiceProvider }); /* - * Override system email with email settings + * Override system mailer with mail settings */ Event::listen('mailer.beforeRegister', function() { if (MailSettings::isConfigured()) @@ -202,7 +214,7 @@ class ServiceProvider extends ModuleServiceProvider */ SettingsManager::instance()->registerCallback(function($manager){ $manager->registerSettingItems('October.System', [ - 'email_settings' => [ + 'mail_settings' => [ 'label' => 'system::lang.mail.menu_label', 'description' => 'system::lang.mail.menu_description', 'category' => 'System', @@ -235,8 +247,25 @@ class ServiceProvider extends ModuleServiceProvider 'url' => Backend::url('system/updates'), 'permissions' => ['system.manage_updates'], 'order' => 700 - ] - + ], + 'event_logs' => [ + 'label' => 'system::lang.event_log.menu_label', + 'description' => 'system::lang.event_log.menu_description', + 'category' => 'Logs', + 'icon' => 'icon-exclamation-triangle', + 'url' => Backend::url('system/eventlogs'), + 'permissions' => ['system.access_event_logs'], + 'order' => 800 + ], + 'request_logs' => [ + 'label' => 'system::lang.request_log.menu_label', + 'description' => 'system::lang.request_log.menu_description', + 'category' => 'Logs', + 'icon' => 'icon-file-o', + 'url' => Backend::url('system/requestlogs'), + 'permissions' => ['system.access_request_logs'], + 'order' => 800 + ], ]); }); @@ -261,7 +290,6 @@ class ServiceProvider extends ModuleServiceProvider /* * Register the sidebar for the System main menu */ - BackendMenu::registerContextSidenavPartial('October.System', 'system', '@/modules/system/partials/_system_sidebar.htm'); } diff --git a/modules/system/behaviors/SettingsModel.php b/modules/system/behaviors/SettingsModel.php index cbff8d117..f4c7c30a8 100644 --- a/modules/system/behaviors/SettingsModel.php +++ b/modules/system/behaviors/SettingsModel.php @@ -176,7 +176,7 @@ class SettingsModel extends ModelBehavior * Checks if a key is legitimate or should be added to * the field value collection */ - private function isKeyAllowed($key) + protected function isKeyAllowed($key) { /* * Let the core columns through diff --git a/modules/system/classes/ExceptionBase.php b/modules/system/classes/ExceptionBase.php index 3a91d6b29..7112a1923 100644 --- a/modules/system/classes/ExceptionBase.php +++ b/modules/system/classes/ExceptionBase.php @@ -18,7 +18,7 @@ class ExceptionBase extends Exception /** * @var Exception If this exception is acting as a mask, this property stores the face exception. */ - private $mask; + protected $mask; /** * @var string Hint Message to help the user with troubleshooting the error (optional). @@ -43,7 +43,7 @@ class ExceptionBase extends Exception /** * @var stdObject Cached code information for highlighting code. */ - private $highlight; + protected $highlight; /** * CMS base exception class constructor. Inherits the native PHP Exception. @@ -254,7 +254,7 @@ class ExceptionBase extends Exception * @param array $traceInfo The trace information from getTrace() or debug_backtrace(). * @return array The filtered array containing the trace information. */ - private function filterCallStack($traceInfo) + protected function filterCallStack($traceInfo) { /* * Determine if filter should be used at all. diff --git a/modules/system/classes/MarkupManager.php b/modules/system/classes/MarkupManager.php index 6a6453900..37cf11193 100644 --- a/modules/system/classes/MarkupManager.php +++ b/modules/system/classes/MarkupManager.php @@ -24,7 +24,7 @@ class MarkupManager /** * @var array Cache of registration callbacks. */ - private $callbacks = []; + protected $callbacks = []; /** * @var array Registered extension items diff --git a/modules/system/classes/SettingsManager.php b/modules/system/classes/SettingsManager.php index bfd0db461..30459ffcb 100644 --- a/modules/system/classes/SettingsManager.php +++ b/modules/system/classes/SettingsManager.php @@ -17,20 +17,20 @@ class SettingsManager /** * @var array Cache of registration callbacks. */ - private $callbacks = []; + protected $callbacks = []; /** * @var array List of registered items. */ - private $items; + protected $items; /** * @var array Flat collection of all items. */ - private $allItems; + protected $allItems; - private $contextOwner; - private $contextItemCode; + protected $contextOwner; + protected $contextItemCode; static $itemDefaults = [ 'code' => null, @@ -270,7 +270,7 @@ class SettingsManager * @param array $items A collection of setting items * @return array The filtered settings items */ - private function filterItemPermissions($user, array $items) + protected function filterItemPermissions($user, array $items) { array_filter($items, function($item) use ($user) { if (!$item->permissions || !count($item->permissions)) diff --git a/modules/system/classes/UpdateManager.php b/modules/system/classes/UpdateManager.php index b1425d14a..04e8dcd2e 100644 --- a/modules/system/classes/UpdateManager.php +++ b/modules/system/classes/UpdateManager.php @@ -606,7 +606,7 @@ class UpdateManager * @param string $fileCode A unique file code * @return string Full path on the disk */ - private function getFilePath($fileCode) + protected function getFilePath($fileCode) { $name = md5($fileCode) . '.arc'; return $this->tempDirectory . '/' . $name; @@ -628,7 +628,7 @@ class UpdateManager * @param string $uri URI * @return string URL */ - private function createServerUrl($uri) + protected function createServerUrl($uri) { $gateway = Config::get('cms.updateServer', 'http://octobercms.com/api'); if (substr($gateway, -1) != '/') @@ -643,7 +643,7 @@ class UpdateManager * @param array $postData Post data * @return void */ - private function applyHttpAttributes($http, $postData) + protected function applyHttpAttributes($http, $postData) { $postData['url'] = base64_encode(URL::to('/')); @@ -664,7 +664,7 @@ class UpdateManager * Create a nonce based on millisecond time * @return int */ - private function createNonce() + protected function createNonce() { $mt = explode(' ', microtime()); return $mt[1] . substr($mt[0], 2, 6); @@ -674,7 +674,7 @@ class UpdateManager * Create a unique signature for transmission. * @return string */ - private function createSignature($data, $secret) + protected function createSignature($data, $secret) { return base64_encode(hash_hmac('sha512', http_build_query($data, '', '&'), base64_decode($secret), true)); } diff --git a/modules/system/classes/VersionManager.php b/modules/system/classes/VersionManager.php index f80f8b1d5..aad27e31a 100644 --- a/modules/system/classes/VersionManager.php +++ b/modules/system/classes/VersionManager.php @@ -243,7 +243,7 @@ class VersionManager /** * Returns the absolute path to a version file for a plugin. */ - private function getVersionFile($code) + protected function getVersionFile($code) { $versionFile = $this->pluginManager->getPluginPath($code) . '/updates/version.yaml'; return $versionFile; @@ -252,7 +252,7 @@ class VersionManager /** * Checks if a plugin has a version file. */ - private function hasVersionFile($code) + protected function hasVersionFile($code) { $versionFile = $this->getVersionFile($code); return File::isFile($versionFile); diff --git a/modules/system/controllers/EventLogs.php b/modules/system/controllers/EventLogs.php new file mode 100644 index 000000000..8e30a8e8d --- /dev/null +++ b/modules/system/controllers/EventLogs.php @@ -0,0 +1,52 @@ +listRefresh(); + } + +} \ No newline at end of file diff --git a/modules/system/controllers/RequestLogs.php b/modules/system/controllers/RequestLogs.php new file mode 100644 index 000000000..f7a78f18e --- /dev/null +++ b/modules/system/controllers/RequestLogs.php @@ -0,0 +1,52 @@ +listRefresh(); + } + +} \ No newline at end of file diff --git a/modules/system/controllers/Settings.php b/modules/system/controllers/Settings.php index bab2723ef..af03d20c7 100644 --- a/modules/system/controllers/Settings.php +++ b/modules/system/controllers/Settings.php @@ -23,7 +23,7 @@ class Settings extends Controller /** * @var WidgetBase Reference to the widget object. */ - private $formWidget; + protected $formWidget; public $requiredPermissions = ['system.manage_settings']; @@ -144,7 +144,7 @@ class Settings extends Controller /** * Locates a setting item for a module or plugin */ - private function findSettingItem($author, $plugin, $code) + protected function findSettingItem($author, $plugin, $code) { $manager = SettingsManager::instance(); diff --git a/modules/system/controllers/Updates.php b/modules/system/controllers/Updates.php index e6a4a783d..72f5a233c 100644 --- a/modules/system/controllers/Updates.php +++ b/modules/system/controllers/Updates.php @@ -263,7 +263,7 @@ class Updates extends Controller return $this->makePartial('execute'); } - private function buildUpdateSteps($core, $plugins, $themes) + protected function buildUpdateSteps($core, $plugins, $themes) { if (!is_array($core)) $core = [null, null]; diff --git a/modules/system/controllers/eventlogs/_hint.htm b/modules/system/controllers/eventlogs/_hint.htm new file mode 100644 index 000000000..b9d16c62f --- /dev/null +++ b/modules/system/controllers/eventlogs/_hint.htm @@ -0,0 +1,4 @@ + +

+ +

\ No newline at end of file diff --git a/modules/system/controllers/eventlogs/_list_toolbar.htm b/modules/system/controllers/eventlogs/_list_toolbar.htm new file mode 100644 index 000000000..1207b2ce3 --- /dev/null +++ b/modules/system/controllers/eventlogs/_list_toolbar.htm @@ -0,0 +1,9 @@ +
+ + + +
\ No newline at end of file diff --git a/modules/system/controllers/eventlogs/_message_column.htm b/modules/system/controllers/eventlogs/_message_column.htm new file mode 100644 index 000000000..664a26e14 --- /dev/null +++ b/modules/system/controllers/eventlogs/_message_column.htm @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/modules/system/controllers/eventlogs/config_form.yaml b/modules/system/controllers/eventlogs/config_form.yaml new file mode 100644 index 000000000..466e9b063 --- /dev/null +++ b/modules/system/controllers/eventlogs/config_form.yaml @@ -0,0 +1,19 @@ +# =================================== +# Form Behavior Config +# =================================== + +# Record name +name: Log + +# Model Form Field configuration +form: @/modules/system/models/eventlog/fields.yaml + +# Model Class name +modelClass: System\Models\EventLog + +# Default redirect location +defaultRedirect: system/eventlogs + +# Preview page +preview: + title: Event \ No newline at end of file diff --git a/modules/system/controllers/eventlogs/config_list.yaml b/modules/system/controllers/eventlogs/config_list.yaml new file mode 100644 index 000000000..0286dffed --- /dev/null +++ b/modules/system/controllers/eventlogs/config_list.yaml @@ -0,0 +1,15 @@ +# =================================== +# List Behavior Config +# =================================== + +title: system::lang.event_log.menu_label +list: @/modules/system/models/eventlog/columns.yaml +modelClass: System\Models\EventLog +recordUrl: system/eventlogs/preview/:id +noRecordsMessage: backend::lang.list.no_records +showSetup: true + +toolbar: + buttons: list_toolbar + search: + prompt: backend::lang.list.search_prompt \ No newline at end of file diff --git a/modules/system/controllers/eventlogs/index.htm b/modules/system/controllers/eventlogs/index.htm new file mode 100644 index 000000000..59f52c79d --- /dev/null +++ b/modules/system/controllers/eventlogs/index.htm @@ -0,0 +1,5 @@ +
+ makeHintPartial('system_eventlogs_hint', 'hint') ?> +
+ +listRender() ?> \ No newline at end of file diff --git a/modules/system/controllers/eventlogs/preview.htm b/modules/system/controllers/eventlogs/preview.htm new file mode 100644 index 000000000..82d865062 --- /dev/null +++ b/modules/system/controllers/eventlogs/preview.htm @@ -0,0 +1,41 @@ + + + + +fatalError): ?> + +
+
+
+

+

#id ?>

+
+
+

+

level ?>

+
+
+

+

created_at->toDayDateTimeString() ?>

+
+
+
+ +
+ formRenderPreview() ?> +
+ + + +

fatalError) ?>

+ + + +

+ + + +

diff --git a/modules/system/controllers/requestlogs/_hint.htm b/modules/system/controllers/requestlogs/_hint.htm new file mode 100644 index 000000000..1835fe83e --- /dev/null +++ b/modules/system/controllers/requestlogs/_hint.htm @@ -0,0 +1,4 @@ + +

+ +

\ No newline at end of file diff --git a/modules/system/controllers/requestlogs/_list_toolbar.htm b/modules/system/controllers/requestlogs/_list_toolbar.htm new file mode 100644 index 000000000..389a5e3c3 --- /dev/null +++ b/modules/system/controllers/requestlogs/_list_toolbar.htm @@ -0,0 +1,9 @@ +
+ + + +
\ No newline at end of file diff --git a/modules/system/controllers/requestlogs/_referer_field.htm b/modules/system/controllers/requestlogs/_referer_field.htm new file mode 100644 index 000000000..fbc41f242 --- /dev/null +++ b/modules/system/controllers/requestlogs/_referer_field.htm @@ -0,0 +1,11 @@ +referer) > 0): ?> +
+ +
+ +
There were no detected referers to this URL.
+ \ No newline at end of file diff --git a/modules/system/controllers/requestlogs/config_form.yaml b/modules/system/controllers/requestlogs/config_form.yaml new file mode 100644 index 000000000..df72d7376 --- /dev/null +++ b/modules/system/controllers/requestlogs/config_form.yaml @@ -0,0 +1,19 @@ +# =================================== +# Form Behavior Config +# =================================== + +# Record name +name: Log + +# Model Form Field configuration +form: @/modules/system/models/requestlog/fields.yaml + +# Model Class name +modelClass: System\Models\RequestLog + +# Default redirect location +defaultRedirect: system/requestlogs + +# Preview page +preview: + title: Request \ No newline at end of file diff --git a/modules/system/controllers/requestlogs/config_list.yaml b/modules/system/controllers/requestlogs/config_list.yaml new file mode 100644 index 000000000..18017ee28 --- /dev/null +++ b/modules/system/controllers/requestlogs/config_list.yaml @@ -0,0 +1,15 @@ +# =================================== +# List Behavior Config +# =================================== + +title: system::lang.request_log.menu_label +list: @/modules/system/models/requestlog/columns.yaml +modelClass: System\Models\RequestLog +recordUrl: system/requestlogs/preview/:id +noRecordsMessage: backend::lang.list.no_records +showSetup: true + +toolbar: + buttons: list_toolbar + search: + prompt: backend::lang.list.search_prompt \ No newline at end of file diff --git a/modules/system/controllers/requestlogs/index.htm b/modules/system/controllers/requestlogs/index.htm new file mode 100644 index 000000000..e55af6fad --- /dev/null +++ b/modules/system/controllers/requestlogs/index.htm @@ -0,0 +1,5 @@ +
+ makeHintPartial('system_requestlogs_hint', 'hint') ?> +
+ +listRender() ?> \ No newline at end of file diff --git a/modules/system/controllers/requestlogs/preview.htm b/modules/system/controllers/requestlogs/preview.htm new file mode 100644 index 000000000..b690986af --- /dev/null +++ b/modules/system/controllers/requestlogs/preview.htm @@ -0,0 +1,45 @@ + + + + +fatalError): ?> + +
+
+
+

+

#id ?>

+
+
+

+

status_code ?>

+
+
+

+

count ?>

+
+
+

+

referer ? count($formModel->referer) : 0 ?>

+
+
+
+ +
+ formRenderPreview() ?> +
+ + + +

fatalError) ?>

+ + + +

+ + + +

\ No newline at end of file diff --git a/modules/system/database/migrations/2014_10_01_000011_Db_System_Event_Logs.php b/modules/system/database/migrations/2014_10_01_000011_Db_System_Event_Logs.php new file mode 100644 index 000000000..abebefa58 --- /dev/null +++ b/modules/system/database/migrations/2014_10_01_000011_Db_System_Event_Logs.php @@ -0,0 +1,27 @@ +engine = 'InnoDB'; + $table->increments('id'); + $table->string('level')->nullable()->index(); + $table->text('message')->nullable(); + $table->mediumtext('details')->nullable(); + $table->timestamps(); + }); + } + + public function down() + { + Schema::dropIfExists('system_event_logs'); + } + +} diff --git a/modules/system/database/migrations/2014_10_01_000012_Db_System_Request_Logs.php b/modules/system/database/migrations/2014_10_01_000012_Db_System_Request_Logs.php new file mode 100644 index 000000000..7d31258c3 --- /dev/null +++ b/modules/system/database/migrations/2014_10_01_000012_Db_System_Request_Logs.php @@ -0,0 +1,28 @@ +engine = 'InnoDB'; + $table->increments('id'); + $table->integer('status_code')->nullable(); + $table->string('url')->nullable(); + $table->text('referer')->nullable(); + $table->integer('count')->default(0); + $table->timestamps(); + }); + } + + public function down() + { + Schema::dropIfExists('system_request_logs'); + } + +} diff --git a/modules/system/lang/en/lang.php b/modules/system/lang/en/lang.php index e506ee274..f3dd476fe 100644 --- a/modules/system/lang/en/lang.php +++ b/modules/system/lang/en/lang.php @@ -176,4 +176,33 @@ return [ 'zip' => [ 'extract_failed' => "Unable to extract core file ':file'.", ], + 'event_log' => [ + 'hint' => 'This log displays a list of potential errors that occur in the application, such as exceptions and debugging information.', + 'menu_label' => 'Event Log', + 'menu_description' => 'View system log messages with their recorded time and details.', + 'empty_link' => 'Empty event log', + 'empty_loading' => 'Emptying event log...', + 'empty_success' => 'Successfully emptied the event log.', + 'return_link' => 'Return to event log', + 'id' => 'ID', + 'id_label' => 'Event ID', + 'created_at' => 'Date & Time', + 'message' => 'Message', + 'level' => 'Level', + ], + 'request_log' => [ + 'hint' => 'This log displays a list of browser requests that may require attention. For example, if a visitor opens a CMS page that cannot be found, a record is created with the status code 404.', + 'menu_label' => 'Request Log', + 'menu_description' => 'View bad or redirected requests, such as Page not found (404).', + 'empty_link' => 'Empty request log', + 'empty_loading' => 'Emptying request log...', + 'empty_success' => 'Successfully emptied the request log.', + 'return_link' => 'Return to request log', + 'id' => 'ID', + 'id_label' => 'Log ID', + 'count' => 'Counter', + 'referer' => 'Referers', + 'url' => 'URL', + 'status_code' => 'Status', + ], ]; diff --git a/modules/system/models/EventLog.php b/modules/system/models/EventLog.php new file mode 100644 index 000000000..d9eed6fb7 --- /dev/null +++ b/modules/system/models/EventLog.php @@ -0,0 +1,52 @@ +message = $message; + $record->level = $level; + + if ($details !== null) + $record->details = (array) $details; + + $record->save(); + + return $record; + } + + /** + * Beautify level value. + * @param string $level + * @return string + */ + public function getLevelAttribute($level) + { + return ucfirst($level); + } + +} \ No newline at end of file diff --git a/modules/system/models/RequestLog.php b/modules/system/models/RequestLog.php new file mode 100644 index 000000000..8f120ee7b --- /dev/null +++ b/modules/system/models/RequestLog.php @@ -0,0 +1,55 @@ + Request::fullUrl(), + 'status_code' => $statusCode, + ]); + + if ($referer = Request::header('referer')) { + $referers = (array) $record->referer ?: []; + $referers[] = $referer; + $record->referer = $referers; + } + + if (!$record->exists) { + $record->count = 1; + $record->save(); + } + else { + $record->increment('count'); + } + + return $record; + } + +} \ No newline at end of file diff --git a/modules/system/models/eventlog/columns.yaml b/modules/system/models/eventlog/columns.yaml new file mode 100644 index 000000000..66468347c --- /dev/null +++ b/modules/system/models/eventlog/columns.yaml @@ -0,0 +1,18 @@ +# =================================== +# Column Definitions +# =================================== + +columns: + id: + label: system::lang.event_log.id + searchable: yes + + created_at: + label: system::lang.event_log.created_at + searchable: yes + + message: + label: system::lang.event_log.message + searchable: yes + type: partial + path: message_column \ No newline at end of file diff --git a/modules/system/models/eventlog/fields.yaml b/modules/system/models/eventlog/fields.yaml new file mode 100644 index 000000000..bc9c06114 --- /dev/null +++ b/modules/system/models/eventlog/fields.yaml @@ -0,0 +1,8 @@ +# =================================== +# Field Definitions +# =================================== + +fields: + + message: + type: textarea diff --git a/modules/system/models/requestlog/columns.yaml b/modules/system/models/requestlog/columns.yaml new file mode 100644 index 000000000..9e29e714f --- /dev/null +++ b/modules/system/models/requestlog/columns.yaml @@ -0,0 +1,15 @@ +# =================================== +# Column Definitions +# =================================== + +columns: + status_code: + label: system::lang.request_log.status_code + searchable: yes + + url: + label: system::lang.request_log.url + searchable: yes + + count: + label: system::lang.request_log.count diff --git a/modules/system/models/requestlog/fields.yaml b/modules/system/models/requestlog/fields.yaml new file mode 100644 index 000000000..cafedf72d --- /dev/null +++ b/modules/system/models/requestlog/fields.yaml @@ -0,0 +1,13 @@ +# =================================== +# Field Definitions +# =================================== + +fields: + + url: + label: system::lang.request_log.url + + referer: + label: system::lang.request_log.referer + type: partial + path: referer_field \ No newline at end of file diff --git a/modules/system/traits/AssetMaker.php b/modules/system/traits/AssetMaker.php index 74c0236b8..6e5be3450 100644 --- a/modules/system/traits/AssetMaker.php +++ b/modules/system/traits/AssetMaker.php @@ -212,7 +212,7 @@ trait AssetMaker * @param array $asset Stored asset array * @return string */ - private function getAssetEntryBuildPath($asset) + protected function getAssetEntryBuildPath($asset) { $path = $asset['path']; if (isset($asset['attributes']['build'])) { @@ -234,7 +234,7 @@ trait AssetMaker * @param string $asset Specifies a path (URL) to the asset. * @return string */ - private function getAssetScheme($asset) + protected function getAssetScheme($asset) { if (preg_match("/(\/\/|http|https)/", $asset)) return $asset; diff --git a/modules/system/twig/Extension.php b/modules/system/twig/Extension.php index 71483a294..22ec306f1 100644 --- a/modules/system/twig/Extension.php +++ b/modules/system/twig/Extension.php @@ -20,7 +20,7 @@ class Extension extends Twig_Extension /** * @var \System\Classes\MarkupManager A reference to the markup manager instance. */ - private $markupManager; + protected $markupManager; /** * Creates the extension instance. diff --git a/tests/unit/cms/classes/ControllerTest.php b/tests/unit/cms/classes/ControllerTest.php index 34d06f5a4..121df73a5 100644 --- a/tests/unit/cms/classes/ControllerTest.php +++ b/tests/unit/cms/classes/ControllerTest.php @@ -1,7 +1,7 @@ load('apitest'); $controller = new Controller($theme); @@ -26,7 +25,6 @@ class ControllerTest extends TestCase /* * Test the theme 404 page */ - $theme = new Theme(); $theme->load('test'); $controller = new Controller($theme);