Adding custom field to User programmatically through liferay.expando - liferay

I am trying to add fields to com.liferay.portal.model.User, an extra attribute using Expando. Can someone explain to me how this method is adding a field because docs don't have much description.
private void addUserCustomAttribute(long companyId, ExpandoTable userExpandoTable, String attributeName, int type) throws PortalException, SystemException {
ExpandoColumnLocalServiceUtil.getColumn(userExpandoTable.getTableId(), attributeName); //should be addColumn(long tableId, String name, int type) ???
} //and where can find type description couse i have very specific type, Map(String,Object) couse in ExpandoColumnConstants didn't see it
I have taken this from Liferay Expando Wiki's Adding User Custom Attributes.
When should I call this all? Where to put this in my project? What change is required or everything needs to be changed to call it.
Some good tutorial will be nice because it's hard to find something from 0 to end, always found only some part with no explanation.

The question is not very clear. But if you simply want to add a custom attribute for your User then you can refer to my answer here and reproduced for your reference:
Custom field for the user-entity can be created through:
Control Panel -> Portal -> Custom Fields -> User.
And programmatically can be created as follows:
user.getExpandoBridge().addAttribute("yourCustomFieldKey");
Then set the value as:
user.getExpandoBridge().setAttribute("yourCustomFieldKey", "valueForCustomField");
If your custom field is already present you can check like this:
if (user.getExpandoBridge().hasAttribute("yourCustomFieldKey")) { ... };
The data is stored in tables prefixed with "EXPANDO":
EXPANDOCOLUMN: stores the custom field key and other settings
(contains the tableId refrences)
EXPANDODATA: stores the custom field value for the key (contains the
columnId and tableId refrences)
EXPANDOTABLE: stores for which liferay entity (user) are you adding
the custom field
EXPANDOROW: stores linking information between a user and its values
(contains tableId and userId refrences)
Hope this helps.

If your custom field is multivalue, you can use this:
String customVal = "yourCustomFieldValue";
user.getExpandoBridge().setAttribute("yourCustomFieldKey", new String[] {customVal }, false);
The last parameter set to "false" avoids permission check.

Related

How to extend the core customer table?

I created a custom table with additional settings for customers. Next I added a field to the customer core table in which I'd like to store the id choice per customer. I extended with EntityExtensionInterface the customerDefinition :
public function extendFields(FieldCollection $collection): void
{
$collection->add(
(new OneToOneAssociationField(
'customerSetting',
'customer_setting',
'id',
WdtCustomerSettingsDefinition::class,
true
))->addFlags(new Inherited())
);
}
public function getDefinitionClass(): string
{
return CustomerDefinition::class;
}
When I manually manipulate the customer table, with an id from my custom table in the added field, I can retrieve and use the settings from my custom table in the storefront.
For the backend I created a single select to the entity custom_table,
<sw-entity-single-select entity="wdt_customer_settings" v-model="customer.extensions.customerSetting.id" >
</sw-entity-single-select>
and with the manually 'injected' id from the custom table, this choice indicates indeed 'selected' However, after changing to another choice and saving results in an error: Customer could not be saved.
What am I missing?
You should look always to the ajax responses. There is the explict error which is occured. Do you added some boilerplate code to check that your extensions is always available? Otherwise it would cause issues on new entities

Orchard CMS front-end all possible content filtering by user permissions

Good day!
In my Orchard, I have several content types all with my custom part attached. This part defines to what users this content is available. For each logged user there is external service, which defines what content user can or cannot access. Now I need access restriction to apply everywhere where orchard display content lists, this includes results by specific tag from a tag cloud, or results listed from Taxonomy term. I seems can’t find any good way to do it except modifying TaxonomyServices code as well as TagCloud services, to join also my part and filter by it. Is this indeed the only way to do it or there are other solutions? I would like to avoid doing changes to built-in modules if possible but cannot find other way.
Thanks in advance.
I'm currently bumbling around with the same issue. One way I'm currently looking at is to hook into the content manager.
[OrchardSuppressDependency("Orchard.ContentManagement.DefaultContentManager")]
public class ModContentManager : DefaultContentManager, IContentManager
{
//private readonly Lazy<IShapeFactory> _shapeFactory;
private readonly IModAuthContext _modAuthContext;
public ModContentManager(IComponentContext context,
IRepository<ContentTypeRecord> contentTypeRepository,
IRepository<ContentItemRecord> contentItemRepository,
IRepository<ContentItemVersionRecord> contentItemVersionRepository,
IContentDefinitionManager contentDefinitionManager,
ICacheManager cacheManager,
Func<IContentManagerSession> contentManagerSession,
Lazy<IContentDisplay> contentDisplay,
Lazy<ISessionLocator> sessionLocator,
Lazy<IEnumerable<IContentHandler>> handlers,
Lazy<IEnumerable<IIdentityResolverSelector>> identityResolverSelectors,
Lazy<IEnumerable<ISqlStatementProvider>> sqlStatementProviders,
ShellSettings shellSettings,
ISignals signals,
//Lazy<IShapeFactory> shapeFactory,
IModAuthContext modAuthContext)
: base(context,
contentTypeRepository,
contentItemRepository,
contentItemVersionRepository,
contentDefinitionManager,
cacheManager,
contentManagerSession,
contentDisplay,
sessionLocator,
handlers,
identityResolverSelectors,
sqlStatementProviders,
shellSettings,
signals) {
//_shapeFactory = shapeFactory;
_modAuthContext = modAuthContext;
}
public new dynamic BuildDisplay(IContent content, string displayType = "", string groupId = "") {
// So you could do something like...
// var myPart = content.As<MyAuthoPart>();
// if(!myPart.IsUserAuthorized)...
// then display something else or display nothing (I think returning null works for this but
//don't quote me on that. Can always return a random empty shape)
// else return base.BuildDisplay(content, displayType, groupId);
// ever want to display a shape based on the name...
//dynamic shapes = _shapeFactory.Value;
}
}
}
Could also hook into the IAuthorizationServiceEventHandler, which is activated before in the main ItemController and do a check to see if you are rendering a projection or taxonomy list set a value to tell your content manager to perform checks else just let them through. Might help :)

Relation between Retieve AND Update in a Dynamic CRM PLUGIN

Is there a relation between Retrieve and Update in a Dynamic CRM Plugin?
For example if I am retrieving only one field:
Entity e = (Entity)service.Retrieve("EntityLogicalName", EntityGuid,
new ColumnSet(new string[] {"entityid"}));
Can I update another field in the Entity e that has NOT been retrieved?
For example:
e.Attributes["AnotherEntityField1] = "test1";
e.Attributes["AnotherEntityField2] = "test2";
service.update(e);
By NOT including all fields that to be updated in the Retrieve, may this cause some hidden issues?
Assuming, as it appears, that you are just retrieving the entity's primary key, entityid, you won't need to do the retrieve.
Entity e = new Entity("EntityLogicalName") { Id = EntityGuid };
e.Attributes.Add("AnotherEntityField1", "test1");
e.Attributes.Add("AnotherEntityField2", "test2");
service.Update(e);
If you are doing a retrieve to confirm the record exists you need to try/catch or use a retrieve multiple since a Retrieve will throw an exception if the record does not exist.
What you are trying to do is perfectly acceptable and won't cause any problems. Since you obtained the Entity instance via a Retrieve operation the required LogicalName and Id will be set correctly for an update.
Your code would need to read as below for adding new attributes not retrieved initially otherwise you'll get a KeyNotFoundException as the Entity type is just a wrapper over Dictionary<string,string>.
e.Attributes.Add("AnotherEntityField2","test2");
When you're trying to update an entity you don't need that an field exists in the attributes colletion, but to avoid the exception "The given key is not presented in the dictionary" is a good practise to check first if the Attributes Colletion contains the field you want to updated. If yes just update it otherwise you have to add it to the Attributes Colletion of the entity.
if(e.Attributes.Contains("AnotherEntityField1"))
{
e.Attributes["AnotherEntityField1"] = "test1";
}
else
{
e.Attributes.Add("AnotherEntityField1", "test1");
}
// now update operation

How to specify a column Name for EF5 navigation property

I'm using EF5 code first to generate my database schema, but my new navigation property is being named in an undesirable way in the table. here is the model I'm working with.
public class User
{
[Key]
public long UserId { get; set; }
...
**public virtual ICollection<ImagePermission> KeepThisNavigationName { get; set; }**
}
However, After I've updated my database and examine the table columns, the column is named:
dbo.ImagePermission.User_UserId
And I would like it to be named
dbo.ImagePermission.KeepThisNavigationName_UserId
I believe there is a way to do this using the Fluent API, but after many failed attempts, I can't get the desired outcome.
P.s. The 'ImagePermission' Entity is currently still in development, so I would prefer to drop the migration which creates this table so I can create this column name correctly during the table create, rather than having additional code to update the column name.
Many thanks, Oliver
The correct mapping with Fluent API would be:
modelBuilder.Entity<User>()
.HasMany(u => u.KeepThisNavigationName)
.WithOptional() // or WithRequired()
.Map(m => m.MapKey("KeepThisNavigationName_UserId"));
If you have a navigation property in ImagePermission refering to User you need to use WithOptional(i => i.User) (or WithRequired(i => i.User)) instead of the parameterless version.

Get old data in preUpdate Sonata Admin Bundle

I have a product entity and it has an images field that store the images names from the product but the images names depends of a part_number field that is unique, so if the user make a mistake in the part number and he wants to edit it then I also have to change the images names
I tried this but it does not works:
// class ProductsAdmin extends Admin
public function preUpdate($product) {
$old_product = $this->getSubject();
if ($old_product->getPartNumber() != $product->getPartNumber)
{
// change file names
}
$this->saveFile($product);
}
How I get the original row in preUpdate() function?
According to the topic taken from the official SonataAdmin google forum:
https://groups.google.com/forum/#!topic/sonata-devs/0zML6N13i3U
you need to make use of the class UnitOfWork:
http://www.doctrine-project.org/api/orm/2.3/class-Doctrine.ORM.UnitOfWork.html
Do this way:
public function preUpdate($object)
{
$em = $this->getModelManager()->getEntityManager($this->getClass());
$original = $em->getUnitOfWork()->getOriginalDocumentData($object);
}
Thus you get an array of values of your database entity.
E.g: to get access to the value password of your entity do:
$password = $original['password'];
That's all.
Enjoy :)
If you just do a doctrine query in the preUpdate function to get the product from the database you'll have the old object. Then do the comparison and you're good to go.

Resources