Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
erp5
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Labels
Merge Requests
139
Merge Requests
139
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Jobs
Commits
Open sidebar
nexedi
erp5
Commits
9606e08e
Commit
9606e08e
authored
Jan 21, 2019
by
Romain Courteaud
1
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[erp5_core/erp5_web_renderjs_ui] Update jIO 3.36.0
parent
7c7d1aa1
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
624 additions
and
4 deletions
+624
-4
bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_jio_js.js
...enderjs_ui/PathTemplateItem/web_page_module/rjs_jio_js.js
+311
-1
bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_jio_js.xml
...nderjs_ui/PathTemplateItem/web_page_module/rjs_jio_js.xml
+2
-2
product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/jio.js.js
...p5_core/SkinTemplateItem/portal_skins/erp5_core/jio.js.js
+311
-1
No files found.
bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_jio_js.js
View file @
9606e08e
...
...
@@ -8849,7 +8849,7 @@ return new Parser;
var
ceilHeapSize
=
function
(
v
)
{
// The asm.js spec says:
// The heap object's byteLength must be either
// 2^n for n in [12, 24) or 2^24 * n for n
≥
1.
// 2^n for n in [12, 24) or 2^24 * n for n
≥
1.
// Also, byteLengths smaller than 2^16 are deprecated.
var
p
;
// If v is smaller than 2^16, the smallest possible solution
...
...
@@ -12959,6 +12959,316 @@ return new Parser;
jIO
.
addStorage
(
'
union
'
,
UnionStorage
);
}(
jIO
,
RSVP
));
/*
* Copyright 2019, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/**
* JIO Linshare Storage. Type = "linshare".
* Linshare "database" storage.
* http://download.linshare.org/components/linshare-core/2.2.2/
* Can't set up id, implied can't put new document
*/
/*global jIO, RSVP, UriTemplate, FormData, Blob*/
/*jslint nomen: true*/
(
function
(
jIO
,
RSVP
,
UriTemplate
,
FormData
,
Blob
)
{
"
use strict
"
;
function
makeRequest
(
storage
,
uuid
,
options
,
download
)
{
if
(
options
===
undefined
)
{
options
=
{};
}
if
(
options
.
xhrFields
===
undefined
)
{
options
.
xhrFields
=
{};
}
if
(
options
.
headers
===
undefined
)
{
options
.
headers
=
{};
}
// Prefer JSON by default
if
(
download
===
true
)
{
options
.
url
=
storage
.
_blob_template
.
expand
({
uuid
:
uuid
});
options
.
dataType
=
'
blob
'
;
}
else
{
options
.
url
=
storage
.
_url_template
.
expand
({
uuid
:
uuid
||
""
});
if
(
!
options
.
headers
.
hasOwnProperty
(
'
Accept
'
))
{
options
.
headers
.
Accept
=
'
application/json
'
;
options
.
dataType
=
'
json
'
;
}
}
// Use cookie based auth
if
(
storage
.
hasOwnProperty
(
'
_access_token
'
))
{
options
.
headers
.
Authorization
=
"
Basic
"
+
storage
.
_access_token
;
}
else
{
options
.
xhrFields
.
withCredentials
=
true
;
}
return
new
RSVP
.
Queue
()
.
push
(
function
()
{
return
jIO
.
util
.
ajax
(
options
);
})
.
push
(
function
(
event
)
{
if
(
download
===
true
)
{
return
(
event
.
target
.
response
||
// sinon does not fill the response attribute
new
Blob
([
event
.
target
.
responseText
],
{
type
:
'
text/plain
'
})
);
}
return
(
event
.
target
.
response
||
// sinon does not fill the response attribute
JSON
.
parse
(
event
.
target
.
responseText
)
);
});
}
/**
* The JIO Linshare Storage extension
*
* @class LinshareStorage
* @constructor
*/
function
LinshareStorage
(
spec
)
{
if
(
typeof
spec
.
url
!==
"
string
"
||
!
spec
.
url
)
{
throw
new
TypeError
(
"
Linshare 'url' must be a string
"
+
"
which contains more than one character.
"
);
}
this
.
_url_template
=
UriTemplate
.
parse
(
spec
.
url
+
'
/linshare/webservice/rest/user/v2/documents/{uuid}
'
);
this
.
_blob_template
=
UriTemplate
.
parse
(
spec
.
url
+
'
/linshare/webservice/rest/user/v2/documents/{uuid}/download
'
);
if
(
spec
.
hasOwnProperty
(
'
access_token
'
))
{
this
.
_access_token
=
spec
.
access_token
;
}
}
var
capacity_list
=
[
'
list
'
,
'
include
'
];
LinshareStorage
.
prototype
.
hasCapacity
=
function
(
name
)
{
return
(
capacity_list
.
indexOf
(
name
)
!==
-
1
);
};
function
sortByModificationDate
(
entry1
,
entry2
)
{
var
date1
=
entry1
.
modificationDate
,
date2
=
entry2
.
modificationDate
;
return
(
date1
===
date2
)
?
0
:
((
date1
<
date2
)
?
1
:
-
1
);
}
function
getDocumentList
(
storage
,
options
)
{
return
makeRequest
(
storage
,
""
,
{
type
:
"
GET
"
})
.
push
(
function
(
entry_list
)
{
// Linshare only allow to get the full list of documents
// First, sort the entries by modificationDate in order to
// drop the 'old' entries with the same 'name'
// (as linshare does not to update an existing doc)
entry_list
.
sort
(
sortByModificationDate
);
// Only return one document per name
// Keep the newer document
var
entry_dict
=
{},
i
,
len
=
entry_list
.
length
,
entry_name
,
entry
,
result_list
=
[];
for
(
i
=
0
;
i
<
len
;
i
+=
1
)
{
entry_name
=
entry_list
[
i
].
name
;
// If we only need one precise name, no need to check the others
if
(
!
options
.
hasOwnProperty
(
'
only_id
'
)
||
(
options
.
only_id
===
entry_name
))
{
if
(
!
entry_dict
.
hasOwnProperty
(
entry_name
))
{
entry
=
{
id
:
entry_name
,
value
:
{},
_linshare_uuid
:
entry_list
[
i
].
uuid
};
if
(
options
.
include_docs
===
true
)
{
entry
.
doc
=
JSON
.
parse
(
entry_list
[
i
].
metaData
)
||
{};
}
result_list
.
push
(
entry
);
if
(
options
.
all_revision
!==
true
)
{
// If we only want to fetch 'one revision',
// ie, the latest document matching this id
entry_dict
[
entry_name
]
=
null
;
if
(
options
.
only_id
===
entry_name
)
{
// Document has been found, no need to check all the others
break
;
}
}
}
}
}
return
result_list
;
});
}
LinshareStorage
.
prototype
.
buildQuery
=
function
(
options
)
{
return
getDocumentList
(
this
,
{
include_docs
:
options
.
include_docs
});
};
LinshareStorage
.
prototype
.
get
=
function
(
id
)
{
// It is not possible to get a document by its name
// The only way is to list all of them, and find it manually
return
getDocumentList
(
this
,
{
include_docs
:
true
,
only_id
:
id
})
.
push
(
function
(
result_list
)
{
if
(
result_list
.
length
===
1
)
{
return
result_list
[
0
].
doc
;
}
throw
new
jIO
.
util
.
jIOError
(
"
Can't find document with id :
"
+
id
,
404
);
});
};
function
createLinshareDocument
(
storage
,
id
,
doc
,
blob
)
{
var
data
=
new
FormData
();
data
.
append
(
'
file
'
,
blob
,
id
);
data
.
append
(
'
filesize
'
,
blob
.
size
);
data
.
append
(
'
filename
'
,
id
);
data
.
append
(
'
description
'
,
doc
.
title
||
doc
.
description
||
''
);
data
.
append
(
'
metadata
'
,
jIO
.
util
.
stringify
(
doc
));
return
makeRequest
(
storage
,
''
,
{
type
:
'
POST
'
,
data
:
data
});
}
LinshareStorage
.
prototype
.
put
=
function
(
id
,
doc
)
{
var
storage
=
this
;
return
getDocumentList
(
storage
,
{
include_docs
:
true
,
only_id
:
id
})
.
push
(
function
(
result_list
)
{
if
(
result_list
.
length
===
1
)
{
// Update existing document metadata
var
data
=
{
uuid
:
result_list
[
0
].
_linshare_uuid
,
metaData
:
jIO
.
util
.
stringify
(
doc
),
name
:
id
,
description
:
doc
.
title
||
doc
.
description
||
''
};
return
makeRequest
(
storage
,
result_list
[
0
].
_linshare_uuid
,
{
type
:
'
PUT
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
},
data
:
jIO
.
util
.
stringify
(
data
)
});
}
// Create a new one
return
createLinshareDocument
(
storage
,
id
,
doc
,
new
Blob
());
});
};
LinshareStorage
.
prototype
.
remove
=
function
(
id
)
{
var
storage
=
this
;
// Delete all entries matching the id
return
getDocumentList
(
storage
,
{
only_id
:
id
,
all_revision
:
true
})
.
push
(
function
(
result_list
)
{
var
promise_list
=
[],
i
,
len
=
result_list
.
length
;
for
(
i
=
0
;
i
<
len
;
i
+=
1
)
{
promise_list
.
push
(
makeRequest
(
storage
,
result_list
[
i
].
_linshare_uuid
,
{
type
:
"
DELETE
"
})
);
}
return
RSVP
.
all
(
promise_list
);
});
};
LinshareStorage
.
prototype
.
allAttachments
=
function
(
id
)
{
return
this
.
get
(
id
)
.
push
(
function
()
{
return
{
enclosure
:
{}};
});
};
function
restrictAttachmentId
(
name
)
{
if
(
name
!==
"
enclosure
"
)
{
throw
new
jIO
.
util
.
jIOError
(
"
attachment name
"
+
name
+
"
is forbidden in linshare
"
,
400
);
}
}
LinshareStorage
.
prototype
.
putAttachment
=
function
(
id
,
name
,
blob
)
{
restrictAttachmentId
(
name
);
var
storage
=
this
;
return
storage
.
get
(
id
)
.
push
(
function
(
doc
)
{
// Create a new document with the same id but a different blob content
return
createLinshareDocument
(
storage
,
id
,
doc
,
blob
);
});
};
LinshareStorage
.
prototype
.
getAttachment
=
function
(
id
,
name
)
{
restrictAttachmentId
(
name
);
var
storage
=
this
;
// It is not possible to get a document by its name
// The only way is to list all of them, and find it manually
return
getDocumentList
(
storage
,
{
only_id
:
id
})
.
push
(
function
(
result_list
)
{
if
(
result_list
.
length
===
1
)
{
return
makeRequest
(
storage
,
result_list
[
0
].
_linshare_uuid
,
{
},
true
);
}
throw
new
jIO
.
util
.
jIOError
(
"
Can't find document with id :
"
+
id
,
404
);
});
};
jIO
.
addStorage
(
'
linshare
'
,
LinshareStorage
);
}(
jIO
,
RSVP
,
UriTemplate
,
FormData
,
Blob
));
/*
* Copyright 2013, Nexedi SA
*
...
...
bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_jio_js.xml
View file @
9606e08e
...
...
@@ -236,7 +236,7 @@
</item>
<item>
<key>
<string>
serial
</string>
</key>
<value>
<string>
97
0.58387.45482.44544
</string>
</value>
<value>
<string>
97
3.4826.12688.30276
</string>
</value>
</item>
<item>
<key>
<string>
state
</string>
</key>
...
...
@@ -254,7 +254,7 @@
</tuple>
<state>
<tuple>
<float>
15
39695044.71
</float>
<float>
15
48090618.17
</float>
<string>
UTC
</string>
</tuple>
</state>
...
...
product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/jio.js.js
View file @
9606e08e
...
...
@@ -8849,7 +8849,7 @@ return new Parser;
var
ceilHeapSize
=
function
(
v
)
{
// The asm.js spec says:
// The heap object's byteLength must be either
// 2^n for n in [12, 24) or 2^24 * n for n
≥
1.
// 2^n for n in [12, 24) or 2^24 * n for n
≥
1.
// Also, byteLengths smaller than 2^16 are deprecated.
var
p
;
// If v is smaller than 2^16, the smallest possible solution
...
...
@@ -12959,6 +12959,316 @@ return new Parser;
jIO
.
addStorage
(
'
union
'
,
UnionStorage
);
}(
jIO
,
RSVP
));
/*
* Copyright 2019, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/**
* JIO Linshare Storage. Type = "linshare".
* Linshare "database" storage.
* http://download.linshare.org/components/linshare-core/2.2.2/
* Can't set up id, implied can't put new document
*/
/*global jIO, RSVP, UriTemplate, FormData, Blob*/
/*jslint nomen: true*/
(
function
(
jIO
,
RSVP
,
UriTemplate
,
FormData
,
Blob
)
{
"
use strict
"
;
function
makeRequest
(
storage
,
uuid
,
options
,
download
)
{
if
(
options
===
undefined
)
{
options
=
{};
}
if
(
options
.
xhrFields
===
undefined
)
{
options
.
xhrFields
=
{};
}
if
(
options
.
headers
===
undefined
)
{
options
.
headers
=
{};
}
// Prefer JSON by default
if
(
download
===
true
)
{
options
.
url
=
storage
.
_blob_template
.
expand
({
uuid
:
uuid
});
options
.
dataType
=
'
blob
'
;
}
else
{
options
.
url
=
storage
.
_url_template
.
expand
({
uuid
:
uuid
||
""
});
if
(
!
options
.
headers
.
hasOwnProperty
(
'
Accept
'
))
{
options
.
headers
.
Accept
=
'
application/json
'
;
options
.
dataType
=
'
json
'
;
}
}
// Use cookie based auth
if
(
storage
.
hasOwnProperty
(
'
_access_token
'
))
{
options
.
headers
.
Authorization
=
"
Basic
"
+
storage
.
_access_token
;
}
else
{
options
.
xhrFields
.
withCredentials
=
true
;
}
return
new
RSVP
.
Queue
()
.
push
(
function
()
{
return
jIO
.
util
.
ajax
(
options
);
})
.
push
(
function
(
event
)
{
if
(
download
===
true
)
{
return
(
event
.
target
.
response
||
// sinon does not fill the response attribute
new
Blob
([
event
.
target
.
responseText
],
{
type
:
'
text/plain
'
})
);
}
return
(
event
.
target
.
response
||
// sinon does not fill the response attribute
JSON
.
parse
(
event
.
target
.
responseText
)
);
});
}
/**
* The JIO Linshare Storage extension
*
* @class LinshareStorage
* @constructor
*/
function
LinshareStorage
(
spec
)
{
if
(
typeof
spec
.
url
!==
"
string
"
||
!
spec
.
url
)
{
throw
new
TypeError
(
"
Linshare 'url' must be a string
"
+
"
which contains more than one character.
"
);
}
this
.
_url_template
=
UriTemplate
.
parse
(
spec
.
url
+
'
/linshare/webservice/rest/user/v2/documents/{uuid}
'
);
this
.
_blob_template
=
UriTemplate
.
parse
(
spec
.
url
+
'
/linshare/webservice/rest/user/v2/documents/{uuid}/download
'
);
if
(
spec
.
hasOwnProperty
(
'
access_token
'
))
{
this
.
_access_token
=
spec
.
access_token
;
}
}
var
capacity_list
=
[
'
list
'
,
'
include
'
];
LinshareStorage
.
prototype
.
hasCapacity
=
function
(
name
)
{
return
(
capacity_list
.
indexOf
(
name
)
!==
-
1
);
};
function
sortByModificationDate
(
entry1
,
entry2
)
{
var
date1
=
entry1
.
modificationDate
,
date2
=
entry2
.
modificationDate
;
return
(
date1
===
date2
)
?
0
:
((
date1
<
date2
)
?
1
:
-
1
);
}
function
getDocumentList
(
storage
,
options
)
{
return
makeRequest
(
storage
,
""
,
{
type
:
"
GET
"
})
.
push
(
function
(
entry_list
)
{
// Linshare only allow to get the full list of documents
// First, sort the entries by modificationDate in order to
// drop the 'old' entries with the same 'name'
// (as linshare does not to update an existing doc)
entry_list
.
sort
(
sortByModificationDate
);
// Only return one document per name
// Keep the newer document
var
entry_dict
=
{},
i
,
len
=
entry_list
.
length
,
entry_name
,
entry
,
result_list
=
[];
for
(
i
=
0
;
i
<
len
;
i
+=
1
)
{
entry_name
=
entry_list
[
i
].
name
;
// If we only need one precise name, no need to check the others
if
(
!
options
.
hasOwnProperty
(
'
only_id
'
)
||
(
options
.
only_id
===
entry_name
))
{
if
(
!
entry_dict
.
hasOwnProperty
(
entry_name
))
{
entry
=
{
id
:
entry_name
,
value
:
{},
_linshare_uuid
:
entry_list
[
i
].
uuid
};
if
(
options
.
include_docs
===
true
)
{
entry
.
doc
=
JSON
.
parse
(
entry_list
[
i
].
metaData
)
||
{};
}
result_list
.
push
(
entry
);
if
(
options
.
all_revision
!==
true
)
{
// If we only want to fetch 'one revision',
// ie, the latest document matching this id
entry_dict
[
entry_name
]
=
null
;
if
(
options
.
only_id
===
entry_name
)
{
// Document has been found, no need to check all the others
break
;
}
}
}
}
}
return
result_list
;
});
}
LinshareStorage
.
prototype
.
buildQuery
=
function
(
options
)
{
return
getDocumentList
(
this
,
{
include_docs
:
options
.
include_docs
});
};
LinshareStorage
.
prototype
.
get
=
function
(
id
)
{
// It is not possible to get a document by its name
// The only way is to list all of them, and find it manually
return
getDocumentList
(
this
,
{
include_docs
:
true
,
only_id
:
id
})
.
push
(
function
(
result_list
)
{
if
(
result_list
.
length
===
1
)
{
return
result_list
[
0
].
doc
;
}
throw
new
jIO
.
util
.
jIOError
(
"
Can't find document with id :
"
+
id
,
404
);
});
};
function
createLinshareDocument
(
storage
,
id
,
doc
,
blob
)
{
var
data
=
new
FormData
();
data
.
append
(
'
file
'
,
blob
,
id
);
data
.
append
(
'
filesize
'
,
blob
.
size
);
data
.
append
(
'
filename
'
,
id
);
data
.
append
(
'
description
'
,
doc
.
title
||
doc
.
description
||
''
);
data
.
append
(
'
metadata
'
,
jIO
.
util
.
stringify
(
doc
));
return
makeRequest
(
storage
,
''
,
{
type
:
'
POST
'
,
data
:
data
});
}
LinshareStorage
.
prototype
.
put
=
function
(
id
,
doc
)
{
var
storage
=
this
;
return
getDocumentList
(
storage
,
{
include_docs
:
true
,
only_id
:
id
})
.
push
(
function
(
result_list
)
{
if
(
result_list
.
length
===
1
)
{
// Update existing document metadata
var
data
=
{
uuid
:
result_list
[
0
].
_linshare_uuid
,
metaData
:
jIO
.
util
.
stringify
(
doc
),
name
:
id
,
description
:
doc
.
title
||
doc
.
description
||
''
};
return
makeRequest
(
storage
,
result_list
[
0
].
_linshare_uuid
,
{
type
:
'
PUT
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
},
data
:
jIO
.
util
.
stringify
(
data
)
});
}
// Create a new one
return
createLinshareDocument
(
storage
,
id
,
doc
,
new
Blob
());
});
};
LinshareStorage
.
prototype
.
remove
=
function
(
id
)
{
var
storage
=
this
;
// Delete all entries matching the id
return
getDocumentList
(
storage
,
{
only_id
:
id
,
all_revision
:
true
})
.
push
(
function
(
result_list
)
{
var
promise_list
=
[],
i
,
len
=
result_list
.
length
;
for
(
i
=
0
;
i
<
len
;
i
+=
1
)
{
promise_list
.
push
(
makeRequest
(
storage
,
result_list
[
i
].
_linshare_uuid
,
{
type
:
"
DELETE
"
})
);
}
return
RSVP
.
all
(
promise_list
);
});
};
LinshareStorage
.
prototype
.
allAttachments
=
function
(
id
)
{
return
this
.
get
(
id
)
.
push
(
function
()
{
return
{
enclosure
:
{}};
});
};
function
restrictAttachmentId
(
name
)
{
if
(
name
!==
"
enclosure
"
)
{
throw
new
jIO
.
util
.
jIOError
(
"
attachment name
"
+
name
+
"
is forbidden in linshare
"
,
400
);
}
}
LinshareStorage
.
prototype
.
putAttachment
=
function
(
id
,
name
,
blob
)
{
restrictAttachmentId
(
name
);
var
storage
=
this
;
return
storage
.
get
(
id
)
.
push
(
function
(
doc
)
{
// Create a new document with the same id but a different blob content
return
createLinshareDocument
(
storage
,
id
,
doc
,
blob
);
});
};
LinshareStorage
.
prototype
.
getAttachment
=
function
(
id
,
name
)
{
restrictAttachmentId
(
name
);
var
storage
=
this
;
// It is not possible to get a document by its name
// The only way is to list all of them, and find it manually
return
getDocumentList
(
storage
,
{
only_id
:
id
})
.
push
(
function
(
result_list
)
{
if
(
result_list
.
length
===
1
)
{
return
makeRequest
(
storage
,
result_list
[
0
].
_linshare_uuid
,
{
},
true
);
}
throw
new
jIO
.
util
.
jIOError
(
"
Can't find document with id :
"
+
id
,
404
);
});
};
jIO
.
addStorage
(
'
linshare
'
,
LinshareStorage
);
}(
jIO
,
RSVP
,
UriTemplate
,
FormData
,
Blob
));
/*
* Copyright 2013, Nexedi SA
*
...
...
Romain Courteaud
@romain
mentioned in merge request
!817 (closed)
·
Feb 01, 2019
mentioned in merge request
!817 (closed)
mentioned in merge request !817
Toggle commit list
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment