SilverStripe Subsite module delete all related data on subsite deletion - silverstripe

I m using Silverstripe subsite module. When Subsite is deleted I want to delete all other information related to Subsite as well, like, Domains, Settings etc. I have created an extension that'll extend Subsite model.
<?php
class SubsiteExtension extends DataExtension {
public function onAfterWrite(){
parent::onAfterWrite();
//Some codes here
}
public function onBeforeDelete(){
//Check if member exist for Subsite, if so show warning.
}
public function onAfterDelete(){
$id = $this->owner->ID;
//DELETE ALL SUBDOMAINS RELATED TO DELETED SUBSITE
DB::query("DELETE FROM SubsiteDomain WHERE SubsiteID='".$id."'");
//DELETE SITE CONFIG
DB::query("DELETE FROM SiteConfig WHERE SubsiteID='".$id."'");
}
}
Problem
The code works flawlessly. Just wondering is there any other better way of deleting related records from other tables??
On method onBeforeDelete , how to show custom message saying "You can't delete this subsite unless you delete all the members" ?

Using onAfterDelete to delete related records is perfectly fine, although it really sounds a lot like you're doing unnecessary work here. Who's going to be bothered by some stray DB entries?
Regarding your onBeforeDelete approach: I would solve it otherwise. Override canDelete in your extension instead, something like this:
public function canDelete($member)
{
if( /* check if member exist for Subsite */ ){
return false;
}
// returning null here means that this extension doesn't influence
// the delete permission at this point
return null;
}
This will prevent deleting of the record in the CMS. Additionally, you could use updateCMSFields to display a notice to the user why he can't delete the record.
public function updateCMSFields(FieldList $fields)
{
if (!$this->owner->canDelete()) {
$fields->addFieldToTab(
'Root.Main',
LiteralField::create('_deleteInfo', 'Your info text')
);
}
}

Related

Silverstripe subsite module, how to make subsite specific members?

I wanted to white label silverstripe CMS, i.e. one codebase serves different domains and each domains have their own members, etc. I have asked this question Here. And I was suggested to use Subsite modules.
However this partially solved my problem (I m very new to SilverStripe and official community isn't that active).
I was able to make my custom modules Subsite specific by using following code in my ModelAdmin
<?php
class CompaniesAdmin extends ModelAdmin {
private static $url_segment = 'Companies';
private static $managed_models = "Company";
private static $menu_title = 'Companies';
private static $menu_icon = 'mysite/images/icons/company-icon.png';
public function getEditForm($id = null, $fields = null){
$form = parent::getEditForm($id, $fields);
$gridField = $form->Fields()->fieldByName($this->sanitiseClassName($this->modelClass));
if(class_exists('Subsite')){
$list = $gridField->getList()->filter(array('SubsiteID'=>Subsite::currentSubsiteID()));
$gridField->setList($list);
}
return $form;
}
}
?>
Everything works as expected, except the "Security" Module. My requirement is even membership should be subsite specific. That means members from one subsite is not visible in another subsite, they can't login to another subsite, etc.
I also visited this post by another user. However the suggested solution is not implementable in my scenario.
My Questions
Is it possible to extend subsite module so that we can have subsite
specific Members??
Can we do without modifying core files?
I was able to add hidden field for SubsiteID while adding/editing Members and was able to save member with specific SubsiteID.
How to override Security module, so that I can list/filter subsite specific Members?
How to prevent editing of other Subsite's member if someone provides member id in URL querystring?
How to prevent member of one subsite login to another subsite?
Any help is highly appreciated.
UPDATE
I tried following injector, but didn't work, got an error Fatal error: Call to a member function getList() on a non-object in...
<?php
class CustomSecurityAdmin extends SecurityAdmin {
public function getEditForm($id = null, $fields = null){
$form = parent::getEditForm($id, $fields);
$gridField = $form->Fields()->fieldByName('Member');
if(class_exists('Subsite')){
$list = $gridField->getList()->filter(array('SubsiteID'=>Subsite::currentSubsiteID()));
$gridField->setList($list);
}
return $form;
}
}
?>
And in _config.yml
Injector:
SecurityAdmin:
class: CustomSecurityAdmin
UPDATE 2
My injector for MemberAuthenticator class
<?php
class CustomMemberAuthenticator extends MemberAuthenticator {
public static function authenticate($RAW_data, Form $form = null) {
//add logic before
//get Subsite ID
$Subsite = SubsiteDomain::get()->filter('Domain', $_SERVER["HTTP_HOST"])->First();
if($Subsite){
$SubsiteID = $Subsite->SubsiteID;
}else{
$SubsiteID = 0;
}
$email = Convert::raw2sql($RAW_data['Email']);
$member = Member::get()->filter(array(
"Email" => $email,
"SubsiteID" => $SubsiteID
))->First();
if(!$member){
if($form) $form->sessionMessage("Invalid User", 'bad');
}else{
parent::authenticate($RAW_data,$form);
}
}
}
?>
And _config.yml
Injector:
MemberAuthenticator:
class: CustomMemberAuthenticator
But this didn't work the injector is not working at all
Is it possible to extend subsite module so that we can have subsite specific Members??
Yes
Can we do without modifying core files?
How to override Security module, so that I can list/filter subsite specific Members?
How to prevent member of one subsite login to another subsite?
The two classes you are probably most interested in are SecurityAdmin and MemberAuthenticator.
All silverstripe core files can be "modified" in some way... of the methods discussed on this video presentation by StripeCon - Loz Calver - Why you shouldn’t edit SilverStripe core files (and how to do it anyway) - I recommend method 3, fork the code and add this custom security to your version of silverstripe.
For SecurityAdmin the best option is to simply removing the current SecurityAdmin from the menu and adding your own custom class:
in _config.php
CMSMenu::remove_menu_item('SecurityAdmin ');
How to prevent editing of other Subsite's member if someone provides member id in URL querystring?
You could determine if the user is allowed to edit the form based on which site they belong to... either in permissions or simply use updateCMSFields to remove all fields and a validator to ensure nothing can be submitted that doesn't match your rules.
public function updateCMSFields(FieldList $fields) {
if (<not valid user to edit>) $fields = FieldList::create();
...
}
Here is another question about how to add a validator and here are the docs for that.
There is another hack for this, which flawlessly worked for me.
/mysite/extensions/CustomLeftAndMain.php
<?php
class CustomLeftAndMain extends Extension {
public function onAfterInit() {
self::handleUser();
}
public static function handleUser(){
$currentSubsiteID = Subsite::currentSubsiteID();
$member = Member::currentUser();
$memberBelongsToSubsite = $member->SubsiteID;
if($memberBelongsToSubsite>0 && $currentSubsiteID!=$memberBelongsToSubsite){
Security::logout(false);
Controller::curr()->redirect("/Security/login/?_c=1001");
}
}
}
and in /mysite/_config.php add an extension
LeftAndMain::add_extension('CustomLeftAndMain');
What above code basically does is, the system lets user login no matter which subsite they belong to. And as long as application is initiated, we'll check whether the logged in user belongs to current website or not (method handleUser does it.).
If user doesnot belong to current site, then they are logged out and then redirected to the login page.

Sonata Admin - how to set the menu.label attribute?

According to the Sonata source code, the last node in the breadcrumb is rendered this way:
# standard_layout.html.twig #
<li class="active"><span>{{ menu.label }}</span></li>
In my setup, when opening a given Admin subclass, the last node simply becomes a raw string according to the entity handled by the Admin:
Dashboard / Entity List / Acme\SomeBundle\Entity\Stuff:000000001d74ac0a00007ff2930a326f
How can I set the value of menu.label to get something more appropriate? I have tried, in my Admin subclass, to override the following:
protected function configureTabMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null) {
$this->configureSideMenu($menu, $action, $childAdmin);
}
protected function configureSideMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null) {
$menu->setLabel("Some nice label");
$menu->setName("Some nice name");
}
However, this does not change anything, even though I have verified that the methods above are called during runtime.
Finally found a good (and somewhat obvious) solution to this.
The Sonata Admin class uses an internal toString($object) method in order to get a label string for the entity it is handling. Thus, the key is to implement the __toString() method of the entity in question:
public function __toString() {
return "test";
}
The best way is to configure the $classnameLabel variable in the Admin Class :
class fooAdmin extends Admin
{
protected $classnameLabel = 'Custom Label';
}
But it have the same issue (weird name with entity path) doing it, even if it is working fine on all the others pages.
Apparently, the Sonata way of solving this is show here:
Quote:
While it’s very friendly of the SonataAdminBundle to notify the admin of a successful creation, the classname and some sort of hash aren’t really nice to read. This is the default string representation of an object in the SonataAdminBundle. You can change it by defining a toString() (note: no underscore prefix) method in the Admin class. This receives the object to transform to a string as the first parameter:
Source: https://sonata-project.org/bundles/admin/master/doc/getting_started/the_form_view.html#creating-a-blog-post

Hide cck field based on role

Im looking for a way to hide a cck field for every one except for one specific role.
I know that there is a module, Content Permission module, that takes good care of this. But I have taken over a very big site with many content types, with lots of related cck fields being defined. So installing Content Permission module is not a good idea because of the great amount of settings it would require.
It's a drupal 6 installation.
You may use hook_nodeapi in a custom module:
/**
* Implements hook_nodeapi().
*/
function yourmodule_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
switch ($op) {
case 'view':
if (! user_access('show restricted content')) {
unset ($node->content['field_restrictedcontent']);
}
break;
}
}
/**
* Implements hook_perm().
*/
function yourmodule_perm () {
return array(
'show restricted content',
);
}
Nevertheless, be aware that this is somewhat a hack: I think you should reconsider using Content Permission module for your site, and make the effort needed to configure it for your node types. It's a one time job and it may protect you from compatibilities issues with other modules in your site.
You need to use any of the permissions module and reconfigure each of those fields in question. With code, you have to check user roles for each of those fields!

Is there a way in Drupal 7 to prevent the administrator from editing a node?

I have a module that creates (and updates) Drupal 7 nodes programmatically.
Since the content of the body these nodes is changed by a program at random intervals I do not want anyone, including the administrator, to be able to edit them. Is there a way way to completely "turn-off" the interface that allows a administrator to edit a node?
If it's a standard user with an administrator role you can implement hook_node_access() in your custom module:
function MYMODULE_node_access($node, $op, $account) {
$type = is_string($node) ? $node : $node->type;
if ($type == 'the_type' && $op == 'update') {
return NODE_ACCESS_DENY;
}
return NODE_ACCESS_IGNORE;
}
If it's the 'super user' (user 1) you need to get a bit more creative as a lot of access checks are bypassed for that user.
You can implement hook_menu_alter() to override the access callback for the node edit page, and provide your own instead:
function MYMODULE_menu_alter(&$items) {
$items['node/%node/edit']['access callback'] = 'MYMODULE_node_edit_form_access';
}
function MYMODULE_node_edit_form_access($node) {
$type = is_string($node) ? $node : $node->type;
if ($type == 'my_type') {
return FALSE;
}
return node_access('update', $node);
}
I like both of Clive's suggestions, but one more option is to simply disable the fields using HOOK_form_alter. This will work for the User 1 account too. I've used this recently to disable a specific field that I don't want anyone modifying.
function YOURMODULE_form_alter(&$form, &$form_state, $form_id) {
if ($form_id == 'your_form_id') {
$form['body_field']['body']['#disabled'] = TRUE;
$form['body_field']['body']['#value'] = $form['body_field']['body']['#default_value'];
}
}
Admittedly, this solution isn't ideal if you're using the built-in body field because of the teaser. But it works great if you want to disable editing of certain fields while leaving other aspects of the node editable and the page intact.
I'm not sure why you need them to be an administrator. That role, honestly, should be reserved for people with absolute control -- and even those people should not use it as their "main" account due to the potential for destroying things. Why not just make an "editor" role or something similar, and give all the permissions you need?

Override user_login_submit in Drupal 6 to redirect an authenticated user

I would like to redirect a user that logged in over the user login block. What I have is a module that contains the following code:
Appendix, 3.9.2011, 15:30h: Changed code according to the advice of kiamlaluno.
Appendix, 3.9.2011, 17:08h: Small Fix: Changed node/admin to admin.
Appendix, 3.9.2011, 17:24h: removed []
-> code is working like this now, but do not forget to change the module priority in DB.
function _MYMODULE_user_login_submit($form, &$form_state) {
global $user;
if ($user->uid == 1) {
$form_state['redirect'] = 'admin';
} elseif ($user->uid) {
$form_state['redirect'] = 'node/add/image';
return;
}
}
/**
* Modifies the outfit and behaviour of the user login block.
*/
function MYMODULE_form_user_login_block_alter(&$form, $form_state) {
unset($form['#action']);
// removes the forgot password and register links
$form['links'] = array();
// Redirects the user to the image upload page after login
// This cannot be done by a rule, the rule based redirect is only
// working for the login page not the user login block.
$form['#submit'] = array('_MYMODULE_user_login_submit');
}
It doesn't redirect users; it seems like _MYMODULE_user_login_submit() is simply ignored.
What I know already:
I cannot use Rules/Triggers, because I do not login over the login page but the user login block
It is always said: "use logintoboggan" on posts, but there I only have redirection options for "on registration" and "on confirmation", but I need "on authentication" or "after login".
Anyway, I do not want to use more modules, I prefer a few lines of PHP.
Your code doesn't work because user_block_login() sets the "#action" property for the form; in that case, redirecting the form after submission doesn't work.
$form = array(
'#action' => url($_GET['q'], array('query' => drupal_get_destination())),
'#id' => 'user-login-form',
'#validate' => user_login_default_validators(),
'#submit' => array('user_login_submit'),
);
To make it work, you should first unset $form[#action], and then executing the code you already execute in your hook_form_alter() implementation.
As side notes, I will add:
If you want to be sure your code effectively redirect the user where you want, be sure your module is executed for last; if any other module that implements hook_form_alter() adds a form submission handler to redirect the user to a different page, and that module is executed after yours, then your module would not have any effect. To make sure your module is executed after the others, you should use code similar to the following during the installation of the module, or in an update hook. (Replace "MYMODULE" with the short name of the module.)
db_query("UPDATE {system} SET weight = 100 WHERE name = 'MYMODULE');
Instead of using MYMODULE_form_alter(), you can use `MYMODULE_form_user_login_block_alter(), which would not require to check the form ID.
You should append new form submission handlers, instead of replacing the existing ones. This means you should use $form['#submit'][] = 'user_login_submit_redirected';.
Functions implemented in a module should be prefixed with the short name of the module, which means "MYMODULE_" or "_MYMODULE_" (the latter is for private functions). Not using such prefix could create a compatibility issue with other module, such as the User module, as the function you are using has a name starting with "user_."
can u try this please
function user_login_submit_redirected($form, &$form_state) {
global $user;
if ($user->uid == 0) {
$form_state['redirect'] = 'node/admin';
drupal_goto('node/admin') ;
} elseif ($user->uid) {
$form_state['redirect'] = 'node/add/image';
drupal_goto('node/add/image') ;
return;
}
}

Resources