Merge branch 'master' into release
This commit is contained in:
commit
b97e792c5f
|
@ -7,7 +7,6 @@ Homestead.yaml
|
||||||
/public/plugins
|
/public/plugins
|
||||||
/public/css/*.map
|
/public/css/*.map
|
||||||
/public/js/*.map
|
/public/js/*.map
|
||||||
/public/uploads
|
|
||||||
/public/bower
|
/public/bower
|
||||||
/storage/images
|
/storage/images
|
||||||
_ide_helper.php
|
_ide_helper.php
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
<?php namespace BookStack\Exceptions;
|
<?php namespace BookStack\Exceptions;
|
||||||
|
|
||||||
|
|
||||||
class ConfirmationEmailException extends NotifyException
|
class ConfirmationEmailException extends NotifyException {}
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
|
@ -5,6 +5,7 @@ namespace BookStack\Exceptions;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Contracts\Validation\ValidationException;
|
use Illuminate\Contracts\Validation\ValidationException;
|
||||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||||
|
use PhpSpec\Exception\Example\ErrorException;
|
||||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||||
use Illuminate\Auth\Access\AuthorizationException;
|
use Illuminate\Auth\Access\AuthorizationException;
|
||||||
|
@ -38,17 +39,26 @@ class Handler extends ExceptionHandler
|
||||||
/**
|
/**
|
||||||
* Render an exception into an HTTP response.
|
* Render an exception into an HTTP response.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
* @param \Illuminate\Http\Request $request
|
||||||
* @param \Exception $e
|
* @param \Exception $e
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function render($request, Exception $e)
|
public function render($request, Exception $e)
|
||||||
{
|
{
|
||||||
if($e instanceof NotifyException) {
|
// Handle notify exceptions which will redirect to the
|
||||||
|
// specified location then show a notification message.
|
||||||
|
if ($e instanceof NotifyException) {
|
||||||
\Session::flash('error', $e->message);
|
\Session::flash('error', $e->message);
|
||||||
return response()->redirectTo($e->redirectLocation);
|
return response()->redirectTo($e->redirectLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle pretty exceptions which will show a friendly application-fitting page
|
||||||
|
// Which will include the basic message to point the user roughly to the cause.
|
||||||
|
if (($e instanceof PrettyException || $e->getPrevious() instanceof PrettyException) && !config('app.debug')) {
|
||||||
|
$message = ($e instanceof PrettyException) ? $e->getMessage() : $e->getPrevious()->getMessage();
|
||||||
|
return response()->view('errors/500', ['message' => $message], 500);
|
||||||
|
}
|
||||||
|
|
||||||
return parent::render($request, $e);
|
return parent::render($request, $e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
<?php namespace BookStack\Exceptions;
|
<?php namespace BookStack\Exceptions;
|
||||||
|
|
||||||
|
class ImageUploadException extends PrettyException {}
|
||||||
use Exception;
|
|
||||||
|
|
||||||
class ImageUploadException extends Exception {}
|
|
|
@ -1,9 +1,3 @@
|
||||||
<?php namespace BookStack\Exceptions;
|
<?php namespace BookStack\Exceptions;
|
||||||
|
|
||||||
|
class LdapException extends PrettyException {}
|
||||||
use Exception;
|
|
||||||
|
|
||||||
class LdapException extends Exception
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?php namespace BookStack\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class PrettyException extends Exception {}
|
|
@ -1,6 +1,4 @@
|
||||||
<?php namespace BookStack\Exceptions;
|
<?php namespace BookStack\Exceptions;
|
||||||
|
|
||||||
|
|
||||||
class SocialDriverNotConfigured extends \Exception
|
class SocialDriverNotConfigured extends PrettyException {}
|
||||||
{
|
|
||||||
}
|
|
|
@ -1,7 +1,4 @@
|
||||||
<?php namespace BookStack\Exceptions;
|
<?php namespace BookStack\Exceptions;
|
||||||
|
|
||||||
|
|
||||||
class SocialSignInException extends NotifyException
|
class SocialSignInException extends NotifyException {}
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,7 +1,4 @@
|
||||||
<?php namespace BookStack\Exceptions;
|
<?php namespace BookStack\Exceptions;
|
||||||
|
|
||||||
|
|
||||||
class UserRegistrationException extends NotifyException
|
class UserRegistrationException extends NotifyException {}
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
|
@ -130,8 +130,8 @@ class UserController extends Controller
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'name' => 'required',
|
'name' => 'min:2',
|
||||||
'email' => 'required|email|unique:users,email,' . $id,
|
'email' => 'min:2|email|unique:users,email,' . $id,
|
||||||
'password' => 'min:5|required_with:password_confirm',
|
'password' => 'min:5|required_with:password_confirm',
|
||||||
'password-confirm' => 'same:password|required_with:password',
|
'password-confirm' => 'same:password|required_with:password',
|
||||||
'role' => 'exists:roles,id'
|
'role' => 'exists:roles,id'
|
||||||
|
|
|
@ -4,6 +4,7 @@ use BookStack\Exceptions\ImageUploadException;
|
||||||
use BookStack\Image;
|
use BookStack\Image;
|
||||||
use BookStack\User;
|
use BookStack\User;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use Intervention\Image\Exception\NotSupportedException;
|
||||||
use Intervention\Image\ImageManager;
|
use Intervention\Image\ImageManager;
|
||||||
use Illuminate\Contracts\Filesystem\Factory as FileSystem;
|
use Illuminate\Contracts\Filesystem\Factory as FileSystem;
|
||||||
use Illuminate\Contracts\Filesystem\Filesystem as FileSystemInstance;
|
use Illuminate\Contracts\Filesystem\Filesystem as FileSystemInstance;
|
||||||
|
@ -119,10 +120,12 @@ class ImageService
|
||||||
* Checks the cache then storage to avoid creating / accessing the filesystem on every check.
|
* Checks the cache then storage to avoid creating / accessing the filesystem on every check.
|
||||||
*
|
*
|
||||||
* @param Image $image
|
* @param Image $image
|
||||||
* @param int $width
|
* @param int $width
|
||||||
* @param int $height
|
* @param int $height
|
||||||
* @param bool $keepRatio
|
* @param bool $keepRatio
|
||||||
* @return string
|
* @return string
|
||||||
|
* @throws Exception
|
||||||
|
* @throws ImageUploadException
|
||||||
*/
|
*/
|
||||||
public function getThumbnail(Image $image, $width = 220, $height = 220, $keepRatio = false)
|
public function getThumbnail(Image $image, $width = 220, $height = 220, $keepRatio = false)
|
||||||
{
|
{
|
||||||
|
@ -139,8 +142,16 @@ class ImageService
|
||||||
return $this->getPublicUrl($thumbFilePath);
|
return $this->getPublicUrl($thumbFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise create the thumbnail
|
try {
|
||||||
$thumb = $this->imageTool->make($storage->get($image->path));
|
$thumb = $this->imageTool->make($storage->get($image->path));
|
||||||
|
} catch (Exception $e) {
|
||||||
|
if ($e instanceof \ErrorException || $e instanceof NotSupportedException) {
|
||||||
|
throw new ImageUploadException('The server cannot create thumbnails. Please check you have the GD PHP extension installed.');
|
||||||
|
} else {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($keepRatio) {
|
if ($keepRatio) {
|
||||||
$thumb->resize($width, null, function ($constraint) {
|
$thumb->resize($width, null, function ($constraint) {
|
||||||
$constraint->aspectRatio();
|
$constraint->aspectRatio();
|
||||||
|
|
|
@ -46,7 +46,7 @@ class LdapService
|
||||||
|
|
||||||
$user = $users[0];
|
$user = $users[0];
|
||||||
return [
|
return [
|
||||||
'uid' => $user['uid'][0],
|
'uid' => (isset($user['uid'])) ? $user['uid'][0] : $user['dn'],
|
||||||
'name' => $user['cn'][0],
|
'name' => $user['cn'][0],
|
||||||
'dn' => $user['dn'],
|
'dn' => $user['dn'],
|
||||||
'email' => (isset($user['mail'])) ? $user['mail'][0] : null
|
'email' => (isset($user['mail'])) ? $user['mail'][0] : null
|
||||||
|
|
|
@ -28,4 +28,4 @@ class AddExternalAuthToUsers extends Migration
|
||||||
$table->dropColumn('external_auth_id');
|
$table->dropColumn('external_auth_id');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|
19
readme.md
19
readme.md
|
@ -17,19 +17,13 @@ A platform to create documentation/wiki content. General information about BookS
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
BookStack has similar requirements to Laravel. On top of those are some front-end build tools which are only required when developing.
|
BookStack has similar requirements to Laravel:
|
||||||
|
|
||||||
* PHP >= 5.5.9, Will need to be usable from the command line.
|
* PHP >= 5.5.9, Will need to be usable from the command line.
|
||||||
* OpenSSL PHP Extension
|
* PHP Extensions: `OpenSSL`, `PDO`, `MBstring`, `Tokenizer`, `GD`
|
||||||
* PDO PHP Extension
|
|
||||||
* MBstring PHP Extension
|
|
||||||
* Tokenizer PHP Extension
|
|
||||||
* MySQL >= 5.6
|
* MySQL >= 5.6
|
||||||
* Git (Not strictly required but helps manage updates)
|
* Git (Not strictly required but helps manage updates)
|
||||||
* [Composer](https://getcomposer.org/)
|
* [Composer](https://getcomposer.org/)
|
||||||
* [Node.js](https://nodejs.org/en/) **Development Only**
|
|
||||||
* [Gulp](http://gulpjs.com/) **Development Only**
|
|
||||||
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
@ -144,7 +138,14 @@ A user in BookStack will be linked to a LDAP user via a 'uid'. If a LDAP user ui
|
||||||
|
|
||||||
You may find that you cannot log in with your initial Admin account after changing the `AUTH_METHOD` to `ldap`. To get around this set the `AUTH_METHOD` to `standard`, login with your admin account then change it back to `ldap`. You get then edit your profile and add your LDAP uid under the 'External Authentication ID' field. You will then be able to login in with that ID.
|
You may find that you cannot log in with your initial Admin account after changing the `AUTH_METHOD` to `ldap`. To get around this set the `AUTH_METHOD` to `standard`, login with your admin account then change it back to `ldap`. You get then edit your profile and add your LDAP uid under the 'External Authentication ID' field. You will then be able to login in with that ID.
|
||||||
|
|
||||||
## Testing
|
## Development & Testing
|
||||||
|
|
||||||
|
All development on BookStack is currently done on the master branch. When it's time for a release the master branch is merged into release with built & minified CSS & JS then tagged at it's version. Here are the current development requirements:
|
||||||
|
|
||||||
|
* [Node.js](https://nodejs.org/en/) **Development Only**
|
||||||
|
* [Gulp](http://gulpjs.com/) **Development Only**
|
||||||
|
|
||||||
|
SASS is used to help the CSS development and the JavaScript is run through browserify/babel to allow for writing ES6 code. Both of these are done using gulp.
|
||||||
|
|
||||||
BookStack has many integration tests that use Laravel's built-in testing capabilities which makes use of PHPUnit. To use you will need PHPUnit installed and accessible via command line. There is a `mysql_testing` database defined within the app config which is what is used by PHPUnit. This database is set with the following database name, user name and password defined as `bookstack-test`. You will have to create that database and credentials before testing.
|
BookStack has many integration tests that use Laravel's built-in testing capabilities which makes use of PHPUnit. To use you will need PHPUnit installed and accessible via command line. There is a `mysql_testing` database defined within the app config which is what is used by PHPUnit. This database is set with the following database name, user name and password defined as `bookstack-test`. You will have to create that database and credentials before testing.
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
module.exports = function (ngApp) {
|
module.exports = function (ngApp, events) {
|
||||||
|
|
||||||
ngApp.controller('ImageManagerController', ['$scope', '$attrs', '$http', '$timeout', 'imageManagerService',
|
ngApp.controller('ImageManagerController', ['$scope', '$attrs', '$http', '$timeout', 'imageManagerService',
|
||||||
function ($scope, $attrs, $http, $timeout, imageManagerService) {
|
function ($scope, $attrs, $http, $timeout, imageManagerService) {
|
||||||
|
@ -17,21 +17,40 @@ module.exports = function (ngApp) {
|
||||||
var dataLoaded = false;
|
var dataLoaded = false;
|
||||||
var callback = false;
|
var callback = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple returns the appropriate upload url depending on the image type set.
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
$scope.getUploadUrl = function () {
|
$scope.getUploadUrl = function () {
|
||||||
return '/images/' + $scope.imageType + '/upload';
|
return '/images/' + $scope.imageType + '/upload';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on image upload, Adds an image to local list of images
|
||||||
|
* and shows a success message to the user.
|
||||||
|
* @param file
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
$scope.uploadSuccess = function (file, data) {
|
$scope.uploadSuccess = function (file, data) {
|
||||||
$scope.$apply(() => {
|
$scope.$apply(() => {
|
||||||
$scope.images.unshift(data);
|
$scope.images.unshift(data);
|
||||||
});
|
});
|
||||||
|
events.emit('success', 'Image uploaded');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the callback and hides the image manager.
|
||||||
|
* @param returnData
|
||||||
|
*/
|
||||||
function callbackAndHide(returnData) {
|
function callbackAndHide(returnData) {
|
||||||
if (callback) callback(returnData);
|
if (callback) callback(returnData);
|
||||||
$scope.showing = false;
|
$scope.showing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image select action. Checks if a double-click was fired.
|
||||||
|
* @param image
|
||||||
|
*/
|
||||||
$scope.imageSelect = function (image) {
|
$scope.imageSelect = function (image) {
|
||||||
var dblClickTime = 300;
|
var dblClickTime = 300;
|
||||||
var currentTime = Date.now();
|
var currentTime = Date.now();
|
||||||
|
@ -48,10 +67,19 @@ module.exports = function (ngApp) {
|
||||||
previousClickTime = currentTime;
|
previousClickTime = currentTime;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action that runs when the 'Select image' button is clicked.
|
||||||
|
* Runs the callback and hides the image manager.
|
||||||
|
*/
|
||||||
$scope.selectButtonClick = function () {
|
$scope.selectButtonClick = function () {
|
||||||
callbackAndHide($scope.selectedImage);
|
callbackAndHide($scope.selectedImage);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the image manager.
|
||||||
|
* Takes a callback to execute later on.
|
||||||
|
* @param doneCallback
|
||||||
|
*/
|
||||||
function show(doneCallback) {
|
function show(doneCallback) {
|
||||||
callback = doneCallback;
|
callback = doneCallback;
|
||||||
$scope.showing = true;
|
$scope.showing = true;
|
||||||
|
@ -62,6 +90,8 @@ module.exports = function (ngApp) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Connects up the image manger so it can be used externally
|
||||||
|
// such as from TinyMCE.
|
||||||
imageManagerService.show = show;
|
imageManagerService.show = show;
|
||||||
imageManagerService.showExternal = function (doneCallback) {
|
imageManagerService.showExternal = function (doneCallback) {
|
||||||
$scope.$apply(() => {
|
$scope.$apply(() => {
|
||||||
|
@ -70,10 +100,16 @@ module.exports = function (ngApp) {
|
||||||
};
|
};
|
||||||
window.ImageManager = imageManagerService;
|
window.ImageManager = imageManagerService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the image manager
|
||||||
|
*/
|
||||||
$scope.hide = function () {
|
$scope.hide = function () {
|
||||||
$scope.showing = false;
|
$scope.showing = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the list image data from the server.
|
||||||
|
*/
|
||||||
function fetchData() {
|
function fetchData() {
|
||||||
var url = '/images/' + $scope.imageType + '/all/' + page;
|
var url = '/images/' + $scope.imageType + '/all/' + page;
|
||||||
$http.get(url).then((response) => {
|
$http.get(url).then((response) => {
|
||||||
|
@ -82,28 +118,33 @@ module.exports = function (ngApp) {
|
||||||
page++;
|
page++;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
$scope.fetchData = fetchData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the details of an image.
|
||||||
|
* @param event
|
||||||
|
*/
|
||||||
$scope.saveImageDetails = function (event) {
|
$scope.saveImageDetails = function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
var url = '/images/update/' + $scope.selectedImage.id;
|
var url = '/images/update/' + $scope.selectedImage.id;
|
||||||
$http.put(url, this.selectedImage).then((response) => {
|
$http.put(url, this.selectedImage).then((response) => {
|
||||||
$scope.imageUpdateSuccess = true;
|
events.emit('success', 'Image details updated');
|
||||||
$timeout(() => {
|
|
||||||
$scope.imageUpdateSuccess = false;
|
|
||||||
}, 3000);
|
|
||||||
}, (response) => {
|
}, (response) => {
|
||||||
var errors = response.data;
|
var errors = response.data;
|
||||||
var message = '';
|
var message = '';
|
||||||
Object.keys(errors).forEach((key) => {
|
Object.keys(errors).forEach((key) => {
|
||||||
message += errors[key].join('\n');
|
message += errors[key].join('\n');
|
||||||
});
|
});
|
||||||
$scope.imageUpdateFailure = message;
|
events.emit('error', message);
|
||||||
$timeout(() => {
|
|
||||||
$scope.imageUpdateFailure = false;
|
|
||||||
}, 5000);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete an image from system and notify of success.
|
||||||
|
* Checks if it should force delete when an image
|
||||||
|
* has dependant pages.
|
||||||
|
* @param event
|
||||||
|
*/
|
||||||
$scope.deleteImage = function (event) {
|
$scope.deleteImage = function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
var force = $scope.dependantPages !== false;
|
var force = $scope.dependantPages !== false;
|
||||||
|
@ -112,10 +153,7 @@ module.exports = function (ngApp) {
|
||||||
$http.delete(url).then((response) => {
|
$http.delete(url).then((response) => {
|
||||||
$scope.images.splice($scope.images.indexOf($scope.selectedImage), 1);
|
$scope.images.splice($scope.images.indexOf($scope.selectedImage), 1);
|
||||||
$scope.selectedImage = false;
|
$scope.selectedImage = false;
|
||||||
$scope.imageDeleteSuccess = true;
|
events.emit('success', 'Image successfully deleted');
|
||||||
$timeout(() => {
|
|
||||||
$scope.imageDeleteSuccess = false;
|
|
||||||
}, 3000);
|
|
||||||
}, (response) => {
|
}, (response) => {
|
||||||
// Pages failure
|
// Pages failure
|
||||||
if (response.status === 400) {
|
if (response.status === 400) {
|
||||||
|
@ -124,6 +162,15 @@ module.exports = function (ngApp) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple date creator used to properly format dates.
|
||||||
|
* @param stringDate
|
||||||
|
* @returns {Date}
|
||||||
|
*/
|
||||||
|
$scope.getDate = function(stringDate) {
|
||||||
|
return new Date(stringDate);
|
||||||
|
};
|
||||||
|
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ var toggleSwitchTemplate = require('./components/toggle-switch.html');
|
||||||
var imagePickerTemplate = require('./components/image-picker.html');
|
var imagePickerTemplate = require('./components/image-picker.html');
|
||||||
var dropZoneTemplate = require('./components/drop-zone.html');
|
var dropZoneTemplate = require('./components/drop-zone.html');
|
||||||
|
|
||||||
module.exports = function (ngApp) {
|
module.exports = function (ngApp, events) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle Switches
|
* Toggle Switches
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
// AngularJS - Create application and load components
|
// AngularJS - Create application and load components
|
||||||
var angular = require('angular');
|
var angular = require('angular');
|
||||||
|
@ -7,9 +7,31 @@ var ngAnimate = require('angular-animate');
|
||||||
var ngSanitize = require('angular-sanitize');
|
var ngSanitize = require('angular-sanitize');
|
||||||
|
|
||||||
var ngApp = angular.module('bookStack', ['ngResource', 'ngAnimate', 'ngSanitize']);
|
var ngApp = angular.module('bookStack', ['ngResource', 'ngAnimate', 'ngSanitize']);
|
||||||
var services = require('./services')(ngApp);
|
|
||||||
var directives = require('./directives')(ngApp);
|
|
||||||
var controllers = require('./controllers')(ngApp);
|
// Global Event System
|
||||||
|
var Events = {
|
||||||
|
listeners: {},
|
||||||
|
emit: function (eventName, eventData) {
|
||||||
|
if (typeof this.listeners[eventName] === 'undefined') return this;
|
||||||
|
var eventsToStart = this.listeners[eventName];
|
||||||
|
for (let i = 0; i < eventsToStart.length; i++) {
|
||||||
|
var event = eventsToStart[i];
|
||||||
|
event(eventData);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
listen: function (eventName, callback) {
|
||||||
|
if (typeof this.listeners[eventName] === 'undefined') this.listeners[eventName] = [];
|
||||||
|
this.listeners[eventName].push(callback);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.Events = Events;
|
||||||
|
|
||||||
|
var services = require('./services')(ngApp, Events);
|
||||||
|
var directives = require('./directives')(ngApp, Events);
|
||||||
|
var controllers = require('./controllers')(ngApp, Events);
|
||||||
|
|
||||||
//Global jQuery Config & Extensions
|
//Global jQuery Config & Extensions
|
||||||
|
|
||||||
|
@ -32,8 +54,25 @@ $.expr[":"].contains = $.expr.createPseudo(function (arg) {
|
||||||
// Global jQuery Elements
|
// Global jQuery Elements
|
||||||
$(function () {
|
$(function () {
|
||||||
|
|
||||||
|
|
||||||
|
var notifications = $('.notification');
|
||||||
|
var successNotification = notifications.filter('.pos');
|
||||||
|
var errorNotification = notifications.filter('.neg');
|
||||||
|
// Notification Events
|
||||||
|
window.Events.listen('success', function (text) {
|
||||||
|
successNotification.hide();
|
||||||
|
successNotification.find('span').text(text);
|
||||||
|
setTimeout(() => {
|
||||||
|
successNotification.show();
|
||||||
|
}, 1);
|
||||||
|
});
|
||||||
|
window.Events.listen('error', function (text) {
|
||||||
|
errorNotification.find('span').text(text);
|
||||||
|
errorNotification.show();
|
||||||
|
});
|
||||||
|
|
||||||
// Notification hiding
|
// Notification hiding
|
||||||
$('.notification').click(function () {
|
notifications.click(function () {
|
||||||
$(this).fadeOut(100);
|
$(this).fadeOut(100);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -44,6 +83,29 @@ $(function () {
|
||||||
$(this).closest('.chapter').find('.inset-list').slideToggle(180);
|
$(this).closest('.chapter').find('.inset-list').slideToggle(180);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Back to top button
|
||||||
|
$('#back-to-top').click(function() {
|
||||||
|
$('#header').smoothScrollTo();
|
||||||
|
});
|
||||||
|
var scrollTopShowing = false;
|
||||||
|
var scrollTop = document.getElementById('back-to-top');
|
||||||
|
var scrollTopBreakpoint = 1200;
|
||||||
|
window.addEventListener('scroll', function() {
|
||||||
|
if (!scrollTopShowing && document.body.scrollTop > scrollTopBreakpoint) {
|
||||||
|
scrollTop.style.display = 'block';
|
||||||
|
scrollTopShowing = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
scrollTop.style.opacity = 1;
|
||||||
|
}, 1);
|
||||||
|
} else if (scrollTopShowing && document.body.scrollTop < scrollTopBreakpoint) {
|
||||||
|
scrollTop.style.opacity = 0;
|
||||||
|
scrollTopShowing = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
scrollTop.style.display = 'none';
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ module.exports = {
|
||||||
statusbar: false,
|
statusbar: false,
|
||||||
menubar: false,
|
menubar: false,
|
||||||
paste_data_images: false,
|
paste_data_images: false,
|
||||||
//height: 700,
|
|
||||||
extended_valid_elements: 'pre[*]',
|
extended_valid_elements: 'pre[*]',
|
||||||
automatic_uploads: false,
|
automatic_uploads: false,
|
||||||
valid_children: "-div[p|pre|h1|h2|h3|h4|h5|h6|blockquote]",
|
valid_children: "-div[p|pre|h1|h2|h3|h4|h5|h6|blockquote]",
|
||||||
|
@ -31,7 +30,7 @@ module.exports = {
|
||||||
alignright: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-right'},
|
alignright: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-right'},
|
||||||
},
|
},
|
||||||
file_browser_callback: function (field_name, url, type, win) {
|
file_browser_callback: function (field_name, url, type, win) {
|
||||||
ImageManager.show(function (image) {
|
window.ImageManager.showExternal(function (image) {
|
||||||
win.document.getElementById(field_name).value = image.url;
|
win.document.getElementById(field_name).value = image.url;
|
||||||
if ("createEvent" in document) {
|
if ("createEvent" in document) {
|
||||||
var evt = document.createEvent("HTMLEvents");
|
var evt = document.createEvent("HTMLEvents");
|
||||||
|
@ -40,6 +39,10 @@ module.exports = {
|
||||||
} else {
|
} else {
|
||||||
win.document.getElementById(field_name).fireEvent("onchange");
|
win.document.getElementById(field_name).fireEvent("onchange");
|
||||||
}
|
}
|
||||||
|
var html = '<a href="' + image.url + '" target="_blank">';
|
||||||
|
html += '<img src="' + image.thumbs.display + '" alt="' + image.name + '">';
|
||||||
|
html += '</a>';
|
||||||
|
win.tinyMCE.activeEditor.execCommand('mceInsertContent', false, html);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
paste_preprocess: function (plugin, args) {
|
paste_preprocess: function (plugin, args) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
module.exports = function(ngApp) {
|
module.exports = function(ngApp, events) {
|
||||||
|
|
||||||
ngApp.factory('imageManagerService', function() {
|
ngApp.factory('imageManagerService', function() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.3);
|
box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.3);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
max-width: 1340px;
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
@ -44,18 +43,49 @@
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-manager-list img {
|
.image-manager-list .image {
|
||||||
display: block;
|
display: block;
|
||||||
|
position: relative;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
float: left;
|
float: left;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: (100%/6);
|
width: (100%/6);
|
||||||
height: auto;
|
height: auto;
|
||||||
border: 1px solid #FFF;
|
border: 1px solid #DDD;
|
||||||
|
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0);
|
||||||
transition: all cubic-bezier(.4, 0, 1, 1) 160ms;
|
transition: all cubic-bezier(.4, 0, 1, 1) 160ms;
|
||||||
|
overflow: hidden;
|
||||||
&.selected {
|
&.selected {
|
||||||
transform: scale3d(0.92, 0.92, 0.92);
|
transform: scale3d(0.92, 0.92, 0.92);
|
||||||
|
border: 1px solid #444;
|
||||||
|
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.image-meta {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
color: #EEE;
|
||||||
|
background-color: rgba(0, 0, 0, 0.4);
|
||||||
|
font-size: 10px;
|
||||||
|
padding: 3px 4px;
|
||||||
|
span {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@include smaller-than($xl) {
|
||||||
|
width: (100%/4);
|
||||||
|
}
|
||||||
|
@include smaller-than($m) {
|
||||||
|
.image-meta {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -105,8 +105,8 @@
|
||||||
}
|
}
|
||||||
.book-tree .sidebar-page-list {
|
.book-tree .sidebar-page-list {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0;
|
margin: $-xs 0 0;
|
||||||
margin-top: $-xs;
|
padding-left: 0;
|
||||||
border-left: 5px solid $color-book;
|
border-left: 5px solid $color-book;
|
||||||
li a {
|
li a {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
|
@ -223,13 +223,13 @@ span.highlight {
|
||||||
* Lists
|
* Lists
|
||||||
*/
|
*/
|
||||||
ul {
|
ul {
|
||||||
list-style: disc;
|
padding-left: $-m * 1.5;
|
||||||
margin-left: $-m*1.5;
|
list-style: disc inside;
|
||||||
}
|
}
|
||||||
|
|
||||||
ol {
|
ol {
|
||||||
list-style: decimal;
|
list-style: decimal inside;
|
||||||
margin-left: $-m*1.5;
|
padding-left: $-m * 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -9,4 +9,9 @@
|
||||||
@import "tables";
|
@import "tables";
|
||||||
@import "header";
|
@import "header";
|
||||||
@import "lists";
|
@import "lists";
|
||||||
@import "pages";
|
@import "pages";
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-spacing: 0;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
|
@ -126,4 +126,42 @@ $loadingSize: 10px;
|
||||||
i {
|
i {
|
||||||
padding-right: $-s;
|
padding-right: $-s;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Back to top link
|
||||||
|
$btt-size: 40px;
|
||||||
|
#back-to-top {
|
||||||
|
background-color: rgba($primary, 0.4);
|
||||||
|
position: fixed;
|
||||||
|
bottom: $-m;
|
||||||
|
right: $-m;
|
||||||
|
padding: $-xs $-s;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #FFF;
|
||||||
|
width: $btt-size;
|
||||||
|
height: $btt-size;
|
||||||
|
border-radius: $btt-size;
|
||||||
|
transition: all ease-in-out 180ms;
|
||||||
|
opacity: 0;
|
||||||
|
z-index: 999;
|
||||||
|
&:hover {
|
||||||
|
width: $btt-size*3.4;
|
||||||
|
background-color: rgba($primary, 1);
|
||||||
|
span {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.inner {
|
||||||
|
width: $btt-size*3.4;
|
||||||
|
}
|
||||||
|
i {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 28px;
|
||||||
|
padding: 0 $-s 0 0;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
line-height: 12px;
|
||||||
|
position: relative;
|
||||||
|
top: -5px;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -77,6 +77,11 @@
|
||||||
@yield('content')
|
@yield('content')
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<div id="back-to-top">
|
||||||
|
<div class="inner">
|
||||||
|
<i class="zmdi zmdi-chevron-up"></i> <span>Back to top</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@yield('bottom')
|
@yield('bottom')
|
||||||
<script src="{{ versioned_asset('js/common.js') }}"></script>
|
<script src="{{ versioned_asset('js/common.js') }}"></script>
|
||||||
@yield('scripts')
|
@yield('scripts')
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
@extends('base')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="text-muted">An Error Occurred</h1>
|
||||||
|
<p>{{ $message }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@stop
|
|
@ -7,12 +7,12 @@
|
||||||
<div class="faded-small">
|
<div class="faded-small">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4 faded">
|
<div class="col-sm-4 faded">
|
||||||
<div class="action-buttons text-left">
|
<div class="action-buttons text-left">
|
||||||
<a onclick="$('body>header').slideToggle();" class="text-button text-primary"><i class="zmdi zmdi-swap-vertical"></i>Toggle Header</a>
|
<a onclick="$('body>header').slideToggle();" class="text-button text-primary"><i class="zmdi zmdi-swap-vertical"></i>Toggle Header</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-8 faded">
|
<div class="col-sm-8 faded">
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<a href="{{ back()->getTargetUrl() }}" class="text-button text-primary"><i class="zmdi zmdi-close"></i>Cancel</a>
|
<a href="{{ back()->getTargetUrl() }}" class="text-button text-primary"><i class="zmdi zmdi-close"></i>Cancel</a>
|
||||||
<button type="submit" id="save-button" class="text-button text-pos"><i class="zmdi zmdi-floppy"></i>Save Page</button>
|
<button type="submit" id="save-button" class="text-button text-pos"><i class="zmdi zmdi-floppy"></i>Save Page</button>
|
||||||
|
|
|
@ -20,5 +20,11 @@
|
||||||
table td {
|
table td {
|
||||||
width: auto !important;
|
width: auto !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page-content img.align-left, .page-content img.align-right {
|
||||||
|
float: none !important;
|
||||||
|
clear: both;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@stop
|
@stop
|
|
@ -22,9 +22,9 @@
|
||||||
<span dropdown class="dropdown-container">
|
<span dropdown class="dropdown-container">
|
||||||
<div dropdown-toggle class="text-button text-primary"><i class="zmdi zmdi-open-in-new"></i>Export</div>
|
<div dropdown-toggle class="text-button text-primary"><i class="zmdi zmdi-open-in-new"></i>Export</div>
|
||||||
<ul class="wide">
|
<ul class="wide">
|
||||||
<li><a href="{{$page->getUrl() . '/export/html'}}" target="_blank">Contained Web File <span class="text-muted pull-right">.html</span></a></li>
|
<li><a href="{{$page->getUrl() . '/export/html'}}" target="_blank">Contained Web File <span class="text-muted float right">.html</span></a></li>
|
||||||
<li><a href="{{$page->getUrl() . '/export/pdf'}}" target="_blank">PDF File <span class="text-muted pull-right">.pdf</span></a></li>
|
<li><a href="{{$page->getUrl() . '/export/pdf'}}" target="_blank">PDF File <span class="text-muted float right">.pdf</span></a></li>
|
||||||
<li><a href="{{$page->getUrl() . '/export/plaintext'}}" target="_blank">Plain Text File <span class="text-muted pull-right">.txt</span></a></li>
|
<li><a href="{{$page->getUrl() . '/export/plaintext'}}" target="_blank">Plain Text File <span class="text-muted float right">.txt</span></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
@if($currentUser->can('page-update'))
|
@if($currentUser->can('page-update'))
|
||||||
|
|
|
@ -5,11 +5,14 @@
|
||||||
<div class="image-manager-content">
|
<div class="image-manager-content">
|
||||||
<div class="image-manager-list">
|
<div class="image-manager-list">
|
||||||
<div ng-repeat="image in images">
|
<div ng-repeat="image in images">
|
||||||
<img class="anim fadeIn"
|
<div class="image anim fadeIn" ng-style="{animationDelay: ($index > 26) ? '160ms' : ($index * 25) + 'ms'}"
|
||||||
ng-class="{selected: (image==selectedImage)}"
|
ng-class="{selected: (image==selectedImage)}" ng-click="imageSelect(image)">
|
||||||
ng-src="@{{image.thumbs.gallery}}" ng-attr-alt="@{{image.title}}" ng-attr-title="@{{image.name}}"
|
<img ng-src="@{{image.thumbs.gallery}}" ng-attr-alt="@{{image.title}}" ng-attr-title="@{{image.name}}">
|
||||||
ng-click="imageSelect(image)"
|
<div class="image-meta">
|
||||||
ng-style="{animationDelay: ($index > 26) ? '160ms' : ($index * 25) + 'ms'}">
|
<span class="name" ng-bind="image.name"></span>
|
||||||
|
<span class="date">Uploaded @{{ getDate(image.created_at) | date:'mediumDate' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="load-more" ng-show="hasMore" ng-click="fetchData()">Load More</div>
|
<div class="load-more" ng-show="hasMore" ng-click="fetchData()">Load More</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,18 +22,20 @@
|
||||||
|
|
||||||
<div class="image-manager-sidebar">
|
<div class="image-manager-sidebar">
|
||||||
<h2>Images</h2>
|
<h2>Images</h2>
|
||||||
<hr class="even">
|
|
||||||
<drop-zone upload-url="@{{getUploadUrl()}}" event-success="uploadSuccess"></drop-zone>
|
<drop-zone upload-url="@{{getUploadUrl()}}" event-success="uploadSuccess"></drop-zone>
|
||||||
<div class="image-manager-details anim fadeIn" ng-show="selectedImage">
|
<div class="image-manager-details anim fadeIn" ng-show="selectedImage">
|
||||||
|
|
||||||
<hr class="even">
|
<hr class="even">
|
||||||
|
|
||||||
<form ng-submit="saveImageDetails($event)">
|
<form ng-submit="saveImageDetails($event)">
|
||||||
|
<div>
|
||||||
|
<a ng-href="@{{selectedImage.url}}" target="_blank" style="display: block;">
|
||||||
|
<img ng-src="@{{selectedImage.thumbs.gallery}}" ng-attr-alt="@{{selectedImage.title}}" ng-attr-title="@{{selectedImage.name}}">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="name">Image Name</label>
|
<label for="name">Image Name</label>
|
||||||
<input type="text" id="name" name="name" ng-model="selectedImage.name">
|
<input type="text" id="name" name="name" ng-model="selectedImage.name">
|
||||||
<p class="text-pos text-small" ng-show="imageUpdateSuccess"><i class="fa fa-check"></i> Image name updated</p>
|
|
||||||
<p class="text-neg text-small" ng-show="imageUpdateFailure"><i class="fa fa-times"></i> <span ng-bind="imageUpdateFailure"></span></p>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
@ -53,8 +58,6 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="text-pos" ng-show="imageDeleteSuccess"><i class="fa fa-check"></i> Image deleted</p>
|
|
||||||
|
|
||||||
<div class="image-manager-bottom">
|
<div class="image-manager-bottom">
|
||||||
<button class="button pos anim fadeIn" ng-show="selectedImage" ng-click="selectButtonClick()">
|
<button class="button pos anim fadeIn" ng-show="selectedImage" ng-click="selectButtonClick()">
|
||||||
<i class="zmdi zmdi-square-right"></i>Select Image
|
<i class="zmdi zmdi-square-right"></i>Select Image
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
@if(Session::has('success'))
|
|
||||||
<div class="notification anim pos">
|
|
||||||
<i class="zmdi zmdi-mood"></i> <span>{{ Session::get('success') }}</span>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
@if(Session::has('error'))
|
<div class="notification anim pos" @if(!Session::has('success')) style="display:none;" @endif>
|
||||||
<div class="notification anim neg stopped">
|
<i class="zmdi zmdi-check-circle"></i> <span>{{ Session::get('success') }}</span>
|
||||||
<i class="zmdi zmdi-alert-circle"></i> <span>{{ Session::get('error') }}</span>
|
</div>
|
||||||
</div>
|
|
||||||
@endif
|
<div class="notification anim neg stopped" @if(!Session::has('error')) style="display:none;" @endif>
|
||||||
|
<i class="zmdi zmdi-alert-circle"></i> <span>{{ Session::get('error') }}</span>
|
||||||
|
</div>
|
||||||
|
|
|
@ -28,7 +28,7 @@ class LdapTest extends \TestCase
|
||||||
->andReturn(['count' => 1, 0 => [
|
->andReturn(['count' => 1, 0 => [
|
||||||
'uid' => [$this->mockUser->name],
|
'uid' => [$this->mockUser->name],
|
||||||
'cn' => [$this->mockUser->name],
|
'cn' => [$this->mockUser->name],
|
||||||
'dn' => ['dc=test'.config('services.ldap.base_dn')]
|
'dn' => ['dc=test' . config('services.ldap.base_dn')]
|
||||||
]]);
|
]]);
|
||||||
$this->mockLdap->shouldReceive('bind')->times(6)->andReturn(true);
|
$this->mockLdap->shouldReceive('bind')->times(6)->andReturn(true);
|
||||||
|
|
||||||
|
@ -46,6 +46,30 @@ class LdapTest extends \TestCase
|
||||||
->seeInDatabase('users', ['email' => $this->mockUser->email, 'email_confirmed' => 1, 'external_auth_id' => $this->mockUser->name]);
|
->seeInDatabase('users', ['email' => $this->mockUser->email, 'email_confirmed' => 1, 'external_auth_id' => $this->mockUser->name]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_login_works_when_no_uid_provided_by_ldap_server()
|
||||||
|
{
|
||||||
|
$this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId);
|
||||||
|
$this->mockLdap->shouldReceive('setOption')->once();
|
||||||
|
$ldapDn = 'cn=test-user,dc=test' . config('services.ldap.base_dn');
|
||||||
|
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
|
||||||
|
->with($this->resourceId, config('services.ldap.base_dn'), Mockery::type('string'), Mockery::type('array'))
|
||||||
|
->andReturn(['count' => 1, 0 => [
|
||||||
|
'cn' => [$this->mockUser->name],
|
||||||
|
'dn' => $ldapDn,
|
||||||
|
'mail' => [$this->mockUser->email]
|
||||||
|
]]);
|
||||||
|
$this->mockLdap->shouldReceive('bind')->times(3)->andReturn(true);
|
||||||
|
|
||||||
|
$this->visit('/login')
|
||||||
|
->see('Username')
|
||||||
|
->type($this->mockUser->name, '#username')
|
||||||
|
->type($this->mockUser->password, '#password')
|
||||||
|
->press('Sign In')
|
||||||
|
->seePageIs('/')
|
||||||
|
->see($this->mockUser->name)
|
||||||
|
->seeInDatabase('users', ['email' => $this->mockUser->email, 'email_confirmed' => 1, 'external_auth_id' => $ldapDn]);
|
||||||
|
}
|
||||||
|
|
||||||
public function test_initial_incorrect_details()
|
public function test_initial_incorrect_details()
|
||||||
{
|
{
|
||||||
$this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId);
|
$this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId);
|
||||||
|
@ -55,7 +79,7 @@ class LdapTest extends \TestCase
|
||||||
->andReturn(['count' => 1, 0 => [
|
->andReturn(['count' => 1, 0 => [
|
||||||
'uid' => [$this->mockUser->name],
|
'uid' => [$this->mockUser->name],
|
||||||
'cn' => [$this->mockUser->name],
|
'cn' => [$this->mockUser->name],
|
||||||
'dn' => ['dc=test'.config('services.ldap.base_dn')]
|
'dn' => ['dc=test' . config('services.ldap.base_dn')]
|
||||||
]]);
|
]]);
|
||||||
$this->mockLdap->shouldReceive('bind')->times(3)->andReturn(true, true, false);
|
$this->mockLdap->shouldReceive('bind')->times(3)->andReturn(true, true, false);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue