Concrete5 Dynamic select list on user account / add user page - concrete5

I want to add a form select input on the user account page / add user page with a dynamic list of options / values.
I know how to get the dynamic list form a database table and create a dynamic list for the select options.
echo $form->select('clientsID', $indexed_array, '0');
Question: Where and how to add this to the account/add user page and save this to the user table?

If I understand you well you need a User Attribute. It will be saved automatically in the UserInfo and if you set the options correctly it will be displayed on the user account page / add user page.
Note that all the following code should go in a Controller file in /application/controllers or even better inside a package. How to create a package is well documented here. That way your site can be updated without any problems.
Your associative array with dynamic values from a DB table or other sources to be filled in the User Attribute (a Dropdown in this case):
$options = array(
'foo' => 'Foo',
'bar' => 'Bar',
'baz' => 'Baz'
);
Attributes parameters:
$selectArgs = array(
'akHandle' => 'my_select',
'akName' => t('my Select'), // t() is for translation
'uakProfileDisplay' => true, // Will be displayed on the Users Profile page
'uakMemberListDisplay' => true, // Will be displayed on the dashboard members page
'uakProfileEdit' => true, // A memeber is able to edit the attribute
'uakProfileEditRequired' => true, // The attribute MUST be filled with a value
'uakRegisterEdit' => true, // Will be displayed on the Register Page
'uakRegisterEditRequired' => true, // The attribute MUST be filled with a value upon registration
'akIsSearchableIndexed' => true, // The attribute will be indexed (searchable)
'akSelectAllowOtherValues' => false, // A user may add/remove other options to the select attribute
'akSelectOptionDisplayOrder' => 'alpha_asc', // the display order
'akIsSearchable' => true // one can search by these options
);
Classes used:
use \Concrete\Core\Attribute\Type as AttributeType;
use UserAttributeKey;
use Concrete\Attribute\Select\Option;
Define the Attribute Type:
$attrSelect = AttributeType::getByHandle('select');
Check if the Attribute already exists and if not, create it:
$mySelect = UserAttributeKey::getByHandle($selectArgs['akHandle'])
if(!is_object($mySelect)) {
UserAttributeKey::add($attrSelect, $selectArgs);
$mySelect = UserKey::getByHandle($args_address['akHandle']);
}
And finally fill the options into the select:
foreach($options as $value) {
Option::add($mySelect, t($value));
}

Related

Changing Type from Content To Global or VDC Drupal

I'm using Drupal 7 with the view_database_connector module. I'm currently working with a view that consists of a table displaying database information. My goal is to have a field with action buttons corresponding to each row, such as delete.
I am not allowed to use global php as a field.
I've attempted to make a custom module following this. I can currently use this module on content tables, however, when I try to use it on my view_database_connector table, I'm unable to add it as a field, since it's not apart of the same group.
Here's where I set up the information for making the action:
function mymodule_views_data_alter(&$data) {
// debug($data['node']);
$data['node']['actions'] = array(
'title' => t('Actions'),
'help' => t('Clickable links to actions a user may perform on a Node.'),
'field' => array(
'handler' => 'mymodule_views_handler_field_actions',
'group' => 'Content',
'click sortable' => FALSE,
),
);
}
I've tried deleting Content, changing it to global, and changing it to the VDC type, but none of that changes it into Global or VDC.
Alternatively, if there's an easier way just to hook into a field that has a button to run my code which will download a file, that could circumvent this issue.
You don't have to create this feature on your own - it already exists in views. There is a field type with actions as delete.
Other option is to use option called "rewrite value of this field" (or similar). With it you can make your field from views interface, and totally rewrite it's output. You can use token and some of them for sure contains node id, so you can use it to generate link to node deleting/editing/viewing pages.

Render a field multiple times in Twig and Symfony

I need two inputs so that the user can choose from
Controller
$etud = new Etudiant();
$form=$this->createFormBuilder($etud)
->add('filierechoisit',EntityType::class,array('class'=>'inscriptionBundle\Entity\filieres', 'choice_label'=>'libelle_filiere'))
->add('filierechoisit',EntityType::class,array('class'=>'inscriptionBundle\Entity\filieres', 'choice_label'=>'libelle_filiere'))->getForm();
if ($form->isValid()) {
// ... maybe do some form processing, like saving the Task and Tag objects
}
return $this->render('inscriptionBundle:Default:authentification.html.twig', array(
'modif' => $form->createView(),
));
How can I do it?
I'm almost sure you want a ChoiceType/EntityType field with multiple and expanded options as true.
It should be something like this:
$form->add('filierechoisit', EntityType::class, array(
# query choices from this entity
'class' => 'inscriptionBundle\Entity\filieres',
# use the filieres.libelle_filiere property as the visible option string
'choice_label' => 'libelle_filiere',
# used to render a select box, check boxes or radios
'multiple' => true,
'expanded' => true,
));
You are mixing the form processing and form rendering. If you want user to choose which way he enters data - you do not want to process this data in two different ways until these are two independent fields.
You should just have one
->add('filierechoisit',EntityType::class,array('class'=>'inscriptionBundle\Entity\filieres', 'choice_label'=>'libelle_filiere'))
call to add field processing and left all rendering for the front-end side. You could use some JS, or API there, or in simple case, just override Twig template for that field
http://symfony.com/doc/current/cookbook/form/form_customization.html
You could render your own widget for your form here, allowing user to do some html stuff to change input.
Currently, making two add with identical names call just makes the second one override the first.

Sonata how make autocomplete filter on current entity property

I'm working on a sf2 project using Sonata Admin Bundle.
The project is a Donations website for humanitarian mission.
I have a 'Personne' entity, represent benefactors (donation makers).
My problem is the following:
I have to filter results in the sonata list view using autocompletion.
I want filter results using the 'name' property of the current entity ('Personne').
What I'm expecting :
$datagridMapper
->add('personne', 'doctrine_orm_model_autocomplete',
array('label' => 'AutoComplete'),
null,
array('property' => 'name'))
// error output : " The option `association_mapping` must be set for field: `personne` "
You can see my full admin class and entity on this gist :
https://gist.github.com/chalasr/0658a02b1c04180f5563
I understand this field type is reserved to entity associations (by example I already use it for filter results of my Donation entity by Personne name (other admin class).
My question is :
Is it possible to do what I need ?
If I can't do that using this field type, what is the right way to achieve this task ?
Thank's for your help.
After a lot of tests, it seems this functionality isn't yet provided by Sonata.
So, I had build an homemade autocomplete method in my admin controller and used it as ajax in my overridden CRUD:list.html.twig template.
This method takes field name, other autocomplete fields values, and keyword as parameters and reload results on keyup event.
You can look on this gist:
https://gist.github.com/chalasr/5c27ae64dc596967f18a
If you have an idea/proposition for optimize my code (simple autocomplete field type for $formMapper ?), I'm really interested.
Well it's possible to make a choice field and use integrated select2 to handle autocomplete, I don't know how well it would fare with large tables though.
$datagridMapper
->add('personne','doctrine_orm_callback', array(
'callback' => array($this, 'filterByName'),
'field_type' => 'text',
), 'entity',array(
'class' => 'AppBundle\Entity\Personne',
'choice_label' => 'name'
))
We add 'doctrine_orm_callback' because regular string filter can't handle EntityType Field, so we need to do it ourselves.
public function filterByName($queryBuilder, $alias, $field, $value)
{
if (!$value['value']) {
return;
}
$queryBuilder
->andWhere($alias . '.name' . ' = ' . ':name' )
->setParameter('name' , $value['value']->getName());
return true;
}
1 more thing, select2 won't create autocomplete (search) box if dropdown box has less than 10 choices, because it's set up in Admin.js that way.
select.select2({
width: function(){
// Select2 v3 and v4 BC. If window.Select2 is defined, then the v3 is installed.
// NEXT_MAJOR: Remove Select2 v3 support.
return Admin.get_select2_width(window.Select2 ? this.element : jQuery(this));
},
dropdownAutoWidth: true,
minimumResultsForSearch: 10,
allowClear: allowClearEnabled
});
so you need to override it, if you want that to be less.
Fonctionnality is now bundled with Sonata as simple as:
$filter->add('label', ModelFilter::class, ['field_type'=>ModelAutocompleteType::class,'field_options' => ['property' => 'name']])

Silverstripe tumblr-like Post Types

I am trying to create a back-end interface for silverstripe that gives the CMS user the option to choose between a set of Post Types (like tumblr) in Silverstripe3. So they can choose to create a News Post, Video Post, Gallery Post, etc.
I initially started off giving all Posts the necessary fields for each Type and adding an enum field that allowed the user to choose the Post Type. I then used the forTemplate method to set the template dependent upon which Post Type was chosen.
class Post extends DataObject {
static $db = array(
'Title' => 'Varchar(255),
'Entry' => 'HTMLText',
'Type' => 'enum('Video, Photo, Gallery, Music')
);
static $many_many = array(
'Videos' => 'SiteVideo',
'Photos' => 'SitePhoto,
'Songs' => 'SiteMp3'
);
public function forTemplate() {
switch ($this->Type) {
case 'Video':
return $this->renderWith('VideoPost');
break;
case 'Photo':
return $this->renderWith('ImagePost');
break;
etc...
}
function getCMSFields($params=null) {
$fields = parent::getCMSFields($params);
...
$videosField = new GridField(
'Videos',
'Videos',
$this->Videos()->sort('SortOrder'),
$gridFieldConfig
);
$fields->addFieldToTab('Root.Videos', $photosField);
$photosField = new GridField(
'Photos',
'Photos',
$this->Photos()->sort('SortOrder'),
$gridFieldConfig
);
$fields->addFieldToTab('Root.Videos', $photosField);
return $fields;
}
}
I would rather the user be able to choose the Post Type in the backend and only the appropriate tabs show up. So if you choose Video, only the Video GridField tab would show up. If you choose Photo Type only the Photo's GridField would show.Then I would like to be able to call something like
public function PostList() {
Posts::get()
}
and be able to output all PostTypes sorted by date.
Does anyone know how this might be accomplished? Thanks.
Well the first part can be accomplished using javascript. Check out this tutorial and the docs let me know if you have questions on it.
The second part would be trickier but I think you could do something with the page controller. Include a method that outputs a different template based on the enum value but you would have to set links somewhere.
I managed this with DataObjectManager in 2.4.7 as I had numerous DataObjects and all were included in one page but I'm not sure if that is feasible in SS3.
return $this->renderWith(array('CustomTemplate'));
This line of code will output the page using a different template. You need to include it in a method and then call that method when the appropriate link is clicked.

Create menu link to view with argument

I'm trying to use hook_menu to create a link to a view which takes an argument. However if I use the path (in $items[view-path/%dest]) that I've already set as the path in the view then the link doesn't appear. I'm guessing there's a path conflict somewhere. Is there a way round this? Or can I use another method to return the view?
I'm using the following code:
/**
* implementation of hook_menu().
*/
function sign_custom_menu() {
$items['view-path/%dest'] = array(
'title' => 'Link to view',
'page callback' => 'sign_custom_hello',
'page arguments' => array(1), //(corrected typo from 'page arguements')
'access callback' => TRUE,
'type' => MENU_NORMAL_ITEM,
'menu_name' => 'menu-student-links',
);
return $items;
}
function dest_to_arg() {
// would normally be dynamic to get view with correct argument
$arg = 73;
return $arg;
}
Thanks in advance.
Addition
function sign_custom_hello() {
//return t('Hello!');
}
I managed to answer my problem. Basically I used a different path to the one I had set in the view and then used views_page() as my "page callback". I passed it the arguments for the view, the page ID and it's own additional arguments to make the view work. I was able to use a wildcard in the menu item to pass to views_page() by using the to_arg() function that works with hook_menu() to pass in wildcards. The 'page arguments' pass in the three arguments. The last argument, "1" is a reference to which position in the path the argument appears (starting from 0).
The working code is:
<?php
/**
* implementation of hook_menu().
*/
function sign_custom_menu() {
$items['view-path/%dest'] = array(
'title' => 'link to view',
'page callback' => 'views_page',
'page arguments' => array('view_name', 'page_1', 1),
'access callback' => TRUE,
'type' => MENU_NORMAL_ITEM,
'menu_name' => 'menu-student-links',
);
return $items;
}
//this function is needed from the "%dest" argument in hook_menu above
function dest_to_arg() {
// would normally be dynamic to get view with correct argument
$arg = 73;
return $arg;
}
?>
I don't have much experience with wildcard URLs in my custom modules much, but I researched the issue in the Pro Drupal Development book. From what I read in the "Wildcards and Parameter Replacement" section on page 77, I think you may want to use $items['view-path/%'] instead. Using %dest apparently makes drupal look for a dest_load function.
Items that appear in menus can't be created by a wildcard router item: each menu item corresponds to exactly one path. That is, if you have a router item that is foo/%bar and %bar can have 10 different values, Drupal's menu system isn't going to create 10 new menu items off of one router item definition.
So what you're going to need to do is create a router item for each possible argument ahead of time. Otherwise, you're going to have to look outside Drupal's menu system and think about creating a separate Views block that looks like a menu but is really a Views unordered list of the available options.
To do the former, you need to implement hook_menu_alter() to add your custom router item after everything else, including the wildcard router item you're trying to override. Your custom router item will be more or less the same as the wildcard router item, but with some defaults set that would normally be derived from the wildcard.
For example, if I wanted to create a new router item for user/1/edit which overrides the built-in user/%user_category/edit, I'd implement hook_menu_alter() as so:
function mymodule_menu_alter(&$items) {
// user_edit and user_edit_access expect a user object
$account = user_load(array('uid' => 1));
$items['user/1/edit'] = array(
'type' => MENU_CALLBACK,
'page arguments' => array($account),
'access arguments' => array($account),
) + $items['user/%user_category/edit'];
}
In this example, user/%user_category/edit calls user_edit() and user_edit_access() for the page and access callbacks, respectively, and they both attempt to use the wildcard. Since there is no wildcard in your router item, you need override the arguments to say "check user 1".
You'll do this for each and every possible value of the wildcard.
But this isn't enough: notice I used MENU_CALLBACK instead of MENU_NORMAL_ITEM. If you use MENU_NORMAL_ITEM, your router item is going to show up in the Navigation menu, not in your custom menu, even if you set menu_name (I don't know why this is: it should work). But you can get around this by using menu_link_save().
Consider this implementation of hook_init():
function mymodule_init() {
$router_path = 'user/1/edit';
// Check to see if the custom router item has been added to menu_links.
// This is to ensure the menu has already been rebuilt.
$router_item = db_fetch_object(db_query("SELECT * FROM {menu_links} WHERE router_path = '%s'", $router_path));
// Only create a new menu item if the router item exists and it
// hasn't already been created (it's hidden until created).
if ($router_item && $router_item->hidden) {
$item = array(
'link_title' => 'Edit Administrator',
'link_path' => $router_path,
'menu_name' => 'primary-links',
'router_path' => $router_path,
'mlid' => $router_item->mlid,
);
// Save the menu item.
menu_link_save($item);
}
}
In this implementation, it checks to see if the custom router has already been created and hasn't been otherwise modified. If this is true, it creates a menu link in the primary links menu that references your custom router item.
Obviously, since this is in hook_init(), it'll perform the check on every page: this is to ensure it fires after the menu is rebuilt. It shouldn't be much of a performance hit, but it's something to keep in mind.
As you can see, it's a long and drawn out process to do this: if you're not going to go the Views fake-menu route, it might be better to just manually create the links yourself.

Resources