working on asset definition
This commit is contained in:
parent
1bd76f9442
commit
6be6aee8c1
4 changed files with 244 additions and 283 deletions
|
|
@ -4,6 +4,7 @@ WebGUI 8 Migration Guide
|
|||
The information contained herein documents the API changes that have occurred in the WebGUI 8 development effort and how to migrate your code to accomodate the new APIs.
|
||||
|
||||
|
||||
|
||||
WebGUI::Cache
|
||||
=============
|
||||
WebGUI::Cache has been completely rewritten. If you were using the cache API in the past, you'll need to update your code to reflect the changes. NOTE: you can get a cached reference to the cache object from WebGUI::Session, which will be substantially faster than instantiating the object yourself.
|
||||
|
|
@ -12,4 +13,60 @@ my $cache = $session->cache;
|
|||
|
||||
|
||||
|
||||
WebGUI::Asset
|
||||
=============
|
||||
The Asset API has been changed in small, but significant ways. You'll need to make a few changes to your asset subclasses to support these changes.
|
||||
|
||||
Definition
|
||||
----------
|
||||
You must migrate your asset to use the new WebGUI::Definition::Asset class instead of the definition() method. This executes several orders of magnitude faster, but is different in a few ways.
|
||||
|
||||
1) You pass your definition into use WebGUI::Definition::Asset ( def goes here );
|
||||
|
||||
2) You no longer have a reference to $session, so you'll need to make sub routine refs to to method calls.
|
||||
|
||||
3) You no longer have customDrawMethod. You must make custom form controls.
|
||||
|
||||
4) You no longer have filters. Instead, each property has a method called propertyName (so a property called 'title' would be title()). You can override that to achieve the same result.
|
||||
|
||||
5) Because you don't have a reference to $session, you can't internationalize right in the definition. So property elements like "label" and "hoverHelp" are just i18n identifiers and will need to be run through internationalization on output.
|
||||
|
||||
6) Definition's are now rigid. This means that every property needs to be defined in the definition, and it must at least have a "fieldType" element. If the field is to be displayed (ie: it doesn't have a noFormPost=>1 element) then it must also at minimum have label and hoverHelp elements. In addition, you must specify assetName, tableName, and properties attributes at minimum. Anything less is invalid.
|
||||
|
||||
7) The properties attribute must be an array reference of properties. No more Tie::IxHash.
|
||||
|
||||
Here's an example.
|
||||
|
||||
use WebGUI::Definition::Asset (
|
||||
assetName => 'Gadget',
|
||||
tableName => 'gadget',
|
||||
properties => [
|
||||
urlToJavascript => {
|
||||
fieldType => 'url',
|
||||
label => 'URL to Javascript Class',
|
||||
hoverHelp => 'URL to Javascript Class help',
|
||||
},
|
||||
foo => {
|
||||
fieldType => 'text',
|
||||
noFormPost => 1,
|
||||
},
|
||||
bar => {
|
||||
fieldType => 'codearea',
|
||||
uiLevel => 9,
|
||||
label => 'Bar',
|
||||
hoverHelp => 'Bar help',
|
||||
defaultValue => sub {
|
||||
my $self = shift;
|
||||
return $self->callSomeMethod;
|
||||
}
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
Removed Methods
|
||||
---------------
|
||||
assetDbProperties - Simply instantiate the asset if you want it's properties.
|
||||
|
||||
assetExists - Simply instantiate the asset if you want to know if it exists.
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,176 @@ use WebGUI::ProgressBar;
|
|||
use WebGUI::Search::Index;
|
||||
use WebGUI::TabForm;
|
||||
use WebGUI::Utility;
|
||||
use WebGUI::Definition::Asset (
|
||||
properties => [
|
||||
title=>{
|
||||
tab =>"properties",
|
||||
label =>'99',
|
||||
hoverHelp =>'99 description',
|
||||
fieldType =>'text',
|
||||
defaultValue =>'Untitled',
|
||||
filter =>'fixTitle',
|
||||
},
|
||||
menuTitle=>{
|
||||
tab =>"properties",
|
||||
label =>'411',
|
||||
hoverHelp =>'411 description',
|
||||
uiLevel =>1,
|
||||
fieldType =>'text',
|
||||
filter =>'fixTitle',
|
||||
defaultValue =>'Untitled',
|
||||
},
|
||||
url=>{
|
||||
tab =>"properties",
|
||||
label =>'104',
|
||||
hoverHelp =>'104 description',
|
||||
uiLevel =>3,
|
||||
fieldType =>'text',
|
||||
defaultValue =>'',
|
||||
filter =>'fixUrl',
|
||||
},
|
||||
isHidden=>{
|
||||
tab =>"display",
|
||||
label =>'886',
|
||||
hoverHelp =>'886 description',
|
||||
uiLevel =>6,
|
||||
fieldType =>'yesNo',
|
||||
defaultValue =>0,
|
||||
},
|
||||
newWindow=>{
|
||||
tab =>"display",
|
||||
label =>'940',
|
||||
hoverHelp =>'940 description',
|
||||
uiLevel =>9,
|
||||
fieldType =>'yesNo',
|
||||
defaultValue =>0,
|
||||
},
|
||||
encryptPage=>{
|
||||
fieldType =>
|
||||
sub {
|
||||
my $self = shift;
|
||||
return $self->session->config->get("sslEnabled") ? 'yesNo' : 'hidden';
|
||||
},
|
||||
tab => "security",
|
||||
label => 'encrypt page',
|
||||
hoverHelp => 'encrypt page description',
|
||||
uiLevel => 6,
|
||||
defaultValue => 0,
|
||||
},
|
||||
ownerUserId=>{
|
||||
tab =>"security",
|
||||
label =>108,
|
||||
hoverHelp =>'108 description',
|
||||
uiLevel =>6,
|
||||
fieldType =>'user',
|
||||
filter =>'fixId',
|
||||
defaultValue =>'3',
|
||||
},
|
||||
groupIdView=>{
|
||||
tab =>"security",
|
||||
label =>872,
|
||||
hoverHelp =>'872 description',
|
||||
uiLevel =>6,
|
||||
fieldType =>'group',
|
||||
filter =>'fixId',
|
||||
defaultValue =>'7',
|
||||
},
|
||||
groupIdEdit=>{
|
||||
tab =>"security",
|
||||
label =>871,
|
||||
excludeGroups =>[1,7],
|
||||
hoverHelp =>'871 description',
|
||||
uiLevel =>6,
|
||||
fieldType =>'group',
|
||||
filter =>'fixId',
|
||||
defaultValue =>'4',
|
||||
},
|
||||
synopsis=>{
|
||||
tab =>"meta",
|
||||
label =>412,
|
||||
hoverHelp =>'412 description',
|
||||
uiLevel =>3,
|
||||
fieldType =>'textarea',
|
||||
defaultValue =>undef,
|
||||
},
|
||||
extraHeadTags=>{
|
||||
tab =>"meta",
|
||||
label =>"extra head tags",
|
||||
hoverHelp =>'extra head tags description',
|
||||
uiLevel =>5,
|
||||
fieldType =>'codearea',
|
||||
defaultValue =>undef,
|
||||
customDrawMethod=> 'drawExtraHeadTags',
|
||||
filter => 'packExtraHeadTags',
|
||||
},
|
||||
extraHeadTagsPacked => {
|
||||
fieldType => 'hidden',
|
||||
defaultValue => undef,
|
||||
noFormPost => 1,
|
||||
},
|
||||
usePackedHeadTags => {
|
||||
tab => "meta",
|
||||
label => 'usePackedHeadTags label',
|
||||
hoverHelp => 'usePackedHeadTags description',
|
||||
uiLevel => 7,
|
||||
fieldType => 'yesNo',
|
||||
defaultValue => 0,
|
||||
},
|
||||
isPackage=>{
|
||||
label =>"make package",
|
||||
tab =>"meta",
|
||||
hoverHelp =>'make package description',
|
||||
uiLevel =>7,
|
||||
fieldType =>'yesNo',
|
||||
defaultValue =>0,
|
||||
},
|
||||
isPrototype=>{
|
||||
tab =>"meta",
|
||||
label =>"make prototype",
|
||||
hoverHelp =>'make prototype description',
|
||||
uiLevel =>9,
|
||||
fieldType =>'yesNo',
|
||||
defaultValue =>0,
|
||||
},
|
||||
isExportable=>{
|
||||
tab =>'meta',
|
||||
label =>'make asset exportable',
|
||||
hoverHelp =>'make asset exportable description',
|
||||
uiLevel =>9,
|
||||
fieldType =>'yesNo',
|
||||
defaultValue =>1,
|
||||
},
|
||||
inheritUrlFromParent=>{
|
||||
tab =>'meta',
|
||||
label =>'does asset inherit URL from parent',
|
||||
hoverHelp =>'does asset inherit URL from parent description',
|
||||
uiLevel =>9,
|
||||
fieldType =>'yesNo',
|
||||
defaultValue =>0,
|
||||
},
|
||||
status=>{
|
||||
noFormPost =>1,
|
||||
fieldType =>'text',
|
||||
defaultValue =>'pending',
|
||||
},
|
||||
lastModified=>{
|
||||
noFormPost =>1,
|
||||
fieldType =>'DateTime',
|
||||
defaultValue => sub { return time() },
|
||||
},
|
||||
assetSize=>{
|
||||
noFormPost =>1,
|
||||
fieldType =>'integer',
|
||||
defaultValue =>0,
|
||||
},
|
||||
],
|
||||
assetName =>'asset',
|
||||
tableName =>'assetData',
|
||||
className =>'WebGUI::Asset',
|
||||
icon =>'assets.gif',
|
||||
);
|
||||
|
||||
|
||||
|
||||
=head1 NAME
|
||||
|
||||
|
|
@ -107,79 +277,6 @@ sub addMissing {
|
|||
return $ac->render($output);
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
=head2 assetDbProperties ( session, assetId, className, revisionDate )
|
||||
|
||||
Class method to return all properties in all tables used by a particular Asset.
|
||||
Returns a hash ref with data from the table.
|
||||
|
||||
=head3 session
|
||||
|
||||
A reference to the current session.
|
||||
|
||||
=head3 assetId
|
||||
|
||||
The assetId of the asset you're creating an object reference for. Must not be blank.
|
||||
|
||||
=head3 className
|
||||
|
||||
By default we'll use whatever class it is called by like WebGUI::Asset::File->new(), so WebGUI::Asset::File would be used.
|
||||
|
||||
=head3 revisionDate
|
||||
|
||||
An epoch date that represents a specific version of an asset.
|
||||
|
||||
=cut
|
||||
|
||||
sub assetDbProperties {
|
||||
my $class = shift;
|
||||
my $session = shift;
|
||||
my ($assetId, $className, $revisionDate) = @_;
|
||||
my $sql = "select * from asset";
|
||||
my $where = " where asset.assetId=?";
|
||||
my $placeHolders = [$assetId];
|
||||
foreach my $definition (@{$className->definition($session)}) {
|
||||
$sql .= ",".$definition->{tableName};
|
||||
$where .= " and (asset.assetId=".$definition->{tableName}.".assetId and ".$definition->{tableName}.".revisionDate=".$revisionDate.")";
|
||||
}
|
||||
return $session->db->quickHashRef($sql.$where, $placeHolders);
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
=head2 assetExists ( session, assetId, className, revisionDate )
|
||||
|
||||
Class method that checks to see if an asset exists in all the proper tables for
|
||||
the requested asset class. Returns true or false.
|
||||
|
||||
=head3 session
|
||||
|
||||
A reference to the current session.
|
||||
|
||||
=head3 assetId
|
||||
|
||||
The assetId of the asset you're creating an object reference for. Must not be blank.
|
||||
|
||||
=head3 className
|
||||
|
||||
By default we'll use whatever class it is called by like WebGUI::Asset::File->new(), so WebGUI::Asset::File would be used.
|
||||
|
||||
=head3 revisionDate
|
||||
|
||||
An epoch date that represents a specific version of an asset.
|
||||
|
||||
=cut
|
||||
|
||||
sub assetExists {
|
||||
my $class = shift;
|
||||
my $session = shift;
|
||||
my ($assetId, $className, $revisionDate) = @_;
|
||||
my $dbProperties = $class->assetDbProperties($session, $assetId, $className, $revisionDate);
|
||||
return exists $dbProperties->{assetId};
|
||||
}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
=head2 canAdd ( session, [userId, groupId] )
|
||||
|
|
@ -363,200 +460,6 @@ sub cloneFromDb {
|
|||
);
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
=head2 definition ( session, [ definition ] )
|
||||
|
||||
Basic definition of an Asset. Properties, default values. Returns an array reference containing tableName,className,properties
|
||||
|
||||
=head3 session
|
||||
|
||||
The current session object.
|
||||
|
||||
=head3 definition
|
||||
|
||||
An array reference containing additional information to include with the default definition.
|
||||
|
||||
=cut
|
||||
|
||||
sub definition {
|
||||
my $class = shift;
|
||||
my $session = shift;
|
||||
my $definition = shift || [];
|
||||
my $i18n = WebGUI::International->new($session, "Asset");
|
||||
my %properties;
|
||||
tie %properties, 'Tie::IxHash';
|
||||
%properties = (
|
||||
title=>{
|
||||
tab=>"properties",
|
||||
label=>$i18n->get(99),
|
||||
hoverHelp=>$i18n->get('99 description'),
|
||||
fieldType=>'text',
|
||||
defaultValue=>'Untitled',
|
||||
filter=>'fixTitle',
|
||||
},
|
||||
menuTitle=>{
|
||||
tab=>"properties",
|
||||
label=>$i18n->get(411),
|
||||
hoverHelp=>$i18n->get('411 description'),
|
||||
uiLevel=>1,
|
||||
fieldType=>'text',
|
||||
filter=>'fixTitle',
|
||||
defaultValue=>'Untitled',
|
||||
},
|
||||
url=>{
|
||||
tab=>"properties",
|
||||
label=>$i18n->get(104),
|
||||
hoverHelp=>$i18n->get('104 description'),
|
||||
uiLevel=>3,
|
||||
fieldType=>'text',
|
||||
defaultValue=>'',
|
||||
filter=>'fixUrl',
|
||||
},
|
||||
isHidden=>{
|
||||
tab=>"display",
|
||||
label=>$i18n->get(886),
|
||||
hoverHelp=>$i18n->get('886 description'),
|
||||
uiLevel=>6,
|
||||
fieldType=>'yesNo',
|
||||
defaultValue=>0,
|
||||
},
|
||||
newWindow=>{
|
||||
tab=>"display",
|
||||
label=>$i18n->get(940),
|
||||
hoverHelp=>$i18n->get('940 description'),
|
||||
uiLevel=>9,
|
||||
fieldType=>'yesNo',
|
||||
defaultValue=>0,
|
||||
},
|
||||
encryptPage=>{
|
||||
fieldType => ($session->config->get("sslEnabled") ? 'yesNo' : 'hidden'),
|
||||
tab => "security",
|
||||
label => $i18n->get('encrypt page'),
|
||||
hoverHelp => $i18n->get('encrypt page description'),
|
||||
uiLevel => 6,
|
||||
defaultValue => 0,
|
||||
},
|
||||
ownerUserId=>{
|
||||
tab=>"security",
|
||||
label=>$i18n->get(108),
|
||||
hoverHelp=>$i18n->get('108 description'),
|
||||
uiLevel=>6,
|
||||
fieldType=>'user',
|
||||
filter=>'fixId',
|
||||
defaultValue=>'3',
|
||||
},
|
||||
groupIdView=>{
|
||||
tab=>"security",
|
||||
label=>$i18n->get(872),
|
||||
hoverHelp=>$i18n->get('872 description'),
|
||||
uiLevel=>6,
|
||||
fieldType=>'group',
|
||||
filter=>'fixId',
|
||||
defaultValue=>'7',
|
||||
},
|
||||
groupIdEdit=>{
|
||||
tab=>"security",
|
||||
label=>$i18n->get(871),
|
||||
excludeGroups=>[1,7],
|
||||
hoverHelp=>$i18n->get('871 description'),
|
||||
uiLevel=>6,
|
||||
fieldType=>'group',
|
||||
filter=>'fixId',
|
||||
defaultValue=>'4',
|
||||
},
|
||||
synopsis=>{
|
||||
tab=>"meta",
|
||||
label=>$i18n->get(412),
|
||||
hoverHelp=>$i18n->get('412 description'),
|
||||
uiLevel=>3,
|
||||
fieldType=>'textarea',
|
||||
defaultValue=>undef,
|
||||
},
|
||||
extraHeadTags=>{
|
||||
tab=>"meta",
|
||||
label=>$i18n->get("extra head tags"),
|
||||
hoverHelp=>$i18n->get('extra head tags description'),
|
||||
uiLevel=>5,
|
||||
fieldType=>'codearea',
|
||||
defaultValue=>undef,
|
||||
customDrawMethod => 'drawExtraHeadTags',
|
||||
filter => 'packExtraHeadTags',
|
||||
},
|
||||
extraHeadTagsPacked => {
|
||||
fieldType => 'hidden',
|
||||
defaultValue => undef,
|
||||
noFormPost => 1,
|
||||
},
|
||||
usePackedHeadTags => {
|
||||
tab => "meta",
|
||||
label => $i18n->get('usePackedHeadTags label'),
|
||||
hoverHelp => $i18n->get('usePackedHeadTags description'),
|
||||
uiLevel => 7,
|
||||
fieldType => 'yesNo',
|
||||
defaultValue => 0,
|
||||
},
|
||||
isPackage=>{
|
||||
label=>$i18n->get("make package"),
|
||||
tab=>"meta",
|
||||
hoverHelp=>$i18n->get('make package description'),
|
||||
uiLevel=>7,
|
||||
fieldType=>'yesNo',
|
||||
defaultValue=>0,
|
||||
},
|
||||
isPrototype=>{
|
||||
tab=>"meta",
|
||||
label=>$i18n->get("make prototype"),
|
||||
hoverHelp=>$i18n->get('make prototype description'),
|
||||
uiLevel=>9,
|
||||
fieldType=>'yesNo',
|
||||
defaultValue=>0,
|
||||
},
|
||||
isExportable=>{
|
||||
tab=>'meta',
|
||||
label=>$i18n->get('make asset exportable'),
|
||||
hoverHelp=>$i18n->get('make asset exportable description'),
|
||||
uiLevel=>9,
|
||||
fieldType=>'yesNo',
|
||||
defaultValue=>1,
|
||||
},
|
||||
inheritUrlFromParent=>{
|
||||
tab=>'meta',
|
||||
label=>$i18n->get('does asset inherit URL from parent'),
|
||||
hoverHelp=>$i18n->get('does asset inherit URL from parent description'),
|
||||
uiLevel=>9,
|
||||
fieldType=>'yesNo',
|
||||
defaultValue=>0,
|
||||
},
|
||||
status=>{
|
||||
noFormPost=>1,
|
||||
fieldType=>'hidden',
|
||||
defaultValue=>'pending',
|
||||
},
|
||||
lastModified=>{
|
||||
noFormPost=>1,
|
||||
fieldType=>'hidden',
|
||||
defaultValue=>time(),
|
||||
},
|
||||
assetSize=>{
|
||||
noFormPost=>1,
|
||||
fieldType=>'hidden',
|
||||
defaultValue=>0,
|
||||
},
|
||||
);
|
||||
push(@{$definition}, {
|
||||
assetName=>$i18n->get("asset"),
|
||||
tableName=>'assetData',
|
||||
autoGenerateForms=>1,
|
||||
className=>'WebGUI::Asset',
|
||||
icon=>'assets.gif',
|
||||
properties=>\%properties
|
||||
}
|
||||
);
|
||||
return $definition;
|
||||
}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
=head2 drawExtraHeadTags ( )
|
||||
|
|
@ -1157,10 +1060,8 @@ If this evaluates to True, then the smaller extras/adminConsole/small/assets.gif
|
|||
=cut
|
||||
|
||||
sub getIcon {
|
||||
my $self = shift;
|
||||
my $small = shift;
|
||||
my $definition = $self->definition($self->session);
|
||||
my $icon = $definition->[0]{icon} || "assets.gif";
|
||||
my ($self, $small) = @_;
|
||||
my $icon = $self->getAttribute("icon");
|
||||
return $self->session->url->extras('assets/small/'.$icon) if ($small);
|
||||
return $self->session->url->extras('assets/'.$icon);
|
||||
}
|
||||
|
|
@ -1226,11 +1127,8 @@ returning results. This allows very large sets of results to be handled in chun
|
|||
=cut
|
||||
|
||||
sub getIsa {
|
||||
my $class = shift;
|
||||
my $session = shift;
|
||||
my $offset = shift;
|
||||
my $def = $class->definition($session);
|
||||
my $tableName = $def->[0]->{tableName};
|
||||
my ($class, $session, $offset) = @_;
|
||||
my $tableName = $self->getAttribute('tableName');
|
||||
my $sql = "select distinct(assetId) from $tableName";
|
||||
if (defined $offset) {
|
||||
$sql .= ' LIMIT '. $offset . ',1234567890';
|
||||
|
|
@ -1302,14 +1200,13 @@ sub getMenuTitle {
|
|||
|
||||
=head2 getName ( )
|
||||
|
||||
Returns the internationalization of the word "Asset".
|
||||
Returns the human readable name of the asset.
|
||||
|
||||
=cut
|
||||
|
||||
sub getName {
|
||||
my $self = shift;
|
||||
my $definition = $self->definition($self->session);
|
||||
return $definition->[0]{assetName};
|
||||
return WebGUI::International->new($self->session, 'Asset')->get($self->getAttribute('assetName'));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1571,7 +1468,7 @@ sub getUiLevel {
|
|||
my $className = $self->get("className");
|
||||
return $uiLevel # passed in
|
||||
|| $self->session->config->get("assets/".$className."/uiLevel") # from config
|
||||
|| $self->definition($self->session)->[0]{uiLevel} # from definition
|
||||
|| $self->getAttribute('uiLevel') # from definition
|
||||
|| 1; # if all else fails
|
||||
}
|
||||
|
||||
|
|
@ -1779,7 +1676,14 @@ sub new {
|
|||
|
||||
my $properties = eval{$session->cache->get(["asset",$assetId,$revisionDate])};
|
||||
unless (exists $properties->{assetId}) {
|
||||
$properties = WebGUI::Asset->assetDbProperties($session, $assetId, $class, $revisionDate);
|
||||
my $sql = "select * from asset";
|
||||
my $where = " where asset.assetId=?";
|
||||
my $placeHolders = [$assetId];
|
||||
foreach my $definition (@{$class->definition($session)}) {
|
||||
$sql .= ",".$definition->{tableName};
|
||||
$where .= " and (asset.assetId=".$definition->{tableName}.".assetId and ".$definition->{tableName}.".revisionDate=".$revisionDate.")";
|
||||
}
|
||||
$properties = $session->db->quickHashRef($sql.$where, $placeHolders);
|
||||
unless (exists $properties->{assetId}) {
|
||||
$session->errorHandler->error("Asset $assetId $class $revisionDate is missing properties. Consult your database tables for corruption. ");
|
||||
return undef;
|
||||
|
|
|
|||
|
|
@ -1282,7 +1282,7 @@ sub importAssetCollateralData {
|
|||
my $id = $data->{properties}{assetId};
|
||||
my $class = $data->{properties}{className};
|
||||
my $version = $data->{properties}{revisionDate};
|
||||
my $assetExists = WebGUI::Asset->assetExists($self->session, $id, $class, $version);
|
||||
my $assetExists = WebGUI::Asset->new($self->session, $id, $class, $version);
|
||||
|
||||
$error->info("Importing Things for Thingy ".$data->{properties}{title});
|
||||
my @importThings;
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ sub importAssetData {
|
|||
WebGUI::Asset->loadModule( $self->session, $class );
|
||||
|
||||
my $asset;
|
||||
my $revisionExists = WebGUI::Asset->assetExists($self->session, $id, $class, $version);
|
||||
my $revisionExists = WebGUI::Asset->new($self->session, $id, $class, $version);
|
||||
my %properties = %{ $data->{properties} };
|
||||
if ($options->{inheritPermissions}) {
|
||||
delete $properties{ownerUserId};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue