mirror of
https://github.com/silverstripe/silverstripe-cms
synced 2024-10-22 08:05:56 +02:00
Merged changes from branches/2.2.2-assets:
Static publishing Ability to lock down comments to logged-in members only git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/cms/trunk@60470 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
parent
abd3cc0d15
commit
387b5edd21
@ -19,6 +19,7 @@ Director::addRules(50, array(
|
||||
'admin//$Action/$ID/$OtherID' => 'CMSMain',
|
||||
'unsubscribe//$Email/$MailingList' => 'Unsubscribe_Controller',
|
||||
'PageComment//$Action/$ID' => 'PageComment_Controller',
|
||||
'dev/buildcache' => 'RebuildStaticCacheTask',
|
||||
));
|
||||
|
||||
// Built-in modules
|
||||
|
@ -290,7 +290,7 @@ HTML;
|
||||
}
|
||||
|
||||
// @todo: These workflow features aren't really appropriate for all projects
|
||||
if( Member::currentUser()->_isAdmin() && project() == 'mot' ) {
|
||||
if( Member::currentUser()->isAdmin() && project() == 'mot' ) {
|
||||
$fields->addFieldsToTab( 'Root.Workflow', new DropdownField("Owner", _t('AssetAdmin.OWNER','Owner'), Member::map() ) );
|
||||
$fields->addFieldsToTab( 'Root.Workflow', new TreeMultiselectField("CanUse", _t('AssetAdmin.CONTENTUSABLEBY','Content usable by')) );
|
||||
$fields->addFieldsToTab( 'Root.Workflow', new TreeMultiselectField("CanEdit", _t('AssetAdmin.CONTENTMODBY','Content modifiable by')) );
|
||||
|
@ -548,20 +548,7 @@ JS;
|
||||
* Actually perform the publication step
|
||||
*/
|
||||
public function performPublish($record) {
|
||||
$record->AssignedToID = 0;
|
||||
$record->RequestedByID = 0;
|
||||
$record->Status = "Published";
|
||||
//$record->PublishedByID = Member::currentUser()->ID;
|
||||
$record->write();
|
||||
$record->publish("Stage", "Live");
|
||||
|
||||
GoogleSitemap::ping();
|
||||
|
||||
// Fix the sort order for this page's siblings
|
||||
DB::query("UPDATE SiteTree_Live
|
||||
INNER JOIN SiteTree ON SiteTree_Live.ID = SiteTree.ID
|
||||
SET SiteTree_Live.Sort = SiteTree.Sort
|
||||
WHERE SiteTree_Live.ParentID = " . sprintf('%d', $record->ParentID));
|
||||
$record->doPublish();
|
||||
}
|
||||
|
||||
public function revert($urlParams, $form) {
|
||||
@ -989,7 +976,7 @@ HTML;
|
||||
return;
|
||||
}
|
||||
|
||||
$ids = split(' *, *', $_REQUEST['csvIDs']);
|
||||
$ids = split(' *, *', $this->requestParams['csvIDs']);
|
||||
|
||||
$notifications = array();
|
||||
|
||||
@ -1004,7 +991,7 @@ HTML;
|
||||
|
||||
if($record) {
|
||||
// Publish this page
|
||||
$this->performPublish($record);
|
||||
$record->doPublish();
|
||||
|
||||
// Now make sure the 'changed' icon is removed
|
||||
$publishedRecord = DataObject::get_by_id($this->stat('tree_class'), $id);
|
||||
@ -1203,17 +1190,19 @@ HTML;
|
||||
ini_set("memory_limit","100M");
|
||||
ini_set('max_execution_time', 300);
|
||||
|
||||
if(isset($_POST['confirm'])) {
|
||||
$response = "";
|
||||
|
||||
if(isset($this->requestParams['confirm'])) {
|
||||
$start = 0;
|
||||
$pages = DataObject::get("SiteTree", "", "", "", "$start,30");
|
||||
$count = 0;
|
||||
while(true) {
|
||||
foreach($pages as $page) {
|
||||
$this->performPublish($page);
|
||||
$page->doPublish();
|
||||
$page->destroy();
|
||||
unset($page);
|
||||
$count++;
|
||||
echo "<li>$count</li>";
|
||||
$response .= "<li>$count</li>";
|
||||
}
|
||||
if($pages->Count() > 29) {
|
||||
$start += 30;
|
||||
@ -1223,10 +1212,10 @@ HTML;
|
||||
}
|
||||
}
|
||||
|
||||
echo sprintf(_t('CMSMain.PUBPAGES',"Done: Published %d pages"), $count);
|
||||
$response .= sprintf(_t('CMSMain.PUBPAGES',"Done: Published %d pages"), $count);
|
||||
|
||||
} else {
|
||||
echo '<h1>' . _t('CMSMain.PUBALLFUN','"Publish All" functionality') . '</h1>
|
||||
$response .= '<h1>' . _t('CMSMain.PUBALLFUN','"Publish All" functionality') . '</h1>
|
||||
<p>' . _t('CMSMain.PUBALLFUN2', 'Pressing this button will do the equivalent of going to every page and pressing "publish". It\'s
|
||||
intended to be used after there have been massive edits of the content, such as when the site was
|
||||
first built.') . '</p>
|
||||
@ -1235,6 +1224,8 @@ HTML;
|
||||
. _t('CMSMain.PUBALLCONFIRM',"Please publish every page in the site, copying content stage to live",PR_LOW,'Confirmation button') .'" />
|
||||
</form>';
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
function restorepage() {
|
||||
|
@ -549,7 +549,7 @@ JS;
|
||||
|
||||
// If the 'Save & Publish' button was clicked, also publish the page
|
||||
if (isset($urlParams['publish']) && $urlParams['publish'] == 1) {
|
||||
$this->performPublish($record);
|
||||
$record->doPublish();
|
||||
|
||||
$record->setClassName($record->ClassName);
|
||||
$newClass = $record->ClassName;
|
||||
|
@ -14,6 +14,7 @@ class PageComment extends DataObject {
|
||||
|
||||
static $has_one = array(
|
||||
"Parent" => "SiteTree",
|
||||
"Author" => "Member" // Only set when the user is logged in when posting
|
||||
);
|
||||
|
||||
static $casting = array(
|
||||
|
@ -9,9 +9,23 @@
|
||||
class PageCommentInterface extends ViewableData {
|
||||
protected $controller, $methodName, $page;
|
||||
|
||||
/**
|
||||
* @var boolean If this is true, you must be logged in to post a comment
|
||||
* (and therefore, you don't need to specify a 'Your name' field unless
|
||||
* your name is blank)
|
||||
*/
|
||||
static $comments_require_login = false;
|
||||
|
||||
/**
|
||||
* @var string If this is a valid permission code, you must be logged in
|
||||
* and have the appropriate permission code on your account before you can
|
||||
* post a comment.
|
||||
*/
|
||||
static $comments_require_permission = "";
|
||||
|
||||
/**
|
||||
* Create a new page comment interface
|
||||
* @param controller The controller that the U
|
||||
* @param controller The controller that the interface is used on
|
||||
* @param methodName The method to return this PageCommentInterface object
|
||||
* @param page The page that we're commenting on
|
||||
*/
|
||||
@ -21,10 +35,59 @@ class PageCommentInterface extends ViewableData {
|
||||
$this->page = $page;
|
||||
}
|
||||
|
||||
/**
|
||||
* See @link PageCommentInterface::$comments_require_login
|
||||
* @param boolean state The new state of this static field
|
||||
*/
|
||||
static function set_comments_require_login($state) {
|
||||
self::$comments_require_login = (boolean) $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* See @link PageCommentInterface::$comments_require_permission
|
||||
* @param string permission The permission to check against.
|
||||
*/
|
||||
static function set_comments_require_permission($permission) {
|
||||
self::$comments_require_permission = $permission;
|
||||
}
|
||||
|
||||
function forTemplate() {
|
||||
return $this->renderWith('PageCommentInterface');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean true if the currently logged in user can post a comment,
|
||||
* false if they can't. Users can post comments by default, enforce
|
||||
* security by using
|
||||
* @link PageCommentInterface::set_comments_require_login() and
|
||||
* @link {PageCommentInterface::set_comments_require_permission()}.
|
||||
*/
|
||||
static function CanPostComment() {
|
||||
$member = Member::currentUser();
|
||||
if(self::$comments_require_permission && $member && Permission::check(self::$comments_require_permission)) {
|
||||
return true; // Comments require a certain permission, and the user has the correct permission
|
||||
} elseif(self::$comments_require_login && $member && !self::$comments_require_permission) {
|
||||
return true; // Comments only require that a member is logged in
|
||||
} elseif(!self::$comments_require_permission && !self::$comments_require_login) {
|
||||
return true; // Comments don't require anything - anyone can add a comment
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean true if this page comment form requires users to have a
|
||||
* valid permission code in order to post (used to customize the error
|
||||
* message).
|
||||
*/
|
||||
function PostingRequiresPermission() {
|
||||
return self::$comments_require_permission;
|
||||
}
|
||||
|
||||
function Page() {
|
||||
return $this->page;
|
||||
}
|
||||
|
||||
function PostCommentForm() {
|
||||
Requirements::javascript('jsparty/behaviour.js');
|
||||
Requirements::javascript('jsparty/prototype.js');
|
||||
@ -33,8 +96,17 @@ class PageCommentInterface extends ViewableData {
|
||||
|
||||
|
||||
$fields = new FieldSet(
|
||||
new HiddenField("ParentID", "ParentID", $this->page->ID),
|
||||
new TextField("Name", _t('PageCommentInterface.YOURNAME', 'Your name')));
|
||||
new HiddenField("ParentID", "ParentID", $this->page->ID)
|
||||
);
|
||||
|
||||
$member = Member::currentUser();
|
||||
|
||||
if((self::$comments_require_login || self::$comments_require_permission) && $member && $member->FirstName) {
|
||||
$fields->push(new ReadonlyField("Name", _t('PageCommentInterface.YOURNAME', 'Your name'), $member->getName()));
|
||||
} else {
|
||||
$fields->push(new TextField("Name", _t('PageCommentInterface.YOURNAME', 'Your name')));
|
||||
}
|
||||
|
||||
|
||||
if(MathSpamProtection::isEnabled()){
|
||||
$fields->push(new TextField("Math", sprintf(_t('PageCommentInterface.SPAMQUESTION', "Spam protection question: %s"), MathSpamProtection::getMathQuestion())));
|
||||
@ -120,12 +192,21 @@ class PageCommentInterface_Form extends Form {
|
||||
if(!Director::is_ajax()) {
|
||||
Director::redirectBack();
|
||||
}
|
||||
return "spamprotectionfalied"; //used by javascript for checking if the spam question was wrong
|
||||
return "spamprotectionfailed"; //used by javascript for checking if the spam question was wrong
|
||||
}
|
||||
}
|
||||
|
||||
Cookie::set("PageCommentInterface_Name", $data['Name']);
|
||||
|
||||
// If commenting can only be done by logged in users, make sure the user is logged in
|
||||
$member = Member::currentUser();
|
||||
if(PageCommentInterface::CanPostComment() && $member) {
|
||||
$this->Fields()->push(new HiddenField("AuthorID", "Author ID", $member->ID));
|
||||
} elseif(!PageCommentInterface::CanPostComment()) {
|
||||
echo "You're not able to post comments to this page. Please ensure you are logged in and have an appropriate permission level.";
|
||||
return;
|
||||
}
|
||||
|
||||
$comment = Object::create('PageComment');
|
||||
$this->saveInto($comment);
|
||||
$comment->IsSpam = false;
|
||||
|
101
code/staticpublisher/FilesystemPublisher.php
Normal file
101
code/staticpublisher/FilesystemPublisher.php
Normal file
@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Usage: Object::add_extension("SiteTree", "FilesystemPublisher('../static-folder/')")
|
||||
*/
|
||||
class FilesystemPublisher extends StaticPublisher {
|
||||
protected $destFolder;
|
||||
protected $fileExtension;
|
||||
|
||||
/**
|
||||
* @param $destFolder The folder to save the cached site into
|
||||
* @param $fileExtension The file extension to use, for example, 'html'. If omitted, then each page will be placed
|
||||
* in its own directory, with the filename 'index.html'
|
||||
*/
|
||||
function __construct($destFolder, $fileExtension = null) {
|
||||
if(substr($destFolder, -1) == '/') $destFolder = substr($destFolder, 0, -1);
|
||||
$this->destFolder = $destFolder;
|
||||
$this->fileExtension = $fileExtension;
|
||||
}
|
||||
|
||||
function publishPages($urls) {
|
||||
//$base = Director::absoluteURL($this->destFolder);
|
||||
//$base = preg_replace('/\/[^\/]+\/\.\./','',$base) . '/';
|
||||
//Director::setBaseURL($base);
|
||||
|
||||
$files = array();
|
||||
$i = 0;
|
||||
$totalURLs = sizeof($urls);
|
||||
foreach($urls as $url) {
|
||||
$i++;
|
||||
|
||||
if(StaticPublisher::echo_progress()) {
|
||||
echo " * Publishing page $i/$totalURLs: $url\n";
|
||||
flush();
|
||||
}
|
||||
|
||||
Requirements::clear();
|
||||
$response = Director::test($url);
|
||||
Requirements::clear();
|
||||
/*
|
||||
if(!is_object($response)) {
|
||||
echo "String response for url '$url'\n";
|
||||
print_r($response);
|
||||
}*/
|
||||
if(is_object($response)) $content = $response->getBody();
|
||||
else $content = $response . '';
|
||||
|
||||
if($this->fileExtension) $filename = $url ? "$url.$this->fileExtension" : "index.$this->fileExtension";
|
||||
else $filename = $url ? "$url/index.html" : "index.html";
|
||||
|
||||
$files[$filename] = array(
|
||||
'Content' => $content,
|
||||
'Folder' => (dirname($filename) == '/') ? '' : (dirname($filename).'/'),
|
||||
'Filename' => basename($filename),
|
||||
);
|
||||
|
||||
// Add externals
|
||||
/*
|
||||
$externals = $this->externalReferencesFor($content);
|
||||
if($externals) foreach($externals as $external) {
|
||||
// Skip absolute URLs
|
||||
if(preg_match('/^[a-zA-Z]+:\/\//', $external)) continue;
|
||||
// Drop querystring parameters
|
||||
$external = strtok($external, '?');
|
||||
|
||||
if(file_exists("../" . $external)) {
|
||||
// Break into folder and filename
|
||||
if(preg_match('/^(.*\/)([^\/]+)$/', $external, $matches)) {
|
||||
$files[$external] = array(
|
||||
"Copy" => "../$external",
|
||||
"Folder" => $matches[1],
|
||||
"Filename" => $matches[2],
|
||||
);
|
||||
|
||||
} else {
|
||||
user_error("Can't parse external: $external", E_USER_WARNING);
|
||||
}
|
||||
} else {
|
||||
$missingFiles[$external] = true;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
Director::setBaseURL(null);
|
||||
|
||||
//Debug::show(array_keys($files));
|
||||
//Debug::show(array_keys($missingFiles));
|
||||
|
||||
$base = "../$this->destFolder";
|
||||
foreach($files as $file) {
|
||||
Filesystem::makeFolder("$base/$file[Folder]");
|
||||
|
||||
if(isset($file['Content'])) {
|
||||
$fh = fopen("$base/$file[Folder]$file[Filename]", "w");
|
||||
fwrite($fh, $file['Content']);
|
||||
fclose($fh);
|
||||
} else if(isset($file['Copy'])) {
|
||||
copy($file['Copy'], "$base/$file[Folder]$file[Filename]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
77
code/staticpublisher/StaticPublisher.php
Normal file
77
code/staticpublisher/StaticPublisher.php
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
abstract class StaticPublisher extends DataObjectDecorator {
|
||||
/**
|
||||
* Defines whether to output information about publishing or not. By
|
||||
* default, this is off, and should be turned on when you want debugging
|
||||
* (for example, in a cron task)
|
||||
*/
|
||||
static $echo_progress = false;
|
||||
|
||||
abstract function publishPages($pages);
|
||||
|
||||
static function echo_progress() {
|
||||
return (boolean)self::$echo_progress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Either turns on (boolean true) or off (boolean false) the progress indicators.
|
||||
* @see StaticPublisher::$echo_progress
|
||||
*/
|
||||
static function set_echo_progress($progress) {
|
||||
self::$echo_progress = (boolean)$progress;
|
||||
}
|
||||
|
||||
function onAfterPublish($original) {
|
||||
if($this->owner->hasMethod('pagesAffectedByChanges')) {
|
||||
$urls = $this->owner->pagesAffectedByChanges($original);
|
||||
} else {
|
||||
// $pages = array(Versioned::get_one_by_stage('SiteTree', 'Live', "`SiteTree`.ID = {$this->owner->ID}"));
|
||||
$pages = Versioned::get_by_stage('SiteTree', 'Live', '', '', 10);
|
||||
foreach($pages as $page) {
|
||||
$urls[] = $page->Link();
|
||||
}
|
||||
}
|
||||
|
||||
foreach($urls as $i => $url) {
|
||||
$url = Director::makeRelative($url);
|
||||
if(substr($url,-1) == '/') $url = substr($url,0,-1);
|
||||
$urls[$i] = $url;
|
||||
}
|
||||
|
||||
$urls = array_unique($urls);
|
||||
|
||||
$this->publishPages($urls);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all external references to CSS, JS,
|
||||
*/
|
||||
function externalReferencesFor($content) {
|
||||
$CLI_content = escapeshellarg($content);
|
||||
$tidy = `echo $CLI_content | tidy -numeric -asxhtml`;
|
||||
$tidy = preg_replace('/xmlns="[^"]+"/','', $tidy);
|
||||
$xContent = new SimpleXMLElement($tidy);
|
||||
//Debug::message($xContent->asXML());
|
||||
|
||||
$xlinks = array(
|
||||
"//link[@rel='stylesheet']/@href" => false,
|
||||
"//script/@src" => false,
|
||||
"//img/@src" => false,
|
||||
"//a/@href" => true,
|
||||
);
|
||||
|
||||
$urls = array();
|
||||
foreach($xlinks as $xlink => $assetsOnly) {
|
||||
$matches = $xContent->xpath($xlink);
|
||||
if($matches) foreach($matches as $item) {
|
||||
$url = $item . '';
|
||||
if($assetsOnly && substr($url,0,7) != 'assets/') continue;
|
||||
|
||||
$urls[] = $url;
|
||||
}
|
||||
}
|
||||
|
||||
return $urls;
|
||||
}
|
||||
}
|
@ -69,7 +69,7 @@ PageCommentInterface.prototype = {
|
||||
});
|
||||
}
|
||||
|
||||
if(response.responseText != "spamprotectionfalied"){
|
||||
if(response.responseText != "spamprotectionfailed"){
|
||||
__newComment.className ="even";
|
||||
// Load the response into the new <li>
|
||||
__newComment.innerHTML = response.responseText;
|
||||
|
52
tasks/RebuildStaticCacheTask.php
Normal file
52
tasks/RebuildStaticCacheTask.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @todo Make this use the Task interface once it gets merged back into trunk
|
||||
*/
|
||||
class RebuildStaticCacheTask extends Controller {
|
||||
function init() {
|
||||
if(!Director::is_cli() && !Director::isDev() && !Permission::check("ADMIN")) Security::permissionFailure();
|
||||
parent::init();
|
||||
}
|
||||
|
||||
function index() {
|
||||
StaticPublisher::set_echo_progress(true);
|
||||
|
||||
$page = singleton('Page');
|
||||
if($_GET['urls']) $urls = $_GET['urls'];
|
||||
else $urls = $page->allPagesToCache();
|
||||
|
||||
$this->rebuildCache($urls, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuilds the static cache for the pages passed through via $urls
|
||||
* @param array $urls The URLs of pages to re-fetch and cache.
|
||||
*/
|
||||
function rebuildCache($urls, $removeAll = true) {
|
||||
if(!is_array($urls)) return; // $urls must be an array
|
||||
|
||||
if(!Director::is_cli()) echo "<pre>\n";
|
||||
echo "Rebuilding cache.\nNOTE: Please ensure that this page ends with 'Done!' - if not, then something may have gone wrong.\n\n";
|
||||
|
||||
$page = singleton('Page');
|
||||
|
||||
foreach($urls as $i => $url) {
|
||||
$url = Director::makeRelative($url);
|
||||
if(substr($url,-1) == '/') $url = substr($url,0,-1);
|
||||
$urls[$i] = $url;
|
||||
}
|
||||
$urls = array_unique($urls);
|
||||
|
||||
if($removeAll) {
|
||||
echo "Removing old cache... \n";
|
||||
flush();
|
||||
Filesystem::removeFolder("../cache", true);
|
||||
echo "done.\n\n";
|
||||
}
|
||||
|
||||
echo "Republishing " . sizeof($urls) . " urls...\n\n";
|
||||
$page->publishPages($urls);
|
||||
echo "\n\n== Done! ==";
|
||||
}
|
||||
}
|
@ -1,7 +1,11 @@
|
||||
<div id="PageComments_holder" class="typography">
|
||||
<% if CanPostComment %>
|
||||
<h4><% _t('POSTCOM','Post your comment') %></h4>
|
||||
|
||||
$PostCommentForm
|
||||
<% else %>
|
||||
<p>You can't post comments until you have logged in<% if PostingRequiresPermission %>, and that you have an appropriate permission level<% end_if %>. Please <a href="Security/login?BackURL={$Page.URLSegment}/" title="Login to post a comment">login by clicking here</a>.</p>
|
||||
<% end_if %>
|
||||
|
||||
<h4><% _t('COMMENTS','Comments') %></h4>
|
||||
|
||||
|
35
tests/CMSMainTest.php
Normal file
35
tests/CMSMainTest.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
class CMSMainTest extends SapphireTest {
|
||||
static $fixture_file = 'cms/tests/CMSMainTest.yml';
|
||||
|
||||
/**
|
||||
* @todo Test the results of a publication better
|
||||
*/
|
||||
function testPublish() {
|
||||
$session = new Session(array(
|
||||
'loggedInAs' => $this->idFromFixture('Member', 'admin')
|
||||
));
|
||||
|
||||
$response = Director::test("admin/publishall", array('confirm' => 1), $session);
|
||||
$this->assertContains('Done: Published 4 pages', $response->getBody());
|
||||
|
||||
$response = Director::test("admin/publishitems", array('csvIDs' => '1,2', 'ajax' => 1), $session);
|
||||
$this->assertContains('setNodeTitle(1, \'Page 1\');', $response->getBody());
|
||||
$this->assertContains('setNodeTitle(2, \'Page 2\');', $response->getBody());
|
||||
|
||||
|
||||
|
||||
//$this->assertRegexp('/Done: Published 4 pages/', $response->getBody())
|
||||
|
||||
/*
|
||||
$response = Director::test("admin/publishitems", array(
|
||||
'ID' => ''
|
||||
'Title' => ''
|
||||
'action_publish' => 'Save and publish',
|
||||
), $session);
|
||||
$this->assertRegexp('/Done: Published 4 pages/', $response->getBody())
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
24
tests/CMSMainTest.yml
Normal file
24
tests/CMSMainTest.yml
Normal file
@ -0,0 +1,24 @@
|
||||
Page:
|
||||
page1:
|
||||
Title: Page 1
|
||||
page2:
|
||||
Title: Page 2
|
||||
page3:
|
||||
Title: Page 2
|
||||
page4:
|
||||
Title: Page 2
|
||||
|
||||
Group:
|
||||
admin:
|
||||
Title: Administrators
|
||||
|
||||
Member:
|
||||
admin:
|
||||
Email: admin@example.com
|
||||
Password: ZXXlkwecxz2390232233
|
||||
Groups: =>Group.admin
|
||||
|
||||
Permission:
|
||||
admin:
|
||||
Code: ADMIN
|
||||
GroupID: =>Group.admin
|
Loading…
Reference in New Issue
Block a user