Compare commits

..

18 Commits

158 changed files with 2600 additions and 3073 deletions

24
.gitmodules vendored
View File

@ -1,3 +1,7 @@
[submodule "web/documentserver-example/java/src/main/resources/assets"]
path = web/documentserver-example/java/src/main/resources/assets
url = https://github.com/ONLYOFFICE/document-templates
branch = main/en
[submodule "web/documentserver-example/csharp-mvc/assets"]
path = web/documentserver-example/csharp-mvc/assets
url = https://github.com/ONLYOFFICE/document-templates
@ -6,6 +10,14 @@
path = web/documentserver-example/csharp/assets
url = https://github.com/ONLYOFFICE/document-templates
branch = main/en
[submodule "web/documentserver-example/ruby/public/assets"]
path = web/documentserver-example/ruby/public/assets
url = https://github.com/ONLYOFFICE/document-templates
branch = main/en
[submodule "web/documentserver-example/java-spring/src/main/resources/assets"]
path = web/documentserver-example/java-spring/src/main/resources/assets
url = https://github.com/ONLYOFFICE/document-templates
branch = main/en
[submodule "web/documentserver-example/nodejs/public/assets/document-templates"]
path = web/documentserver-example/nodejs/public/assets/document-templates
url = https://github.com/ONLYOFFICE/document-templates
@ -26,18 +38,6 @@
path = web/documentserver-example/python/assets/document-templates
url = https://github.com/ONLYOFFICE/document-templates
branch = main/en
[submodule "web/documentserver-example/java/src/main/resources/assets/document-templates"]
path = web/documentserver-example/java/src/main/resources/assets/document-templates
url = https://github.com/ONLYOFFICE/document-templates
branch = main/en
[submodule "web/documentserver-example/ruby/assets/document-templates"]
path = web/documentserver-example/ruby/assets/document-templates
url = https://github.com/ONLYOFFICE/document-templates
branch = main/en
[submodule "web/documentserver-example/java-spring/src/main/resources/assets/document-templates"]
path = web/documentserver-example/java-spring/src/main/resources/assets/document-templates
url = https://github.com/ONLYOFFICE/document-templates
branch = main/en
[submodule "web/documentserver-example/python/assets/document-formats"]
path = web/documentserver-example/python/assets/document-formats
url = https://github.com/ONLYOFFICE/document-formats

View File

@ -307,14 +307,6 @@ PHPUnit - The PHP Unit Testing framework. (https://github.com/sebastianb
License: BSD 3-Clause
License File: phpunit.license
property-access - Provides functions to read and write from/to an object or array using a simple string notation. (https://github.com/symfony/property-access/blob/6.3/LICENSE)
License: MIT
License File: property-access.license
serializer - Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON. (https://github.com/symfony/serializer/blob/6.3/LICENSE)
License: MIT
License File: serializer.license
web/documentserver-example/python
@ -354,10 +346,6 @@ jQuery.UI - jQuery UI is an open source library of interface components —
License: MIT
License File: jQuery.UI.license
msgspec - A fast serialization and validation library, with builtin support for JSON, MessagePack, YAML, and TOML. (https://github.com/jcrist/msgspec/blob/0.18.1/LICENSE)
License: BSD 3-Clause
License File: msgspec.license
mypy - Optional static typing for Python. (https://github.com/python/mypy/blob/master/LICENSE)
License: MIT
License File: mypy.license

View File

@ -1,9 +1,5 @@
# Change Log
- nodejs: onRequestSelectSpreadsheet method
- nodejs: onRequestOpen
- nodejs: submitForm
- nodejs: key in referenceData
- nodejs: change reference source
- php: using a repo with a list of formats
- nodejs: using a repo with a list of formats
@ -13,10 +9,6 @@
- python: restore from history
- ruby: restore from history
- php: restore from history
- csharp-mvc: getting history by a separate request
- csharp-mvc: restore from history
- csharp: getting history by a separate request
- csharp: restore from history
## 1.6.0
- nodejs: setUsers for region protection

View File

@ -17,6 +17,7 @@
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Web;
@ -235,6 +236,123 @@ namespace OnlineEditorsExampleMVC.Models
return jss.Serialize(config);
}
// get the document history
public void GetHistory(out string history, out string historyData)
{
var storagePath = WebConfigurationManager.AppSettings["storage-path"];
var jss = new JavaScriptSerializer();
var histDir = DocManagerHelper.HistoryDir(DocManagerHelper.StoragePath(FileName, null));
history = null;
historyData = null;
if (DocManagerHelper.GetFileVersion(histDir) > 0) // if the file was modified (the file version is greater than 0)
{
var currentVersion = DocManagerHelper.GetFileVersion(histDir);
var hist = new List<Dictionary<string, object>>();
var histData = new Dictionary<string, object>();
for (var i = 1; i <= currentVersion; i++) // run through all the file versions
{
var obj = new Dictionary<string, object>();
var dataObj = new Dictionary<string, object>();
var verDir = DocManagerHelper.VersionDir(histDir, i); // get the path to the given file version
var key = i == currentVersion ? Key : File.ReadAllText(Path.Combine(verDir, "key.txt")); // get document key
obj.Add("key", key);
obj.Add("version", i);
if (i == 1) // check if the version number is equal to 1
{
var infoPath = Path.Combine(histDir, "createdInfo.json"); // get meta data of this file
if (File.Exists(infoPath))
{
var info = jss.Deserialize<Dictionary<string, object>>(File.ReadAllText(infoPath));
obj.Add("created", info["created"]); // write meta information to the object (user information and creation date)
obj.Add("user", new Dictionary<string, object>() {
{ "id", info["id"] },
{ "name", info["name"] },
});
}
}
var ext = Path.GetExtension(FileName).ToLower();
dataObj.Add("fileType", ext.Replace(".", ""));
dataObj.Add("key", key);
// write file url to the data object
string prevFileUrl;
string directPrevFileUrl;
if (Path.IsPathRooted(storagePath) && !string.IsNullOrEmpty(storagePath))
{
prevFileUrl = i == currentVersion ? DocManagerHelper.GetHistoryDownloadUrl(FileName, i.ToString(), "prev" + ext)
: DocManagerHelper.GetDownloadUrl(Directory.GetFiles(verDir, "prev.*")[0].Replace(storagePath + "\\", ""));
directPrevFileUrl = i == currentVersion ? DocManagerHelper.GetHistoryDownloadUrl(FileName, i.ToString(), "prev" + ext, false)
: DocManagerHelper.GetDownloadUrl(Directory.GetFiles(verDir, "prev.*")[0].Replace(storagePath + "\\", ""), false);
}
else {
prevFileUrl = i == currentVersion ? FileUri
: DocManagerHelper.GetHistoryDownloadUrl(FileName, i.ToString(), "prev" + ext);
directPrevFileUrl = i == currentVersion ? DocManagerHelper.GetHistoryDownloadUrl(FileName, i.ToString(), "prev" + ext, false)
: DocManagerHelper.GetDownloadUrl(Directory.GetFiles(verDir, "prev.*")[0].Replace(storagePath + "\\", ""), false);
}
dataObj.Add("url", prevFileUrl);
if (IsEnabledDirectUrl)
{
dataObj.Add("directUrl", directPrevFileUrl);
}
dataObj.Add("version", i);
if (i > 1) // check if the version number is greater than 1 (the file was modified)
{
// get the path to the changes.json file
var changes = jss.Deserialize<Dictionary<string, object>>(File.ReadAllText(Path.Combine(DocManagerHelper.VersionDir(histDir, i - 1), "changes.json")));
var changesArray = (ArrayList)changes["changes"];
var change = changesArray.Count > 0
? (Dictionary<string, object>)changesArray[0]
: new Dictionary<string, object>();
// write information about changes to the object
obj.Add("changes", change.Count > 0 ? changes["changes"] : null);
obj.Add("serverVersion", changes["serverVersion"]);
obj.Add("created", change.Count > 0 ? change["created"] : null);
obj.Add("user", change.Count > 0 ? change["user"] : null);
var prev = (Dictionary<string, object>)histData[(i - 2).ToString()]; // get the history data from the previous file version
dataObj.Add("previous", IsEnabledDirectUrl ? new Dictionary<string, object>() { // write information about previous file version to the data object with direct url
{ "fileType", prev["fileType"] },
{ "key", prev["key"] }, // write key and url information about previous file version
{ "url", prev["url"] },
{ "directUrl", prev["directUrl"] },
} : new Dictionary<string, object>() { // write information about previous file version to the data object without direct url
{ "fileType", prev["fileType"] },
{ "key", prev["key"] }, // write key and url information about previous file version
{ "url", prev["url"] },
});
// write the path to the diff.zip archive with differences in this file version
var changesUrl = DocManagerHelper.GetHistoryDownloadUrl(FileName, (i - 1).ToString(), "diff.zip");
dataObj.Add("changesUrl", changesUrl);
}
if(JwtManager.Enabled)
{
var token = JwtManager.Encode(dataObj);
dataObj.Add("token", token);
}
hist.Add(obj); // add object dictionary to the hist list
histData.Add((i - 1).ToString(), dataObj); // write data object information to the history data
}
// write history information about the current file version to the history object
history = jss.Serialize(new Dictionary<string, object>()
{
{ "currentVersion", currentVersion },
{ "history", hist }
});
historyData = jss.Serialize(histData);
}
}
// get a document which will be compared with the current document
public void GetCompareFileData(out string compareConfig)
{

View File

@ -190,47 +190,6 @@
}
};
var onRequestHistory = function () {
let xhr = new XMLHttpRequest();
xhr.open("GET", "webeditor.ashx?type=gethistory&filename=<%= Model.FileName %>");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send();
xhr.onload = function () {
console.log(xhr.responseText);
docEditor.refreshHistory(JSON.parse(xhr.responseText));
}
};
var onRequestHistoryData = function (event) {
var ver = event.data;
let xhr = new XMLHttpRequest();
xhr.open("GET", "webeditor.ashx?type=getversiondata&filename=<%= Model.FileName %>&version=" + ver + "&directUrl=" + !!config.document.directUrl);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send();
xhr.onload = function () {
console.log(xhr.responseText);
docEditor.setHistoryData(JSON.parse(xhr.responseText)); // send the link to the document for viewing the version history
}
};
var onRequestRestore = function (event) {
var fileName = "<%= Model.FileName %>";
var version = event.data.version;
var data = {
fileName: fileName,
version: version
};
let xhr = new XMLHttpRequest();
xhr.open("POST", "webeditor.ashx?type=restore&directUrl=" + !!config.document.directUrl);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(data));
xhr.onload = function () {
docEditor.refreshHistory(JSON.parse(xhr.responseText));
}
}
config = <%= Model.GetDocConfig(Request, Url) %>;
config.width = "100%";
@ -248,19 +207,30 @@
"onRequestMailMergeRecipients": onRequestMailMergeRecipients,
};
<% string hist, histData; %>
<% Model.GetHistory(out hist, out histData); %>
<% string usersForMentions; %>
<% Model.GetUsersMentions(Request, out usersForMentions); %>
if (config.editorConfig.user.id) {
// the user is trying to show the document version history
config.events['onRequestHistory'] = onRequestHistory;
// the user is trying to click the specific document version in the document version history
config.events['onRequestHistoryData'] = onRequestHistoryData;
// the user is trying to go back to the document from viewing the document version history
config.events['onRequestHistoryClose'] = function () {
document.location.reload();
};
config.events['onRequestRestore'] = onRequestRestore;
<% if (!string.IsNullOrEmpty(hist) && !string.IsNullOrEmpty(histData))
{ %>
// the user is trying to show the document version history
config.events['onRequestHistory'] = function () {
docEditor.refreshHistory(<%= hist %>); // show the document version history
};
// the user is trying to click the specific document version in the document version history
config.events['onRequestHistoryData'] = function (event) {
var ver = event.data;
var histData = <%= histData %>;
docEditor.setHistoryData(histData[ver - 1]); // send the link to the document for viewing the version history
};
// the user is trying to go back to the document from viewing the document version history
config.events['onRequestHistoryClose'] = function () {
document.location.reload();
};
<% } %>
// add mentions for not anonymous users
<% if (!string.IsNullOrEmpty(usersForMentions))

View File

@ -17,7 +17,6 @@
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
@ -51,15 +50,6 @@ namespace OnlineEditorsExampleMVC
case "downloadhistory":
DownloadHistory(context);
break;
case "gethistory":
GetHistory(context);
break;
case "getversiondata":
GetVersionData(context);
break;
case "restore":
Restore(context);
break;
case "convert":
Convert(context);
break;
@ -523,173 +513,6 @@ namespace OnlineEditorsExampleMVC
get { return false; }
}
private static void GetHistory(HttpContext context)
{
var jss = new JavaScriptSerializer();
var fileName = context.Request["filename"];
var history = GetHistory(fileName);
context.Response.Write(jss.Serialize(history));
}
private static void GetVersionData(HttpContext context)
{
var storagePath = WebConfigurationManager.AppSettings["storage-path"];
var jss = new JavaScriptSerializer();
var fileName = context.Request["filename"];
int version;
if (!int.TryParse(context.Request["version"], out version))
{
context.Response.Write("{ \"error\": \"Version number invalid!\"}");
return;
}
var versionData = new Dictionary<string, object>();
var histDir = DocManagerHelper.HistoryDir(DocManagerHelper.StoragePath(fileName, null));
var lastVersion = DocManagerHelper.GetFileVersion(histDir);
var verDir = DocManagerHelper.VersionDir(histDir, version);
var key = version == lastVersion
? ServiceConverter.GenerateRevisionId(DocManagerHelper.CurUserHostAddress()
+ "/" + fileName + "/"
+ File.GetLastWriteTime(DocManagerHelper.StoragePath(fileName, null)).GetHashCode())
: File.ReadAllText(Path.Combine(verDir, "key.txt"));
var ext = Path.GetExtension(fileName).ToLower();
versionData.Add("fileType", ext.Replace(".", ""));
versionData.Add("key", key);
// write file url to the data object
string prevFileUrl;
string directPrevFileUrl;
if (Path.IsPathRooted(storagePath) && !string.IsNullOrEmpty(storagePath))
{
prevFileUrl = version == lastVersion ? DocManagerHelper.GetDownloadUrl(fileName)
: DocManagerHelper.GetDownloadUrl(Directory.GetFiles(verDir, "prev.*")[0].Replace(storagePath + "\\", ""));
directPrevFileUrl = version == lastVersion ? DocManagerHelper.GetDownloadUrl(fileName, false)
: DocManagerHelper.GetDownloadUrl(Directory.GetFiles(verDir, "prev.*")[0].Replace(storagePath + "\\", ""), false);
}
else
{
prevFileUrl = version == lastVersion ? DocManagerHelper.GetFileUri(fileName, true)
: DocManagerHelper.GetHistoryDownloadUrl(fileName, version.ToString(), "prev" + ext);
directPrevFileUrl = version == lastVersion ? DocManagerHelper.GetFileUri(fileName, false)
: DocManagerHelper.GetHistoryDownloadUrl(fileName, version.ToString(), "prev" + ext, false);
}
versionData.Add("url", prevFileUrl);
var isEnableDirectUrl = string.Equals(DocManagerHelper.GetDirectUrl(), "true");
if (isEnableDirectUrl)
{
versionData.Add("directUrl", directPrevFileUrl); // write direct url to the data object
}
versionData.Add("version", version);
if (version > 1)
{
var prevVerDir = DocManagerHelper.VersionDir(histDir, version - 1);
var prevUrl = Path.IsPathRooted(storagePath) && !string.IsNullOrEmpty(storagePath)
? DocManagerHelper.GetDownloadUrl(Directory.GetFiles(prevVerDir, "prev.*")[0].Replace(storagePath + "\\", ""))
: DocManagerHelper.GetHistoryDownloadUrl(fileName, (version - 1).ToString(), "prev" + ext);
var prevKey = File.ReadAllText(Path.Combine(prevVerDir, "key.txt"));
Dictionary<string, object> dataPrev = new Dictionary<string, object>() { // write information about previous file version to the data object
{ "fileType", ext.Replace(".", "") },
{ "key", prevKey }, // write key and url information about previous file version
{ "url", prevUrl }
};
string directPrevUrl;
if (isEnableDirectUrl)
{
directPrevUrl = Path.IsPathRooted(storagePath) && !string.IsNullOrEmpty(storagePath)
? DocManagerHelper.GetDownloadUrl(Directory.GetFiles(prevVerDir, "prev.*")[0].Replace(storagePath + "\\", ""), false)
: DocManagerHelper.GetHistoryDownloadUrl(fileName, version.ToString(), "prev" + ext, false);
dataPrev.Add("directUrl", directPrevUrl);
}
versionData.Add("previous", dataPrev);
if (File.Exists(Path.Combine(prevVerDir, "diff.zip")))
{
var changesUrl = DocManagerHelper.GetHistoryDownloadUrl(fileName, (version - 1).ToString(), "diff.zip");
versionData.Add("changesUrl", changesUrl);
}
}
if (JwtManager.Enabled)
{
var token = JwtManager.Encode(versionData);
versionData.Add("token", token);
}
context.Response.Write(jss.Serialize(versionData));
}
private static void Restore(HttpContext context)
{
string fileData;
try
{
using (var receiveStream = context.Request.InputStream)
using (var readStream = new StreamReader(receiveStream))
{
fileData = readStream.ReadToEnd();
if (string.IsNullOrEmpty(fileData)) return;
}
}
catch (Exception e)
{
throw new HttpException((int)HttpStatusCode.BadRequest, e.Message);
}
var jss = new JavaScriptSerializer();
var body = jss.Deserialize<Dictionary<string, object>>(fileData);
var fileName = (string)body["fileName"];
var version = (int)body["version"];
var key = ServiceConverter.GenerateRevisionId(DocManagerHelper.CurUserHostAddress()
+ "/" + fileName + "/"
+ File.GetLastWriteTime(DocManagerHelper.StoragePath(fileName, null)).GetHashCode());
var histDir = DocManagerHelper.HistoryDir(DocManagerHelper.StoragePath(fileName, null));
var currentVersionDir = DocManagerHelper.VersionDir(histDir, DocManagerHelper.GetFileVersion(histDir));
var verDir = DocManagerHelper.VersionDir(histDir, version);
if (!Directory.Exists(currentVersionDir)) Directory.CreateDirectory(currentVersionDir);
var ext = Path.GetExtension(fileName).ToLower();
File.Copy(DocManagerHelper.StoragePath(fileName, null), Path.Combine(currentVersionDir, "prev" + ext));
File.WriteAllText(Path.Combine(currentVersionDir, "key.txt"), key);
var changesPath = Path.Combine(DocManagerHelper.VersionDir(histDir, version - 1), "changes.json");
if (File.Exists(changesPath))
{
File.Copy(changesPath, Path.Combine(currentVersionDir, "changes.json"));
}
File.Copy(Path.Combine(verDir, "prev" + ext), DocManagerHelper.StoragePath(fileName, null), true);
var fileInfo = new FileInfo(DocManagerHelper.StoragePath(fileName, null));
fileInfo.LastWriteTimeUtc = DateTime.UtcNow;
var history = GetHistory(fileName);
context.Response.Write(jss.Serialize(history));
}
// download a history file
private static void DownloadHistory(HttpContext context)
{
@ -864,70 +687,6 @@ namespace OnlineEditorsExampleMVC
context.Response.Write(jss.Serialize(data));
}
// get the document history
private static Dictionary<string, object> GetHistory(string fileName)
{
var jss = new JavaScriptSerializer();
var histDir = DocManagerHelper.HistoryDir(DocManagerHelper.StoragePath(fileName, null));
var history = new Dictionary<string, object>();
var currentVersion = DocManagerHelper.GetFileVersion(histDir);
var currentKey = ServiceConverter.GenerateRevisionId(DocManagerHelper.CurUserHostAddress()
+ "/" + fileName + "/"
+ File.GetLastWriteTime(DocManagerHelper.StoragePath(fileName, null)).GetHashCode());
var versionList = new List<Dictionary<string, object>>();
for (var versionNum = 1; versionNum <= currentVersion; versionNum++)
{
var versionObj = new Dictionary<string, object>();
var verDir = DocManagerHelper.VersionDir(histDir, versionNum); // get the path to the given file version
var key = versionNum == currentVersion ? currentKey : File.ReadAllText(Path.Combine(verDir, "key.txt")); // get document key
versionObj.Add("key", key);
versionObj.Add("version", versionNum);
var changesPath = Path.Combine(DocManagerHelper.VersionDir(histDir, versionNum - 1), "changes.json");
if (versionNum == 1 || !File.Exists(changesPath)) // check if the version number is equal to 1
{
var infoPath = Path.Combine(histDir, "createdInfo.json"); // get meta data of this file
if (File.Exists(infoPath))
{
var info = jss.Deserialize<Dictionary<string, object>>(File.ReadAllText(infoPath));
versionObj.Add("created", info["created"]); // write meta information to the object (user information and creation date)
versionObj.Add("user", new Dictionary<string, object>()
{
{ "id", info["id"] },
{ "name", info["name"] },
});
}
}
else if (versionNum > 1) // check if the version number is greater than 1 (the file was modified)
{
// get the path to the changes.json file
var changes = jss.Deserialize<Dictionary<string, object>>(File.ReadAllText(changesPath));
var changesArray = (ArrayList)changes["changes"];
var change = changesArray.Count > 0
? (Dictionary<string, object>)changesArray[0]
: new Dictionary<string, object>();
// write information about changes to the object
versionObj.Add("changes", change.Count > 0 ? changes["changes"] : null);
versionObj.Add("serverVersion", changes["serverVersion"]);
versionObj.Add("created", change.Count > 0 ? change["created"] : null);
versionObj.Add("user", change.Count > 0 ? change["user"] : null);
}
versionList.Add(versionObj);
}
history.Add("currentVersion", currentVersion);
history.Add("history", versionList);
return history;
}
}
}

View File

@ -633,12 +633,5 @@ namespace OnlineEditorsExample
string isEnabledDirectUrl = HttpUtility.ParseQueryString(HttpContext.Current.Request.Url.Query).Get("directUrl");
return "&directUrl=" + (isEnabledDirectUrl != null ? isEnabledDirectUrl : "false");
}
// get direct url flag
public static bool IsEnabledDirectUrl()
{
string isEnabledDirectUrl = HttpUtility.ParseQueryString(HttpContext.Current.Request.Url.Query).Get("directUrl");
return isEnabledDirectUrl != null ? Convert.ToBoolean(isEnabledDirectUrl) : false;
}
}
}

View File

@ -221,49 +221,20 @@
};
if (config.editorConfig.user.id) {
config.events['onRequestHistory'] = function (event) { // the user is trying to show the document version history
let xhr = new XMLHttpRequest();
xhr.open("GET", "webeditor.ashx?type=gethistory&filename=<%= FileName %>");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send();
xhr.onload = function () {
console.log(xhr.responseText);
docEditor.refreshHistory(JSON.parse(xhr.responseText));
}
};
config.events['onRequestHistoryData'] = function (event) { // the user is trying to click the specific document version in the document version history
var ver = event.data;
let xhr = new XMLHttpRequest();
xhr.open("GET", "webeditor.ashx?type=getversiondata&filename=<%= FileName %>&version=" + ver + "&directUrl=" + !!config.document.directUrl);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send();
xhr.onload = function () {
console.log(xhr.responseText);
docEditor.setHistoryData(JSON.parse(xhr.responseText)); // send the link to the document for viewing the version history
}
};
config.events['onRequestHistoryClose'] = function () { // the user is trying to go back to the document from viewing the document version history
document.location.reload();
};
config.events['onRequestRestore'] = function (event) {
var fileName = "<%= FileName %>";
var version = event.data.version;
var data = {
fileName: fileName,
version: version
<% if (!string.IsNullOrEmpty(History) && !string.IsNullOrEmpty(HistoryData))
{ %>
config.events['onRequestHistory'] = function () { // the user is trying to show the document version history
docEditor.refreshHistory(<%= History %>); // show the document version history
};
let xhr = new XMLHttpRequest();
xhr.open("POST", "webeditor.ashx?type=restore&directUrl=" + !!config.document.directUrl);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(data));
xhr.onload = function () {
docEditor.refreshHistory(JSON.parse(xhr.responseText));
}
};
config.events['onRequestHistoryData'] = function (event) { // the user is trying to click the specific document version in the document version history
var ver = event.data;
var histData = <%= HistoryData %>;
docEditor.setHistoryData(histData[ver - 1]); // send the link to the document for viewing the version history
};
config.events['onRequestHistoryClose'] = function () { // the user is trying to go back to the document from viewing the document version history
document.location.reload();
};
<% } %>
// add mentions for not anonymous users
<% if (!string.IsNullOrEmpty(UsersForMentions))

View File

@ -17,6 +17,7 @@
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Web;
@ -60,6 +61,8 @@ namespace OnlineEditorsExample
}
protected string DocConfig { get; private set; }
protected string History { get; private set; }
protected string HistoryData { get; private set; }
protected string InsertImageConfig { get; private set; }
protected string CompareFileData { get; private set; }
protected string DataMailMergeRecipients { get; private set; }
@ -192,7 +195,7 @@ namespace OnlineEditorsExample
{
{ "title", FileName },
{ "url", getDownloadUrl(FileName) },
{ "directUrl", _Default.IsEnabledDirectUrl() ? directUrl : "" },
{ "directUrl", IsEnabledDirectUrl() ? directUrl : "" },
{ "fileType", ext.Trim('.') },
{ "key", Key },
{
@ -317,10 +320,134 @@ namespace OnlineEditorsExample
// get users for mentions
List<Dictionary<string, object>> usersData = Users.getUsersForMentions(user.id);
UsersForMentions = !user.id.Equals("uid-0") ? jss.Serialize(usersData) : null;
Dictionary<string, object> hist;
Dictionary<string, object> histData;
// get the document history
GetHistory(out hist, out histData);
if (hist != null && histData != null)
{
History = jss.Serialize(hist);
HistoryData = jss.Serialize(histData);
}
}
catch { }
}
// get the document history
private void GetHistory(out Dictionary<string, object> history, out Dictionary<string, object> historyData)
{
var storagePath = WebConfigurationManager.AppSettings["storage-path"];
var jss = new JavaScriptSerializer();
var histDir = _Default.HistoryDir(_Default.StoragePath(FileName, null));
history = null;
historyData = null;
if (_Default.GetFileVersion(histDir) > 0) // if the file was modified (the file version is greater than 0)
{
var currentVersion = _Default.GetFileVersion(histDir);
var hist = new List<Dictionary<string, object>>();
var histData = new Dictionary<string, object>();
for (var i = 1; i <= currentVersion; i++) // run through all the file versions
{
var obj = new Dictionary<string, object>();
var dataObj = new Dictionary<string, object>();
var verDir = _Default.VersionDir(histDir, i); // get the path to the given file version
var key = i == currentVersion ? Key : File.ReadAllText(Path.Combine(verDir, "key.txt")); // get document key
obj.Add("key", key);
obj.Add("version", i);
if (i == 1) // check if the version number is equal to 1
{
var infoPath = Path.Combine(histDir, "createdInfo.json"); // get meta data of this file
if (File.Exists(infoPath)) {
var info = jss.Deserialize<Dictionary<string, object>>(File.ReadAllText(infoPath));
obj.Add("created", info["created"]); // write meta information to the object (user information and creation date)
obj.Add("user", new Dictionary<string, object>() {
{ "id", info["id"] },
{ "name", info["name"] },
});
}
}
var ext = Path.GetExtension(FileName).ToLower();
dataObj.Add("fileType", ext.Replace(".", ""));
dataObj.Add("key", key);
// write file url to the data object
var directPrevFileUrl = i == currentVersion ? _Default.FileUri(FileName, false) : MakePublicHistoryUrl(FileName, i.ToString(), "prev" + ext, false);
var prevFileUrl = i == currentVersion ? FileUri : MakePublicHistoryUrl(FileName, i.ToString(), "prev" + ext);
if (Path.IsPathRooted(storagePath))
{
prevFileUrl = i == currentVersion ? getDownloadUrl(FileName) : getDownloadUrl(Directory.GetFiles(verDir, "prev.*")[0].Replace(storagePath + "\\", ""));
directPrevFileUrl = i == currentVersion ? getDownloadUrl(FileName, false) : getDownloadUrl(Directory.GetFiles(verDir, "prev.*")[0].Replace(storagePath + "\\", ""), false);
}
dataObj.Add("url", prevFileUrl); // write file url to the data object
if (IsEnabledDirectUrl())
{
dataObj.Add("directUrl", directPrevFileUrl); // write direct url to the data object
}
dataObj.Add("version", i);
if (i > 1) // check if the version number is greater than 1 (the file was modified)
{
// get the path to the changes.json file
var changes = jss.Deserialize<Dictionary<string, object>>(File.ReadAllText(Path.Combine(_Default.VersionDir(histDir, i - 1), "changes.json")));
var changesArray = (ArrayList)changes["changes"];
var change = changesArray.Count > 0
? (Dictionary<string, object>)changesArray[0]
: new Dictionary<string, object>();
// write information about changes to the object
obj.Add("changes", change.Count > 0 ? changes["changes"] : null);
obj.Add("serverVersion", changes["serverVersion"]);
obj.Add("created", change.Count > 0 ? change["created"] : null);
obj.Add("user", change.Count > 0 ? change["user"] : null);
var prev = (Dictionary<string, object>)histData[(i - 2).ToString()]; // get the history data from the previous file version
Dictionary<string, object> dataPrev = new Dictionary<string, object>() { // write information about previous file version to the data object
{ "fileType", prev["fileType"] },
{ "key", prev["key"] }, // write key and url information about previous file version
{ "url", prev["url"] }
};
if (IsEnabledDirectUrl())
{
dataPrev.Add("directUrl", prev["directUrl"]);
}
dataObj.Add("previous", dataPrev);
// write the path to the diff.zip archive with differences in this file version
var changesUrl = MakePublicHistoryUrl(FileName, (i - 1).ToString(), "diff.zip");
dataObj.Add("changesUrl", changesUrl);
}
if (JwtManager.Enabled)
{
var token = JwtManager.Encode(dataObj);
dataObj.Add("token", token);
}
hist.Add(obj); // add object dictionary to the hist list
histData.Add((i - 1).ToString(), dataObj); // write data object information to the history data
}
// write history information about the current file version to the history object
history = new Dictionary<string, object>()
{
{ "currentVersion", currentVersion },
{ "history", hist }
};
historyData = histData;
}
}
// get a logo config
private Dictionary<string, object> GetLogoConfig()
{
@ -342,7 +469,7 @@ namespace OnlineEditorsExample
{ "url", InsertImageUrl.ToString()}
};
if (_Default.IsEnabledDirectUrl())
if (IsEnabledDirectUrl())
{
logoConfig.Add("directUrl", DirectImageUrl.ToString());
}
@ -379,7 +506,7 @@ namespace OnlineEditorsExample
{ "url", compareFileUrl.ToString() }
};
if (_Default.IsEnabledDirectUrl())
if (IsEnabledDirectUrl())
{
dataCompareFile.Add("directUrl", DirectFileUrl.ToString());
}
@ -418,7 +545,7 @@ namespace OnlineEditorsExample
{ "url", mailmergeUrl.ToString() }
};
if (_Default.IsEnabledDirectUrl())
if (IsEnabledDirectUrl())
{
mailMergeConfig.Add("directUrl", DirectMailMergeUrl.ToString());
}
@ -462,6 +589,21 @@ namespace OnlineEditorsExample
return _Default.GetServerUrl(true) + fullPath.Substring(root.Length).Replace(Path.DirectorySeparatorChar, '/');
}
// create the public history url
private string MakePublicHistoryUrl(string filename, string version, string file, Boolean isServer = true)
{
var userAddress = isServer ? "&userAddress=" + HttpUtility.UrlEncode(_Default.CurUserHostAddress(HttpContext.Current.Request.UserHostAddress)) : "";
var fileUrl = new UriBuilder(_Default.GetServerUrl(isServer));
fileUrl.Path = HttpRuntime.AppDomainAppVirtualPath
+ (HttpRuntime.AppDomainAppVirtualPath.EndsWith("/") ? "" : "/")
+ "webeditor.ashx";
fileUrl.Query = "type=downloadhistory&fileName=" + HttpUtility.UrlEncode(filename)
+ "&ver=" + version + "&file=" + file
+ userAddress;
return fileUrl.ToString();
}
// create demo document
private static void Try(string type, string sample, HttpRequest request)
{
@ -510,5 +652,12 @@ namespace OnlineEditorsExample
{ "name", uname }
}));
}
// get direct url flag
private static bool IsEnabledDirectUrl()
{
string isEnabledDirectUrl = HttpUtility.ParseQueryString(HttpContext.Current.Request.Url.Query).Get("directUrl");
return isEnabledDirectUrl != null ? Convert.ToBoolean(isEnabledDirectUrl) : false;
}
}
}

View File

@ -26,9 +26,7 @@ using System.Diagnostics;
using System.Web.Configuration;
using System.Linq;
using System.Net;
using System.Collections;
using System.Net.Sockets;
using ASC.Api.DocumentConverter;
namespace OnlineEditorsExample
{
@ -50,15 +48,6 @@ namespace OnlineEditorsExample
case "downloadhistory":
DownloadHistory(context);
break;
case "gethistory":
GetHistory(context);
break;
case "getversiondata":
GetVersionData(context);
break;
case "restore":
Restore(context);
break;
case "convert":
Convert(context);
break;
@ -344,164 +333,6 @@ namespace OnlineEditorsExample
get { return false; }
}
private static void GetHistory(HttpContext context)
{
var jss = new JavaScriptSerializer();
var fileName = context.Request["filename"];
var history = GetHistory(fileName);
context.Response.Write(jss.Serialize(history));
}
private static void GetVersionData(HttpContext context)
{
var storagePath = WebConfigurationManager.AppSettings["storage-path"];
var jss = new JavaScriptSerializer();
var fileName = context.Request["filename"];
int version;
if (!int.TryParse(context.Request["version"], out version))
{
context.Response.Write("{ \"error\": \"Version number invalid!\"}");
return;
}
var versionData = new Dictionary<string, object>();
var histDir = _Default.HistoryDir(_Default.StoragePath(fileName, null));
var lastVersion = _Default.GetFileVersion(histDir);
var verDir = _Default.VersionDir(histDir, version);
var lastVersionUri = _Default.FileUri(fileName, true);
var key = version == lastVersion
? ServiceConverter.GenerateRevisionId(_Default.CurUserHostAddress(null)
+ "/" + Path.GetFileName(lastVersionUri)
+ "/" + File.GetLastWriteTime(_Default.StoragePath(fileName, null)).GetHashCode())
: File.ReadAllText(Path.Combine(verDir, "key.txt"));
var ext = Path.GetExtension(fileName).ToLower();
versionData.Add("fileType", ext.Replace(".", ""));
versionData.Add("key", key);
var directPrevFileUrl = version == lastVersion ? _Default.FileUri(fileName, false) : MakePublicHistoryUrl(fileName, version.ToString(), "prev" + ext, false);
var prevFileUrl = version == lastVersion ? lastVersionUri : MakePublicHistoryUrl(fileName, version.ToString(), "prev" + ext);
if (Path.IsPathRooted(storagePath))
{
prevFileUrl = version == lastVersion ? DocEditor.getDownloadUrl(fileName) : DocEditor.getDownloadUrl(Directory.GetFiles(verDir, "prev.*")[0].Replace(storagePath + "\\", ""));
directPrevFileUrl = version == lastVersion ? DocEditor.getDownloadUrl(fileName, false) : DocEditor.getDownloadUrl(Directory.GetFiles(verDir, "prev.*")[0].Replace(storagePath + "\\", ""), false);
}
versionData.Add("url", prevFileUrl);
if (_Default.IsEnabledDirectUrl())
{
versionData.Add("directUrl", directPrevFileUrl); // write direct url to the data object
}
versionData.Add("version", version);
if (version > 1)
{
var prevVerDir = _Default.VersionDir(histDir, version - 1);
var prevUrl = MakePublicHistoryUrl(fileName, (version - 1).ToString(), "prev" + ext);
if (Path.IsPathRooted(storagePath))
prevUrl = DocEditor.getDownloadUrl(Directory.GetFiles(prevVerDir, "prev.*")[0].Replace(storagePath + "\\", ""));
var prevKey = File.ReadAllText(Path.Combine(prevVerDir, "key.txt"));
Dictionary<string, object> dataPrev = new Dictionary<string, object>() { // write information about previous file version to the data object
{ "fileType", ext.Replace(".", "") },
{ "key", prevKey }, // write key and url information about previous file version
{ "url", prevUrl }
};
string directPrevUrl;
if (_Default.IsEnabledDirectUrl())
{
directPrevUrl = Path.IsPathRooted(storagePath)
? DocEditor.getDownloadUrl(Directory.GetFiles(prevVerDir, "prev.*")[0].Replace(storagePath + "\\", ""), false)
: MakePublicHistoryUrl(fileName, (version - 1).ToString(), "prev" + ext, false);
dataPrev.Add("directUrl", directPrevUrl); // write direct url to the data object
}
versionData.Add("previous", dataPrev);
if (File.Exists(Path.Combine(prevVerDir, "diff.zip")))
{
var changesUrl = MakePublicHistoryUrl(fileName, (version - 1).ToString(), "diff.zip");
versionData.Add("changesUrl", changesUrl);
}
}
if (JwtManager.Enabled)
{
var token = JwtManager.Encode(versionData);
versionData.Add("token", token);
}
context.Response.Write(jss.Serialize(versionData));
}
private void Restore(HttpContext context)
{
string fileData;
try
{
using (var receiveStream = context.Request.InputStream)
using (var readStream = new StreamReader(receiveStream))
{
fileData = readStream.ReadToEnd();
if (string.IsNullOrEmpty(fileData)) return;
}
}
catch (Exception e)
{
throw new HttpException((int)HttpStatusCode.BadRequest, e.Message);
}
var jss = new JavaScriptSerializer();
var body = jss.Deserialize<Dictionary<string, object>>(fileData);
var fileName = (string)body["fileName"];
var version = (int)body["version"];
var lastVersionUri = _Default.FileUri(fileName, true);
var key = ServiceConverter.GenerateRevisionId(_Default.CurUserHostAddress(null)
+ "/" + Path.GetFileName(lastVersionUri)
+ "/" + File.GetLastWriteTime(_Default.StoragePath(fileName, null)).GetHashCode());
var histDir = _Default.HistoryDir(_Default.StoragePath(fileName, null));
var currentVersionDir = _Default.VersionDir(histDir, _Default.GetFileVersion(histDir));
var verDir = _Default.VersionDir(histDir, version);
if (!Directory.Exists(currentVersionDir)) Directory.CreateDirectory(currentVersionDir);
var ext = Path.GetExtension(fileName).ToLower();
File.Copy(_Default.StoragePath(fileName, null), Path.Combine(currentVersionDir, "prev" + ext));
File.WriteAllText(Path.Combine(currentVersionDir, "key.txt"), key);
var changesPath = Path.Combine(_Default.VersionDir(histDir, version - 1), "changes.json");
if (File.Exists(changesPath))
{
File.Copy(changesPath, Path.Combine(currentVersionDir, "changes.json"));
}
File.Copy(Path.Combine(verDir, "prev" + ext), _Default.StoragePath(fileName, null), true);
var fileInfo = new FileInfo(_Default.StoragePath(fileName, null));
fileInfo.LastWriteTimeUtc = DateTime.UtcNow;
var history = GetHistory(fileName);
context.Response.Write(jss.Serialize(history));
}
private static void DownloadHistory(HttpContext context)
{
try
@ -673,84 +504,5 @@ namespace OnlineEditorsExample
context.Response.Write(jss.Serialize(data));
}
// get the document history
private static Dictionary<string, object> GetHistory(string fileName)
{
var jss = new JavaScriptSerializer();
var histDir = _Default.HistoryDir(_Default.StoragePath(fileName, null));
var history = new Dictionary<string, object>();
var currentVersion = _Default.GetFileVersion(histDir);
var currentFileUri = _Default.FileUri(fileName, true);
var currentKey = ServiceConverter.GenerateRevisionId(_Default.CurUserHostAddress(null)
+ "/" + Path.GetFileName(currentFileUri)
+ "/" + File.GetLastWriteTime(_Default.StoragePath(fileName, null)).GetHashCode());
var versionList = new List<Dictionary<string, object>>();
for (var versionNum = 1; versionNum <= currentVersion; versionNum++)
{
var versionObj = new Dictionary<string, object>();
var verDir = _Default.VersionDir(histDir, versionNum); // get the path to the given file version
var key = versionNum == currentVersion ? currentKey : File.ReadAllText(Path.Combine(verDir, "key.txt")); // get document key
versionObj.Add("key", key);
versionObj.Add("version", versionNum);
var changesPath = Path.Combine(_Default.VersionDir(histDir, versionNum - 1), "changes.json");
if (versionNum == 1 || !File.Exists(changesPath)) // check if the version number is equal to 1
{
var infoPath = Path.Combine(histDir, "createdInfo.json"); // get meta data of this file
if (File.Exists(infoPath))
{
var info = jss.Deserialize<Dictionary<string, object>>(File.ReadAllText(infoPath));
versionObj.Add("created", info["created"]); // write meta information to the object (user information and creation date)
versionObj.Add("user", new Dictionary<string, object>()
{
{ "id", info["id"] },
{ "name", info["name"] },
});
}
}
else if (versionNum > 1) // check if the version number is greater than 1 (the file was modified)
{
// get the path to the changes.json file
var changes = jss.Deserialize<Dictionary<string, object>>(File.ReadAllText(changesPath));
var changesArray = (ArrayList)changes["changes"];
var change = changesArray.Count > 0
? (Dictionary<string, object>)changesArray[0]
: new Dictionary<string, object>();
// write information about changes to the object
versionObj.Add("changes", change.Count > 0 ? changes["changes"] : null);
versionObj.Add("serverVersion", changes["serverVersion"]);
versionObj.Add("created", change.Count > 0 ? change["created"] : null);
versionObj.Add("user", change.Count > 0 ? change["user"] : null);
}
versionList.Add(versionObj);
}
history.Add("currentVersion", currentVersion);
history.Add("history", versionList);
return history;
}
// create the public history url
private static string MakePublicHistoryUrl(string filename, string version, string file, Boolean isServer = true)
{
var userAddress = isServer ? "&userAddress=" + HttpUtility.UrlEncode(_Default.CurUserHostAddress(HttpContext.Current.Request.UserHostAddress)) : "";
var fileUrl = new UriBuilder(_Default.GetServerUrl(isServer));
fileUrl.Path = HttpRuntime.AppDomainAppVirtualPath
+ (HttpRuntime.AppDomainAppVirtualPath.EndsWith("/") ? "" : "/")
+ "webeditor.ashx";
fileUrl.Query = "type=downloadhistory&fileName=" + HttpUtility.UrlEncode(filename)
+ "&ver=" + version + "&file=" + file
+ userAddress;
return fileUrl.ToString();
}
}
}

View File

@ -35,8 +35,6 @@ import com.onlyoffice.integration.documentserver.util.file.FileUtility;
import com.onlyoffice.integration.documentserver.util.service.ServiceConverter;
import com.onlyoffice.integration.documentserver.managers.document.DocumentManager;
import com.onlyoffice.integration.documentserver.managers.callback.CallbackManager;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.springframework.beans.factory.annotation.Autowired;
@ -51,7 +49,6 @@ import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@ -59,20 +56,14 @@ import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@ -364,13 +355,13 @@ public class FileController {
@GetMapping("/assets")
public ResponseEntity<Resource> assets(@RequestParam("name")
final String name) { // get sample files from the assests
String fileName = Path.of("assets", "document-templates", "sample", fileUtility.getFileName(name)).toString();
String fileName = Path.of("assets", "sample", fileUtility.getFileName(name)).toString();
return downloadFile(fileName);
}
@GetMapping("/csv")
public ResponseEntity<Resource> csv() { // download a csv file
String fileName = Path.of("assets", "document-templates", "sample", "csv.csv").toString();
String fileName = Path.of("assets", "sample", "csv.csv").toString();
return downloadFile(fileName);
}
@ -537,88 +528,4 @@ public class FileController {
return "{ \"error\" : 1, \"message\" : \"" + e.getMessage() + "\"}";
}
}
@PutMapping("/restore")
@ResponseBody
public String restore(@RequestBody final JSONObject body) {
try {
String sourceBasename = (String) body.get("fileName");
Integer version = (Integer) body.get("version");
String userID = (String) body.get("userId");
String sourceStringFile = storagePathBuilder.getFileLocation(sourceBasename);
File sourceFile = new File(sourceStringFile);
Path sourcePathFile = sourceFile.toPath();
String historyDirectory = storagePathBuilder.getHistoryDir(sourcePathFile.toString());
Integer bumpedVersion = storagePathBuilder.getFileVersion(historyDirectory, false);
String bumpedVersionStringDirectory = documentManager.versionDir(historyDirectory, bumpedVersion, true);
File bumpedVersionDirectory = new File(bumpedVersionStringDirectory);
if (!bumpedVersionDirectory.exists()) {
bumpedVersionDirectory.mkdir();
}
Path bumpedKeyPathFile = Paths.get(bumpedVersionStringDirectory, "key.txt");
String bumpedKeyStringFile = bumpedKeyPathFile.toString();
File bumpedKeyFile = new File(bumpedKeyStringFile);
String bumpedKey = serviceConverter.generateRevisionId(
storagePathBuilder.getStorageLocation()
+ "/"
+ sourceBasename
+ "/"
+ Long.toString(sourceFile.lastModified())
);
FileWriter bumpedKeyFileWriter = new FileWriter(bumpedKeyFile);
bumpedKeyFileWriter.write(bumpedKey);
bumpedKeyFileWriter.close();
Integer userInnerID = Integer.parseInt(userID.replace("uid-", ""));
User user = userService.findUserById(userInnerID).get();
Path bumpedChangesPathFile = Paths.get(bumpedVersionStringDirectory, "changes.json");
String bumpedChangesStringFile = bumpedChangesPathFile.toString();
File bumpedChangesFile = new File(bumpedChangesStringFile);
JSONObject bumpedChangesUser = new JSONObject();
// Don't add the `uid-` prefix.
// https://github.com/ONLYOFFICE/document-server-integration/issues/437#issuecomment-1663526562
bumpedChangesUser.put("id", user.getId());
bumpedChangesUser.put("name", user.getName());
JSONObject bumpedChangesChangesItem = new JSONObject();
bumpedChangesChangesItem.put("created", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
bumpedChangesChangesItem.put("user", bumpedChangesUser);
JSONArray bumpedChangesChanges = new JSONArray();
bumpedChangesChanges.add(bumpedChangesChangesItem);
JSONObject bumpedChanges = new JSONObject();
bumpedChanges.put("serverVersion", null);
bumpedChanges.put("changes", bumpedChangesChanges);
String bumpedChangesContent = bumpedChanges.toJSONString();
FileWriter bumpedChangesFileWriter = new FileWriter(bumpedChangesFile);
bumpedChangesFileWriter.write(bumpedChangesContent);
bumpedChangesFileWriter.close();
String sourceExtension = fileUtility.getFileExtension(sourceBasename);
String previousBasename = "prev" + sourceExtension;
Path bumpedFile = Paths.get(bumpedVersionStringDirectory, previousBasename);
Files.move(sourcePathFile, bumpedFile);
String recoveryVersionStringDirectory = documentManager.versionDir(historyDirectory, version, true);
Path recoveryPathFile = Paths.get(recoveryVersionStringDirectory, previousBasename);
String recoveryStringFile = recoveryPathFile.toString();
FileInputStream recoveryStream = new FileInputStream(recoveryStringFile);
storageMutator.createFile(sourcePathFile, recoveryStream);
recoveryStream.close();
JSONObject responseBody = new JSONObject();
responseBody.put("error", null);
responseBody.put("success", true);
return responseBody.toJSONString();
} catch (Exception error) {
error.printStackTrace();
JSONObject responseBody = new JSONObject();
responseBody.put("error", error.getMessage());
responseBody.put("success", false);
return responseBody.toJSONString();
}
}
}

View File

@ -217,14 +217,8 @@ public class DefaultDocumentManager implements DocumentManager {
public String createDemo(final String fileExt, final Boolean sample, final String uid, final String uname) {
String demoName = (sample ? "sample." : "new.")
+ fileExt; // create sample or new template file with the necessary extension
String demoPath =
"assets"
+ File.separator
+ "document-templates"
+ File.separator
+ (sample ? "sample" : "new")
+ File.separator
+ demoName;
String demoPath = "assets" + File.separator + (sample ? "sample" : "new")
+ File.separator + demoName; // get the path to the sample document
// get a file name with an index if the file with such a name already exists
String fileName = getCorrectName(demoName);

View File

@ -172,28 +172,6 @@
}
};
function onRequestRestore(event) {
const query = new URLSearchParams(window.location.search)
const config = [[${model}]]
const payload = {
fileName: query.get('fileName'),
version: event.data.version,
userId: config.editorConfig.user.id
}
const request = new XMLHttpRequest()
request.open('PUT', 'restore')
request.setRequestHeader('Content-Type', 'application/json')
request.send(JSON.stringify(payload))
request.onload = function () {
if (request.status != 200) {
response = JSON.parse(request.response)
innerAlert(response.error)
return
}
document.location.reload()
}
}
config.width = "100%";
config.height = "100%";
config.events = {
@ -206,7 +184,6 @@
"onRequestInsertImage": onRequestInsertImage,
"onRequestCompareFile": onRequestCompareFile,
"onRequestMailMergeRecipients": onRequestMailMergeRecipients,
"onRequestRestore": onRequestRestore
};
var histArray = [[${fileHistory}]];

View File

@ -48,7 +48,6 @@ import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
@ -58,9 +57,7 @@ import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
@ -129,9 +126,6 @@ public class IndexServlet extends HttpServlet {
case "reference":
reference(request, response, writer);
break;
case "restore":
restore(request, response, writer);
break;
default:
break;
}
@ -457,7 +451,7 @@ public class IndexServlet extends HttpServlet {
private static void csv(final HttpServletRequest request,
final HttpServletResponse response,
final PrintWriter writer) {
String fileName = "assets/document-templates/sample/csv.csv";
String fileName = "assets/sample/csv.csv";
URL fileUrl = Thread.currentThread().getContextClassLoader().getResource(fileName);
Path filePath = null;
try {
@ -472,7 +466,7 @@ public class IndexServlet extends HttpServlet {
private static void assets(final HttpServletRequest request,
final HttpServletResponse response,
final PrintWriter writer) {
String fileName = "assets/document-templates/sample/" + FileUtility.getFileName(request.getParameter("name"));
String fileName = "assets/sample/" + FileUtility.getFileName(request.getParameter("name"));
URL fileUrl = Thread.currentThread().getContextClassLoader().getResource(fileName);
Path filePath = null;
try {
@ -734,97 +728,6 @@ public class IndexServlet extends HttpServlet {
}
}
private static void restore(final HttpServletRequest request,
final HttpServletResponse response,
final PrintWriter writer) {
try {
Scanner scanner = new Scanner(request.getInputStream());
scanner.useDelimiter("\\A");
String bodyString = scanner.hasNext() ? scanner.next() : "";
scanner.close();
JSONParser parser = new JSONParser();
JSONObject body = (JSONObject) parser.parse(bodyString);
String sourceBasename = (String) body.get("fileName");
Integer version = ((Long) body.get("version")).intValue();
String userID = (String) body.get("userId");
String sourceStringFile = DocumentManager.storagePath(sourceBasename, null);
File sourceFile = new File(sourceStringFile);
Path sourcePathFile = sourceFile.toPath();
String historyDirectory = DocumentManager.historyDir(sourceStringFile);
Integer bumpedVersion = DocumentManager.getFileVersion(historyDirectory);
String bumpedVersionStringDirectory = DocumentManager.versionDir(historyDirectory, bumpedVersion);
File bumpedVersionDirectory = new File(bumpedVersionStringDirectory);
if (!bumpedVersionDirectory.exists()) {
bumpedVersionDirectory.mkdir();
}
Path bumpedKeyPathFile = Paths.get(bumpedVersionStringDirectory, "key.txt");
String bumpedKeyStringFile = bumpedKeyPathFile.toString();
File bumpedKeyFile = new File(bumpedKeyStringFile);
String bumpedKey = ServiceConverter.generateRevisionId(
DocumentManager.curUserHostAddress(null)
+ "/"
+ sourceBasename
+ "/"
+ Long.toString(sourceFile.lastModified())
);
FileWriter bumpedKeyFileWriter = new FileWriter(bumpedKeyFile);
bumpedKeyFileWriter.write(bumpedKey);
bumpedKeyFileWriter.close();
User user = Users.getUser(userID);
Path bumpedChangesPathFile = Paths.get(bumpedVersionStringDirectory, "changes.json");
String bumpedChangesStringFile = bumpedChangesPathFile.toString();
File bumpedChangesFile = new File(bumpedChangesStringFile);
JSONObject bumpedChangesUser = new JSONObject();
bumpedChangesUser.put("id", user.getId());
bumpedChangesUser.put("name", user.getName());
JSONObject bumpedChangesChangesItem = new JSONObject();
bumpedChangesChangesItem.put("created", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
bumpedChangesChangesItem.put("user", bumpedChangesUser);
JSONArray bumpedChangesChanges = new JSONArray();
bumpedChangesChanges.add(bumpedChangesChangesItem);
JSONObject bumpedChanges = new JSONObject();
bumpedChanges.put("serverVersion", null);
bumpedChanges.put("changes", bumpedChangesChanges);
String bumpedChangesContent = bumpedChanges.toJSONString();
FileWriter bumpedChangesFileWriter = new FileWriter(bumpedChangesFile);
bumpedChangesFileWriter.write(bumpedChangesContent);
bumpedChangesFileWriter.close();
String sourceExtension = FileUtility.getFileExtension(sourceBasename);
String previousBasename = "prev" + sourceExtension;
Path bumpedFile = Paths.get(bumpedVersionStringDirectory, previousBasename);
Files.move(sourcePathFile, bumpedFile);
String recoveryVersionStringDirectory = DocumentManager.versionDir(historyDirectory, version);
Path recoveryPathFile = Paths.get(recoveryVersionStringDirectory, previousBasename);
String recoveryStringFile = recoveryPathFile.toString();
FileInputStream recoveryStream = new FileInputStream(recoveryStringFile);
DocumentManager.createFile(sourcePathFile, recoveryStream);
recoveryStream.close();
JSONObject responseBody = new JSONObject();
responseBody.put("error", null);
responseBody.put("success", true);
String responseContent = responseBody.toJSONString();
writer.write(responseContent);
} catch (Exception error) {
error.printStackTrace();
JSONObject responseBody = new JSONObject();
responseBody.put("error", error.getMessage());
responseBody.put("success", false);
String responseContent = responseBody.toJSONString();
writer.write(responseContent);
}
}
// process get request
@Override
protected void doGet(final HttpServletRequest request,
@ -839,12 +742,6 @@ public class IndexServlet extends HttpServlet {
processRequest(request, response);
}
@Override
protected void doPut(final HttpServletRequest request,
final HttpServletResponse response) throws ServletException, IOException {
processRequest(request, response);
}
// get servlet information
@Override
public String getServletInfo() {

View File

@ -306,12 +306,7 @@ public final class DocumentManager {
String demoName = (sample ? "sample." : "new.") + fileExt;
// get the path to the sample document
String demoPath = "assets"
+ File.separator
+ "document-templates"
+ File.separator
+ (sample ? "sample" : "new")
+ File.separator;
String demoPath = "assets" + File.separator + (sample ? "sample" : "new") + File.separator;
// get a file name with an index if the file with such a name already exists
String fileName = getCorrectName(demoName, null);

View File

@ -173,26 +173,6 @@
}
};
function onRequestRestore(event) {
const query = new URLSearchParams(window.location.search)
const payload = {
fileName: query.get('fileName'),
version: event.data.version,
userId: config.editorConfig.user.id
}
const request = new XMLHttpRequest()
request.open('PUT', 'IndexServlet?type=restore')
request.send(JSON.stringify(payload))
request.onload = function () {
if (request.status != 200) {
response = JSON.parse(request.response)
innerAlert(response.error)
return
}
document.location.reload()
}
}
config = JSON.parse('<%= FileModel.serialize(Model) %>');
config.width = "100%";
config.height = "100%";
@ -206,7 +186,6 @@
"onRequestInsertImage": onRequestInsertImage,
"onRequestCompareFile": onRequestCompareFile,
"onRequestMailMergeRecipients": onRequestMailMergeRecipients,
"onRequestRestore": onRequestRestore
};
<%

View File

@ -1,4 +1,4 @@
/**
/**
*
* (c) Copyright Ascensio System SIA 2023
*
@ -521,14 +521,12 @@ app.post('/reference', (req, res) => { // define a handler for renaming file
const data = {
fileType: fileUtility.getFileExtension(fileName).slice(1),
key: req.DocManager.getKey(fileName),
url: req.DocManager.getDownloadUrl(fileName, true),
directUrl: req.body.directUrl ? req.DocManager.getDownloadUrl(fileName) : null,
referenceData: {
fileKey: JSON.stringify({ fileName, userAddress: req.DocManager.curUserHostAddress() }),
instanceId: req.DocManager.getServerUrl(),
},
link: `${req.DocManager.getServerUrl()}/editor?fileName=${encodeURIComponent(fileName)}`,
path: fileName,
};
@ -551,16 +549,13 @@ app.put('/restore', (req, res) => { // define a handler for restore file version
const filePath = req.DocManager.storagePath(fileName, userAddress);
const historyPath = req.DocManager.historyPath(fileName, userAddress);
const newVersion = req.DocManager.countVersion(historyPath) + 1;
const versionPath = path.join(`${historyPath}`, `${version}`, `prev${fileUtility.getFileExtension(fileName)}`);
const newVersionPath = path.join(`${historyPath}`, `${newVersion}`);
const versionPath = `${historyPath}\\${version}\\prev${fileUtility.getFileExtension(fileName)}`;
const newVersionPath = `${historyPath}\\${newVersion}`;
if (fileSystem.existsSync(versionPath)) {
req.DocManager.createDirectory(newVersionPath);
req.DocManager.copyFile(
filePath,
path.join(`${newVersionPath}`, `prev${fileUtility.getFileExtension(fileName)}`),
);
fileSystem.writeFileSync(path.join(`${newVersionPath}`, 'key.txt'), key);
req.DocManager.copyFile(filePath, `${newVersionPath}\\prev${fileUtility.getFileExtension(fileName)}`);
fileSystem.writeFileSync(`${newVersionPath}\\key.txt`, key);
req.DocManager.copyFile(versionPath, filePath);
result.success = true;
} else {
@ -687,6 +682,7 @@ app.post('/track', async (req, res) => { // define a handler for tracking file c
try {
const resp = documentService.getResponseUri(data);
await callbackProcessSave(resp.uri, body, fileName, userAddress, fileName);
return;
} catch (ex) {
console.log(ex);
await callbackProcessSave(downloadUri, body, fileName, userAddress, newFileName);
@ -715,22 +711,18 @@ app.post('/track', async (req, res) => { // define a handler for tracking file c
const downloadExt = `.${body.fileType}`;
const isSubmitForm = body.forcesavetype === 3; // SubmitForm
let correctName = fileName;
let correctName = '';
let forcesavePath = '';
if (isSubmitForm) {
// new file
if (newFileName) {
correctName = req.DocManager.getCorrectName(
`${fileUtility.getFileName(fileName, true)}-form${downloadExt}`,
userAddress,
);
correctName = req.DocManager.getCorrectName(`${fileUtility.getFileName(fileName, true)}
-form${downloadExt}`, userAddress);
} else {
const ext = fileUtility.getFileExtension(fileName);
correctName = req.DocManager.getCorrectName(
`${fileUtility.getFileName(fileName, true)}-form${ext}`,
userAddress,
);
correctName = req.DocManager.getCorrectName(`${fileUtility.getFileName(fileName, true)}
-form${ext}`, userAddress);
}
forcesavePath = req.DocManager.storagePath(correctName, userAddress);
} else {
@ -786,6 +778,7 @@ app.post('/track', async (req, res) => { // define a handler for tracking file c
try {
const resp = documentService.getResponseUri(data);
await callbackProcessForceSave(resp.uri, body, fileName, userAddress, false);
return;
} catch (ex) {
console.log(ex);
await callbackProcessForceSave(downloadUri, body, fileName, userAddress, true);
@ -954,7 +947,7 @@ app.get('/editor', (req, res) => { // define a handler for editing document
if (!canEdit && mode === 'edit') {
mode = 'view';
}
const submitForm = mode === 'fillForms' && userid === 'uid-1';
const submitForm = mode === 'fillForms' && userid === 'uid-1' && !1;
// file config data
const argss = {
@ -1019,7 +1012,7 @@ app.get('/editor', (req, res) => { // define a handler for editing document
? null
: `${req.DocManager.getServerUrl()}/assets/document-templates/sample/sample.docx`,
},
dataSpreadsheet: {
dataMailMergeRecipients: {
fileType: 'csv',
url: `${req.DocManager.getServerUrl(true)}/csv`,
directUrl: !userDirectUrl ? null : `${req.DocManager.getServerUrl()}/csv`,
@ -1049,8 +1042,8 @@ app.get('/editor', (req, res) => { // define a handler for editing document
cfgSignatureSecret,
{ expiresIn: cfgSignatureSecretExpiresIn },
);
argss.dataSpreadsheet.token = jwt.sign(
argss.dataSpreadsheet,
argss.dataMailMergeRecipients.token = jwt.sign(
argss.dataMailMergeRecipients,
cfgSignatureSecret,
{ expiresIn: cfgSignatureSecretExpiresIn },
);

View File

@ -389,14 +389,14 @@ DocManager.prototype.getTemplateImageUrl = function getTemplateImageUrl(fileType
}
if (fileType === fileUtility.fileType.cell) { // for cell type
return `${serverUrl}/images/file_xlsx.svg`;
return `${path}/images/file_xlsx.svg`;
}
if (fileType === fileUtility.fileType.slide) { // for slide type
return `${serverUrl}/images/file_pptx.svg`;
return `${path}/images/file_pptx.svg`;
}
return `${serverUrl}/images/file_docx.svg`; // the default value
return `${path}/images/file_docx.svg`; // the default value
};
// get document key

View File

@ -90,7 +90,9 @@ fileUtility.getFillExtensions = function getFillExtensions() {
fileUtility.getConvertExtensions = function getConvertExtensions() {
return supportedFormats.filter(
(format) => format.actions.includes('auto-convert'),
(format) => (format.type === 'word' && format.convert.includes('docx'))
|| (format.type === 'cell' && format.convert.includes('xlsx'))
|| (format.type === 'slide' && format.convert.includes('pptx')),
).reduce((extensions, format) => [...extensions, format.name], []);
};

View File

@ -52,7 +52,7 @@ const descrUser1 = [
'The file favorite state is undefined',
'Can create files from templates using data from the editor',
'Can see the information about all users',
'Can submit forms',
// "Can submit forms"
];
const descrUser2 = [
@ -62,7 +62,7 @@ const descrUser2 = [
'This file is marked as favorite',
'Can create new files from the editor',
'Can see the information about users from Group2 and users who dont belong to any group',
'Cant submit forms',
// "Cant submit forms"
];
const descrUser3 = [
@ -75,7 +75,7 @@ const descrUser3 = [
'Cant print the file',
'Can create new files from the editor',
'Can see the information about Group2 users',
'Cant submit forms',
// "Cant submit forms"
];
const descrUser0 = [
@ -91,7 +91,7 @@ const descrUser0 = [
'Can\'t view chat',
'Can\'t protect file',
'View file without collaboration',
'Cant submit forms',
// "Cant submit forms"
];
const users = [

View File

@ -1,12 +1,12 @@
{
"name": "OnlineEditorsExampleNodeJS",
"version": "1.6.0",
"version": "4.1.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "OnlineEditorsExampleNodeJS",
"version": "1.6.0",
"version": "4.1.0",
"license": "Apache",
"dependencies": {
"body-parser": "^1.19.0",

View File

@ -172,19 +172,18 @@
};
var onRequestInsertImage = function(event) { // the user is trying to insert an image by clicking the Image from Storage button
var data = <%- JSON.stringify(dataInsertImage) %>;
data.c = event.data.c
docEditor.insertImage(data); // insert an image into the file
docEditor.insertImage({ // insert an image into the file
"c": event.data.c,
<%- JSON.stringify(dataInsertImage).substring(1, JSON.stringify(dataInsertImage).length - 1)%>
})
};
var onRequestCompareFile = function() { // the user is trying to select document for comparing by clicking the Document from Storage button
docEditor.setRevisedFile(<%- JSON.stringify(dataCompareFile) %>); // select a document for comparing
};
var onRequestSelectSpreadsheet = function (event) { // the user is trying to select recipients data by clicking the Mail merge button
var data = <%- JSON.stringify(dataSpreadsheet) %>;
data.c = event.data.c;
docEditor.setRequestedSpreadsheet(data); // insert recipient data for mail merge into the file
var onRequestMailMergeRecipients = function (event) { // the user is trying to select recipients data by clicking the Mail merge button
docEditor.setMailMergeRecipients(<%- JSON.stringify(dataMailMergeRecipients) %>); // insert recipient data for mail merge into the file
};
var onRequestUsers = function (event) {
@ -212,43 +211,19 @@
innerAlert("onRequestSendNotify: " + data);
};
var onRequestOpen = function(event) { // user open external data source
innerAlert("onRequestOpen");
var windowName = event.data.windowName;
requestReference(event.data, function (data) {
if (data.error) {
var winEditor = window.open("", windowName);
winEditor.close();
innerAlert(data.error, true);
return;
}
var link = data.link;
window.open(link, windowName);
});
};
var onRequestReferenceData = function(event) { // user refresh external data source
innerAlert("onRequestReferenceData");
requestReference(event.data, function (data) {
docEditor.setReferenceData(data);
});
};
var requestReference = function(data, callback) {
innerAlert(data);
innerAlert(event.data);
event.data.directUrl = !!config.document.directUrl;
let xhr = new XMLHttpRequest();
xhr.open("POST", "reference");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send(JSON.stringify(data));
xhr.send(JSON.stringify(event.data));
xhr.onload = function () {
innerAlert(xhr.responseText);
callback(JSON.parse(xhr.responseText));
docEditor.setReferenceData(JSON.parse(xhr.responseText));
}
};
@ -346,8 +321,7 @@
"onMetaChange": onMetaChange,
"onRequestInsertImage": onRequestInsertImage,
"onRequestCompareFile": onRequestCompareFile,
"onRequestSelectSpreadsheet": onRequestSelectSpreadsheet,
"onRequestOpen": onRequestOpen,
"onRequestMailMergeRecipients": onRequestMailMergeRecipients,
};
if (<%- JSON.stringify(editor.userid) %> != null) {

View File

@ -35,11 +35,3 @@ License File: PHP_CodeSniffer.license
PHPUnit - The PHP Unit Testing framework. (https://github.com/sebastianbergmann/phpunit/blob/main/LICENSE)
License: BSD 3-Clause
License File: phpunit.license
property-access - Provides functions to read and write from/to an object or array using a simple string notation. (https://github.com/symfony/property-access/blob/6.3/LICENSE)
License: MIT
License File: property-access.license
serializer - Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON. (https://github.com/symfony/serializer/blob/6.3/LICENSE)
License: MIT
License File: serializer.license

View File

@ -1,14 +0,0 @@
FROM php:8.2.8-fpm-alpine3.18 AS example
WORKDIR /srv
COPY . .
RUN \
chown -R www-data:www-data /srv && \
apk update && \
apk add --no-cache \
composer \
make && \
make prod
CMD ["make", "server-prod"]
FROM nginx:1.23.4-alpine3.17 AS proxy
COPY proxy/nginx.conf /etc/nginx/nginx.conf

View File

@ -4,16 +4,16 @@ ADDRESS := $(ADDRESS)
PORT := $(PORT)
.PHONY: help
help: # Show help message for each of the Makefile recipes.
help: # Show help message for each of the Makefile recipes.
@grep -E "^[a-z-]+: #" $(MAKEFILE_LIST) | \
awk 'BEGIN {FS = ": # "}; {printf "%s: %s\n", $$1, $$2}'
.PHONY: dev
dev: # Install development dependencies.
dev: # Install development dependencies.
@composer install
.PHONY: prod
prod: # Install production dependencies.
prod: # Install production dependencies.
@composer install --no-dev
ifeq ($(ADDRESS),)
@ -33,7 +33,7 @@ server-dev: \
endif
.PHONY: server-dev
server-dev: # Start the development server on localhost at $PORT (default: 9000).
server-dev: # Start the development server on localhost at $PORT (default: 9000).
@php --server $(ADDRESS):$(PORT)
ifeq ($(ADDRESS),)
@ -53,20 +53,15 @@ server-prod: \
endif
.PHONY: server-prod
server-prod: # Start the production server on 0.0.0.0 at $PORT (default: 9000).
server-prod: # Start the production server on 0.0.0.0 at $PORT (default: 9000).
@php-fpm --fpm-config php-fpm.conf
.PHONY: compose-prod
compose-prod: # Up containers in a production environment.
@docker-compose build
@docker-compose up --detach
.PHONY: lint
lint: # Lint the source code for the style.
lint: # Lint the source code for the style.
@./vendor/bin/phpcs .
.PHONY: test
test: # Run tests recursively.
test: # Run tests recursively.
@./vendor/bin/phpunit \
--test-suffix "Tests.php" \
--display-incomplete \
@ -74,4 +69,12 @@ test: # Run tests recursively.
--display-errors \
--display-notices \
--display-warnings \
src
common
@./vendor/bin/phpunit \
--test-suffix "Tests.php" \
--display-incomplete \
--display-deprecations \
--display-errors \
--display-notices \
--display-warnings \
configuration

View File

@ -15,14 +15,14 @@
* limitations under the License.
*/
namespace Example;
namespace OnlineEditorsExamplePhp;
use Exception;
use Example\Common\Path;
use Example\Configuration\ConfigurationManager;
use Example\Format\FormatManager;
use Example\Helpers\ExampleUsers;
use Example\Helpers\JwtManager;
use OnlineEditorsExamplePhp\Common\Path;
use OnlineEditorsExamplePhp\Configuration\ConfigurationManager;
use OnlineEditorsExamplePhp\Helpers\ConfigManager;
use OnlineEditorsExamplePhp\Helpers\ExampleUsers;
use OnlineEditorsExamplePhp\Helpers\JwtManager;
/**
* Check if the request is an AJAX request
@ -58,13 +58,13 @@ function saveas()
{
try {
$config_manager = new ConfigurationManager();
$formatManager = new FormatManager();
$post = json_decode(file_get_contents('php://input'), true);
$fileurl = $post["url"];
$title = $post["title"];
$extension = mb_strtolower(pathinfo($title, PATHINFO_EXTENSION));
$allexts = $formatManager->allExtensions();
$configManager = new ConfigManager();
$allexts = $configManager->getSuppotredExtensions();
$filename = GetCorrectName($title);
if (!in_array($extension, $allexts)) {
@ -102,8 +102,8 @@ function saveas()
function upload()
{
$config_manager = new ConfigurationManager();
$formatManager = new FormatManager();
$configManager = new ConfigManager();
if ($_FILES['files']['error'] > 0) {
$result["error"] = 'Error ' . json_encode($_FILES['files']['error']);
return $result;
@ -130,7 +130,7 @@ function upload()
}
// check if the file extension is supported by the editor
if (!in_array($ext, $formatManager->allExtensions())) {
if (!in_array($ext, $configManager->getSuppotredExtensions())) {
$result["error"] = 'File type is not supported'; // if not, then an error occurs
return $result;
}
@ -220,17 +220,16 @@ function track()
*/
function convert()
{
$formatManager = new FormatManager();
$post = json_decode(file_get_contents('php://input'), true);
$fileName = basename($post["filename"]);
$filePass = $post["filePass"];
$lang = $_COOKIE["ulang"] ?? "";
$extension = mb_strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
$internalExtension = "ooxml";
$configManager = new ConfigManager();
// check if the file with such an extension can be converted
if (in_array($extension, $formatManager->convertibleExtensions()) &&
if (in_array($extension, $configManager->getConvertExtensions()) &&
$internalExtension != "") {
$fileUri = $post["fileUri"];
if ($fileUri == null || $fileUri == "") {
@ -341,7 +340,7 @@ function files()
function assets()
{
$fileName = basename($_GET["name"]);
$filePath = dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' .
$filePath = dirname(__FILE__) .
DIRECTORY_SEPARATOR . "assets" . DIRECTORY_SEPARATOR . "document-templates"
. DIRECTORY_SEPARATOR . "sample" . DIRECTORY_SEPARATOR . $fileName;
downloadFile($filePath);
@ -355,7 +354,7 @@ function assets()
function csv()
{
$fileName = "csv.csv";
$filePath = dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' .
$filePath = dirname(__FILE__) .
DIRECTORY_SEPARATOR . "assets" . DIRECTORY_SEPARATOR . "document-templates"
. DIRECTORY_SEPARATOR . "sample" . DIRECTORY_SEPARATOR . $fileName;
downloadFile($filePath);
@ -379,6 +378,7 @@ function historyDownload()
$jwtManager = new JwtManager();
if ($jwtManager->isJwtEnabled()) {
$configManager = new ConfigManager();
$jwtHeader = $config_manager->jwt_header();
if (!empty(apache_request_headers()[$jwtHeader])) {
$token = $jwtManager->jwtDecode(mb_substr(

View File

@ -15,7 +15,7 @@
// limitations under the License.
//
namespace Example\Common;
namespace OnlineEditorsExamplePhp\Common;
enum HTTPStatus: int {
case not_found = 404;

View File

@ -15,9 +15,9 @@
// limitations under the License.
//
namespace Example\Common;
namespace OnlineEditorsExamplePhp\Common;
class Path {
final class Path {
private string $separator = DIRECTORY_SEPARATOR;
private string $string;

View File

@ -16,7 +16,7 @@
//
use PHPUnit\Framework\TestCase;
use Example\Common\Path;
use OnlineEditorsExamplePhp\Common\Path;
final class PathAbsolutePOSIXTests extends TestCase {
public function test_recognizes_an_empty_as_a_non_absolute() {

View File

@ -16,7 +16,7 @@
//
use PHPUnit\Framework\TestCase;
use Example\Common\Path;
use OnlineEditorsExamplePhp\Common\Path;
final class PathJoinPOSIXTests extends TestCase {
public function test_joins_a_relative_to_an_empty_one() {

View File

@ -16,7 +16,7 @@
//
use PHPUnit\Framework\TestCase;
use Example\Common\Path;
use OnlineEditorsExamplePhp\Common\Path;
final class PathNormalizePOSIXTests extends TestCase {
public function test_normalizes() {

View File

@ -16,7 +16,7 @@
//
use PHPUnit\Framework\TestCase;
use Example\Common\Path;
use OnlineEditorsExamplePhp\Common\Path;
final class PathStringPOSIXTests extends TestCase {
public function test_generates_with_an_empty() {

View File

@ -15,9 +15,9 @@
// limitations under the License.
//
namespace Example\Common;
namespace OnlineEditorsExamplePhp\Common;
class URL {
final class URL {
private string $string;
public function __construct(string $url) {

View File

@ -16,7 +16,7 @@
//
use PHPUnit\Framework\TestCase;
use Example\Common\URL;
use OnlineEditorsExamplePhp\Common\URL;
final class URLFromComponentsTests extends TestCase {
public function test_creates() {

View File

@ -16,7 +16,7 @@
//
use PHPUnit\Framework\TestCase;
use Example\Common\URL;
use OnlineEditorsExamplePhp\Common\URL;
final class URLJoinPathTests extends TestCase {
public function test_joins_a_relative_to_an_empty_one() {

View File

@ -16,7 +16,7 @@
//
use PHPUnit\Framework\TestCase;
use Example\Common\URL;
use OnlineEditorsExamplePhp\Common\URL;
final class URLStringTests extends TestCase {
public function test_generates() {

View File

@ -1,9 +1,7 @@
{
"require": {
"ext-mbstring": "*",
"firebase/php-jwt": "^6.8.1",
"symfony/serializer": "^6.3",
"symfony/property-access": "^6.3"
"firebase/php-jwt": "^6.8.1"
},
"require-dev": {
"squizlabs/php_codesniffer": "^3.7.2",
@ -11,12 +9,11 @@
},
"autoload": {
"psr-4": {
"Example\\Common\\": "src/common/",
"Example\\Configuration\\" : "src/configuration/",
"Example\\Format\\" : "src/format/",
"Example\\Helpers\\" : "src/helpers/",
"Example\\Proxy\\": "src/proxy/",
"Example\\Views\\" : "src/views/"
"OnlineEditorsExamplePhp\\" : "",
"OnlineEditorsExamplePhp\\Common\\": "common/",
"OnlineEditorsExamplePhp\\Configuration\\" : "configuration/",
"OnlineEditorsExamplePhp\\Helpers\\" : "helpers/",
"OnlineEditorsExamplePhp\\Views\\" : "views/"
}
}
}

View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "5d476b9c61f48836ee5e6c9cab7a1980",
"content-hash": "1408ebe84361a11eb7a751e9603b937f",
"packages": [
{
"name": "firebase/php-jwt",
@ -68,742 +68,6 @@
"source": "https://github.com/firebase/php-jwt/tree/v6.8.1"
},
"time": "2023-07-14T18:33:00+00:00"
},
{
"name": "symfony/deprecation-contracts",
"version": "v3.3.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "7c3aff79d10325257a001fcf92d991f24fc967cf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf",
"reference": "7c3aff79d10325257a001fcf92d991f24fc967cf",
"shasum": ""
},
"require": {
"php": ">=8.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.4-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"autoload": {
"files": [
"function.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2023-05-23T14:45:45+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.27.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a",
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"provide": {
"ext-ctype": "*"
},
"suggest": {
"ext-ctype": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.27-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Gert de Pagter",
"email": "BackEndTea@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for ctype functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"ctype",
"polyfill",
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2022-11-03T14:55:06+00:00"
},
{
"name": "symfony/polyfill-intl-grapheme",
"version": "v1.27.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
"reference": "511a08c03c1960e08a883f4cffcacd219b758354"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354",
"reference": "511a08c03c1960e08a883f4cffcacd219b758354",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.27-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Intl\\Grapheme\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's grapheme_* functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"grapheme",
"intl",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2022-11-03T14:55:06+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
"version": "v1.27.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
"reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6",
"reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.27-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Intl\\Normalizer\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's Normalizer class and related functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"intl",
"normalizer",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2022-11-03T14:55:06+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.27.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
"reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"provide": {
"ext-mbstring": "*"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.27-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2022-11-03T14:55:06+00:00"
},
{
"name": "symfony/property-access",
"version": "v6.3.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/property-access.git",
"reference": "db9358571ce63f09c439c2fee6c12e5b090b69ac"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/property-access/zipball/db9358571ce63f09c439c2fee6c12e5b090b69ac",
"reference": "db9358571ce63f09c439c2fee6c12e5b090b69ac",
"shasum": ""
},
"require": {
"php": ">=8.1",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/property-info": "^5.4|^6.0"
},
"require-dev": {
"symfony/cache": "^5.4|^6.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\PropertyAccess\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Provides functions to read and write from/to an object or array using a simple string notation",
"homepage": "https://symfony.com",
"keywords": [
"access",
"array",
"extraction",
"index",
"injection",
"object",
"property",
"property-path",
"reflection"
],
"support": {
"source": "https://github.com/symfony/property-access/tree/v6.3.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2023-05-19T08:06:44+00:00"
},
{
"name": "symfony/property-info",
"version": "v6.3.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/property-info.git",
"reference": "7f3a03716112269741fe2a809f8f791a371d1fcd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/property-info/zipball/7f3a03716112269741fe2a809f8f791a371d1fcd",
"reference": "7f3a03716112269741fe2a809f8f791a371d1fcd",
"shasum": ""
},
"require": {
"php": ">=8.1",
"symfony/string": "^5.4|^6.0"
},
"conflict": {
"phpdocumentor/reflection-docblock": "<5.2",
"phpdocumentor/type-resolver": "<1.5.1",
"symfony/dependency-injection": "<5.4"
},
"require-dev": {
"doctrine/annotations": "^1.10.4|^2",
"phpdocumentor/reflection-docblock": "^5.2",
"phpstan/phpdoc-parser": "^1.0",
"symfony/cache": "^5.4|^6.0",
"symfony/dependency-injection": "^5.4|^6.0",
"symfony/serializer": "^5.4|^6.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\PropertyInfo\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Kévin Dunglas",
"email": "dunglas@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Extracts information about PHP class' properties using metadata of popular sources",
"homepage": "https://symfony.com",
"keywords": [
"doctrine",
"phpdoc",
"property",
"symfony",
"type",
"validator"
],
"support": {
"source": "https://github.com/symfony/property-info/tree/v6.3.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2023-05-19T08:06:44+00:00"
},
{
"name": "symfony/serializer",
"version": "v6.3.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/serializer.git",
"reference": "1d238ee3180bc047f8ab713bfb73848d553f4407"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/serializer/zipball/1d238ee3180bc047f8ab713bfb73848d553f4407",
"reference": "1d238ee3180bc047f8ab713bfb73848d553f4407",
"shasum": ""
},
"require": {
"php": ">=8.1",
"symfony/polyfill-ctype": "~1.8"
},
"conflict": {
"doctrine/annotations": "<1.12",
"phpdocumentor/reflection-docblock": "<3.2.2",
"phpdocumentor/type-resolver": "<1.4.0",
"symfony/dependency-injection": "<5.4",
"symfony/property-access": "<5.4",
"symfony/property-info": "<5.4",
"symfony/uid": "<5.4",
"symfony/yaml": "<5.4"
},
"require-dev": {
"doctrine/annotations": "^1.12|^2",
"phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0",
"symfony/cache": "^5.4|^6.0",
"symfony/config": "^5.4|^6.0",
"symfony/console": "^5.4|^6.0",
"symfony/dependency-injection": "^5.4|^6.0",
"symfony/error-handler": "^5.4|^6.0",
"symfony/filesystem": "^5.4|^6.0",
"symfony/form": "^5.4|^6.0",
"symfony/http-foundation": "^5.4|^6.0",
"symfony/http-kernel": "^5.4|^6.0",
"symfony/mime": "^5.4|^6.0",
"symfony/property-access": "^5.4|^6.0",
"symfony/property-info": "^5.4|^6.0",
"symfony/uid": "^5.4|^6.0",
"symfony/validator": "^5.4|^6.0",
"symfony/var-dumper": "^5.4|^6.0",
"symfony/var-exporter": "^5.4|^6.0",
"symfony/yaml": "^5.4|^6.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Serializer\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/serializer/tree/v6.3.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2023-06-21T19:54:33+00:00"
},
{
"name": "symfony/string",
"version": "v6.3.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "f2e190ee75ff0f5eced645ec0be5c66fac81f51f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/f2e190ee75ff0f5eced645ec0be5c66fac81f51f",
"reference": "f2e190ee75ff0f5eced645ec0be5c66fac81f51f",
"shasum": ""
},
"require": {
"php": ">=8.1",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-intl-grapheme": "~1.0",
"symfony/polyfill-intl-normalizer": "~1.0",
"symfony/polyfill-mbstring": "~1.0"
},
"conflict": {
"symfony/translation-contracts": "<2.5"
},
"require-dev": {
"symfony/error-handler": "^5.4|^6.0",
"symfony/http-client": "^5.4|^6.0",
"symfony/intl": "^6.2",
"symfony/translation-contracts": "^2.5|^3.0",
"symfony/var-exporter": "^5.4|^6.0"
},
"type": "library",
"autoload": {
"files": [
"Resources/functions.php"
],
"psr-4": {
"Symfony\\Component\\String\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way",
"homepage": "https://symfony.com",
"keywords": [
"grapheme",
"i18n",
"string",
"unicode",
"utf-8",
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v6.3.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2023-03-21T21:06:29+00:00"
}
],
"packages-dev": [

View File

@ -15,12 +15,12 @@
// limitations under the License.
//
namespace Example\Configuration;
namespace OnlineEditorsExamplePhp\Configuration;
use Example\Common\Path;
use Example\Common\URL;
use OnlineEditorsExamplePhp\Common\Path;
use OnlineEditorsExamplePhp\Common\URL;
class ConfigurationManager {
final class ConfigurationManager {
public string $version = '1.6.0';
public function example_url(): ?URL {
@ -31,42 +31,34 @@ class ConfigurationManager {
return new URL($url);
}
public function document_server_public_url(): URL {
$url = getenv('DOCUMENT_SERVER_PUBLIC_URL') ?: 'http://document-server';
return new URL($url);
}
public function document_server_private_url(): URL {
$url = getenv('DOCUMENT_SERVER_PRIVATE_URL');
if (!$url) {
return $this->document_server_public_url();
}
public function document_server_url(): URL {
$url = getenv('DOCUMENT_SERVER_URL') ?: 'http://document-server';
return new URL($url);
}
public function document_server_api_url(): URL {
$server_url = $this->document_server_public_url();
$server_url = $this->document_server_url();
$path = getenv('DOCUMENT_SERVER_API_PATH')
?: 'web-apps/apps/api/documents/api.js';
return $server_url->join_path($path);
}
public function document_server_preloader_url(): URL {
$server_url = $this->document_server_public_url();
$server_url = $this->document_server_url();
$path = getenv('DOCUMENT_SERVER_PRELOADER_PATH')
?: 'web-apps/apps/api/documents/cache-scripts.html';
return $server_url->join_path($path);
}
public function document_server_command_url(): URL {
$server_url = $this->document_server_private_url();
$server_url = $this->document_server_url();
$path = getenv('DOCUMENT_SERVER_COMMAND_PATH')
?: 'coauthoring/CommandService.ashx';
return $server_url->join_path($path);
}
public function document_server_converter_url(): URL {
$server_url = $this->document_server_private_url();
$server_url = $this->document_server_url();
$path = getenv('DOCUMENT_SERVER_CONVERTER_PATH')
?: 'ConvertService.ashx';
return $server_url->join_path($path);
@ -106,7 +98,6 @@ class ConfigurationManager {
$storage_string_directory = $storage_directory->string();
$current_directory = new Path(__DIR__);
$directory = $current_directory
->join_path('..')
->join_path('..')
->join_path($storage_string_directory);
return $directory->normalize();

View File

@ -16,7 +16,7 @@
//
use PHPUnit\Framework\TestCase;
use Example\Configuration\ConfigurationManager;
use OnlineEditorsExamplePhp\Configuration\ConfigurationManager;
final class ConfigurationManagerConversionTimeoutTests extends TestCase {
public array $env;

View File

@ -16,7 +16,7 @@
//
use PHPUnit\Framework\TestCase;
use Example\Configuration\ConfigurationManager;
use OnlineEditorsExamplePhp\Configuration\ConfigurationManager;
final class ConfigurationManagerDocumentServerAPIURLTests extends TestCase {
public array $env;

View File

@ -16,7 +16,7 @@
//
use PHPUnit\Framework\TestCase;
use Example\Configuration\ConfigurationManager;
use OnlineEditorsExamplePhp\Configuration\ConfigurationManager;
final class ConfigurationManagerDocumentServerCommandURLTests extends TestCase {
public array $env;

View File

@ -16,7 +16,7 @@
//
use PHPUnit\Framework\TestCase;
use Example\Configuration\ConfigurationManager;
use OnlineEditorsExamplePhp\Configuration\ConfigurationManager;
final class ConfigurationManagerDocumentServerConverterURLTests extends TestCase {
public array $env;

View File

@ -16,7 +16,7 @@
//
use PHPUnit\Framework\TestCase;
use Example\Configuration\ConfigurationManager;
use OnlineEditorsExamplePhp\Configuration\ConfigurationManager;
final class ConfigurationManagerDocumentServerPreloaderURLTests extends TestCase {
public array $env;

View File

@ -16,9 +16,9 @@
//
use PHPUnit\Framework\TestCase;
use Example\Configuration\ConfigurationManager;
use OnlineEditorsExamplePhp\Configuration\ConfigurationManager;
final class ConfigurationManagerDocumentServerPublicURLTests extends TestCase {
final class ConfigurationManagerDocumentServerURLTests extends TestCase {
public array $env;
public function __construct(string $name) {
@ -34,14 +34,14 @@ final class ConfigurationManagerDocumentServerPublicURLTests extends TestCase {
public function test_assigns_a_default_value() {
$config_manager = new ConfigurationManager();
$url = $config_manager->document_server_public_url();
$url = $config_manager->document_server_url();
$this->assertEquals('http://document-server', $url->string());
}
public function test_assigns_a_value_from_the_environment() {
putenv('DOCUMENT_SERVER_PUBLIC_URL=http://localhost');
putenv('DOCUMENT_SERVER_URL=http://localhost');
$config_manager = new ConfigurationManager();
$url = $config_manager->document_server_public_url();
$url = $config_manager->document_server_url();
$this->assertEquals('http://localhost', $url->string());
}
}

View File

@ -16,7 +16,7 @@
//
use PHPUnit\Framework\TestCase;
use Example\Configuration\ConfigurationManager;
use OnlineEditorsExamplePhp\Configuration\ConfigurationManager;
final class ConfigurationManagerExampleURLTests extends TestCase {
public array $env;

View File

@ -16,7 +16,7 @@
//
use PHPUnit\Framework\TestCase;
use Example\Configuration\ConfigurationManager;
use OnlineEditorsExamplePhp\Configuration\ConfigurationManager;
final class ConfigurationManagerJWTHeaderTests extends TestCase {
public array $env;

View File

@ -16,7 +16,7 @@
//
use PHPUnit\Framework\TestCase;
use Example\Configuration\ConfigurationManager;
use OnlineEditorsExamplePhp\Configuration\ConfigurationManager;
final class ConfigurationManagerJWTSecretTests extends TestCase {
public array $env;

View File

@ -16,7 +16,7 @@
//
use PHPUnit\Framework\TestCase;
use Example\Configuration\ConfigurationManager;
use OnlineEditorsExamplePhp\Configuration\ConfigurationManager;
final class ConfigurationManagerJWTUseForRequest extends TestCase {
public array $env;

View File

@ -16,7 +16,7 @@
//
use PHPUnit\Framework\TestCase;
use Example\Configuration\ConfigurationManager;
use OnlineEditorsExamplePhp\Configuration\ConfigurationManager;
final class ConfigurationManagerMaximumFileSizeTests extends TestCase {
public array $env;

View File

@ -16,7 +16,7 @@
//
use PHPUnit\Framework\TestCase;
use Example\Configuration\ConfigurationManager;
use OnlineEditorsExamplePhp\Configuration\ConfigurationManager;
final class ConfigurationManagerSSLTests extends TestCase {
public array $env;

View File

@ -16,7 +16,7 @@
//
use PHPUnit\Framework\TestCase;
use Example\Configuration\ConfigurationManager;
use OnlineEditorsExamplePhp\Configuration\ConfigurationManager;
final class ConfigurationManagerSingleUserTests extends TestCase {
public array $env;

View File

@ -16,7 +16,7 @@
//
use PHPUnit\Framework\TestCase;
use Example\Configuration\ConfigurationManager;
use OnlineEditorsExamplePhp\Configuration\ConfigurationManager;
final class ConfigurationManagerStoragePathTests extends TestCase {
public array $env;

View File

@ -16,7 +16,7 @@
//
use PHPUnit\Framework\TestCase;
use Example\Configuration\ConfigurationManager;
use OnlineEditorsExamplePhp\Configuration\ConfigurationManager;
final class ConfigurationManagerTests extends TestCase {
public function test_corresponds_the_latest_version() {

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

Before

Width:  |  Height:  |  Size: 781 B

After

Width:  |  Height:  |  Size: 781 B

View File

Before

Width:  |  Height:  |  Size: 547 B

After

Width:  |  Height:  |  Size: 547 B

View File

Before

Width:  |  Height:  |  Size: 425 B

After

Width:  |  Height:  |  Size: 425 B

View File

Before

Width:  |  Height:  |  Size: 331 B

After

Width:  |  Height:  |  Size: 331 B

View File

Before

Width:  |  Height:  |  Size: 507 B

After

Width:  |  Height:  |  Size: 507 B

View File

Before

Width:  |  Height:  |  Size: 757 B

After

Width:  |  Height:  |  Size: 757 B

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 593 B

After

Width:  |  Height:  |  Size: 593 B

View File

Before

Width:  |  Height:  |  Size: 738 B

After

Width:  |  Height:  |  Size: 738 B

View File

Before

Width:  |  Height:  |  Size: 477 B

After

Width:  |  Height:  |  Size: 477 B

View File

Before

Width:  |  Height:  |  Size: 691 B

After

Width:  |  Height:  |  Size: 691 B

View File

Before

Width:  |  Height:  |  Size: 992 B

After

Width:  |  Height:  |  Size: 992 B

View File

Before

Width:  |  Height:  |  Size: 1004 B

After

Width:  |  Height:  |  Size: 1004 B

View File

Before

Width:  |  Height:  |  Size: 379 B

After

Width:  |  Height:  |  Size: 379 B

View File

Before

Width:  |  Height:  |  Size: 233 B

After

Width:  |  Height:  |  Size: 233 B

View File

Before

Width:  |  Height:  |  Size: 832 B

After

Width:  |  Height:  |  Size: 832 B

View File

Before

Width:  |  Height:  |  Size: 631 B

After

Width:  |  Height:  |  Size: 631 B

View File

Before

Width:  |  Height:  |  Size: 832 B

After

Width:  |  Height:  |  Size: 832 B

View File

Before

Width:  |  Height:  |  Size: 488 B

After

Width:  |  Height:  |  Size: 488 B

View File

Before

Width:  |  Height:  |  Size: 673 B

After

Width:  |  Height:  |  Size: 673 B

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

Before

Width:  |  Height:  |  Size: 790 B

After

Width:  |  Height:  |  Size: 790 B

View File

Before

Width:  |  Height:  |  Size: 506 B

After

Width:  |  Height:  |  Size: 506 B

View File

Before

Width:  |  Height:  |  Size: 549 B

After

Width:  |  Height:  |  Size: 549 B

View File

Before

Width:  |  Height:  |  Size: 438 B

After

Width:  |  Height:  |  Size: 438 B

View File

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@ -151,19 +151,19 @@ label .checkbox {
}
.try-editor.word {
background-image: url("/assets/images/file_docx.svg");
background-image: url("images/file_docx.svg");
}
.try-editor.cell {
background-image: url("/assets/images/file_xlsx.svg");
background-image: url("images/file_xlsx.svg");
}
.try-editor.slide {
background-image: url("/assets/images/file_pptx.svg");
background-image: url("images/file_pptx.svg");
}
.try-editor.form {
background-image: url("/assets/images/file_docxf.svg");
background-image: url("images/file_docxf.svg");
}
.side-option {
@ -235,7 +235,7 @@ label .checkbox {
}
.file-upload {
background: url("/assets/images/file_upload.svg") no-repeat 0 transparent;
background: url("images/file_upload.svg") no-repeat 0 transparent;
cursor: pointer;
display: block;
font-size: 14px;
@ -308,7 +308,7 @@ label .checkbox {
}
.error-message {
background: url("/assets/images/error.svg") no-repeat scroll 4px 10px;
background: url("images/error.svg") no-repeat scroll 4px 10px;
color: #CB0000;
display: none;
line-height: 160%;
@ -329,15 +329,15 @@ label .checkbox {
}
.current {
background-image: url("/assets/images/loader16.gif");
background-image: url("images/loader16.gif");
}
.done {
background-image: url("/assets/images/done.svg");
background-image: url("images/done.svg");
}
.error {
background-image: url("/assets/images/notdone.svg");
background-image: url("images/notdone.svg");
}
.step-descr {
@ -451,17 +451,17 @@ footer table tr td:first-child {
.stored-edit.word,
.uploadFileName.word {
background-image: url("/assets/images/icon_docx.svg");
background-image: url("images/icon_docx.svg");
}
.stored-edit.cell,
.uploadFileName.cell {
background-image: url("/assets/images/icon_xlsx.svg");
background-image: url("images/icon_xlsx.svg");
}
.stored-edit.slide,
.uploadFileName.slide {
background-image: url("/assets/images/icon_pptx.svg");
background-image: url("images/icon_pptx.svg");
}
.stored-edit span {
@ -490,7 +490,7 @@ footer table tr td:first-child {
}
.dialog-close {
background: url("/assets/images/close.svg") no-repeat scroll left top;
background: url("images/close.svg") no-repeat scroll left top;
cursor: pointer;
float: right;
font-size: 1px;
@ -766,4 +766,4 @@ html {
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
}
}

View File

@ -1,41 +0,0 @@
version: "3.8"
services:
document-server:
container_name: document-server
image: onlyoffice/documentserver:7.3.3.50
expose:
- "80"
environment:
- JWT_SECRET=your-256-bit-secret
example:
container_name: example
build:
context: .
target: example
expose:
- "80"
volumes:
- "example:/srv"
environment:
- ADDRESS=0.0.0.0
- DOCUMENT_SERVER_PRIVATE_URL=http://proxy:8080
- DOCUMENT_SERVER_PUBLIC_URL=http://localhost:8080
- EXAMPLE_URL=http://proxy
- JWT_SECRET=your-256-bit-secret
- PORT=80
proxy:
container_name: proxy
build:
context: .
target: proxy
ports:
- "80:80"
- "8080:8080"
volumes:
- "example:/srv"
volumes:
example:

View File

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 144 KiB

View File

@ -15,14 +15,14 @@
* limitations under the License.
*/
namespace Example;
namespace OnlineEditorsExamplePhp;
use Exception;
use Example\Configuration\ConfigurationManager;
use Example\Format\FormatManager;
use Example\Helpers\ExampleUsers;
use Example\Helpers\JwtManager;
use Example\Helpers\Users;
use OnlineEditorsExamplePhp\Configuration\ConfigurationManager;
use OnlineEditorsExamplePhp\Helpers\ConfigManager;
use OnlineEditorsExamplePhp\Helpers\ExampleUsers;
use OnlineEditorsExamplePhp\Helpers\JwtManager;
use OnlineEditorsExamplePhp\Helpers\Users;
/**
* Put log files into the log folder
@ -128,10 +128,11 @@ function getCurUserHostAddress($userAddress = null)
*/
function getInternalExtension($filename)
{
$formatManager = new FormatManager();
$ext = mb_strtolower(pathinfo($filename, PATHINFO_EXTENSION));
foreach ($formatManager->all() as $format) {
$configManager = new ConfigManager();
foreach ($configManager->getSuppotredFormats() as $format) {
if ($format->name === $ext) {
if ($format->type === "word") {
return ".docx";
@ -157,11 +158,11 @@ function getInternalExtension($filename)
*/
function getTemplateImageUrl($filename)
{
$formatManager = new FormatManager();
$ext = mb_strtolower(pathinfo($filename, PATHINFO_EXTENSION));
$path = serverPath(true) . "/assets/images/";
$path = serverPath(true) . "/css/images/";
foreach ($formatManager->all() as $format) {
$configManager = new ConfigManager();
foreach ($configManager->getSuppotredFormats() as $format) {
if ($format->name === $ext) {
if ($format->type === "word") {
return $path . "file_docx.svg";
@ -187,10 +188,10 @@ function getTemplateImageUrl($filename)
*/
function getDocumentType($filename)
{
$formatManager = new FormatManager();
$ext = mb_strtolower(pathinfo($filename, PATHINFO_EXTENSION));
foreach ($formatManager->all() as $format) {
$configManager = new ConfigManager();
foreach ($configManager->getSuppotredFormats() as $format) {
if ($format->name === $ext) {
return $format->type;
}
@ -335,7 +336,7 @@ function getFileVersion($histDir)
*/
function getStoredFiles()
{
$formatManager = new FormatManager();
$configManager = new ConfigManager();
$config_manager = new ConfigurationManager();
$storage_path = $config_manager->storage_path();
@ -362,8 +363,8 @@ function getStoredFiles()
$result[$dat] = (object) [ // and write the file to the result
"name" => $fileName,
"documentType" => getDocumentType($fileName),
"canEdit" => in_array($ext, $formatManager->editableExtensions()),
"isFillFormDoc" => in_array($ext, $formatManager->fillableExtensions()),
"canEdit" => in_array($ext, $configManager->getEditExtensions()),
"isFillFormDoc" => in_array($ext, $configManager->getFillExtensions()),
];
}
}
@ -517,13 +518,12 @@ function getDocEditorKey($fileName)
*/
function doUpload($fileUri)
{
$formatManager = new FormatManager();
$_fileName = GetCorrectName($fileUri);
$configManager = new ConfigManager();
// check if file extension is supported by the editor
$ext = mb_strtolower(pathinfo($_fileName, PATHINFO_EXTENSION));
if (!in_array($ext, $formatManager->allExtensions())) {
if (!in_array($ext, $configManager->getSuppotredExtensions())) {
throw new Exception("File type is not supported");
}
@ -760,6 +760,60 @@ function getConvertedData(
return ["percent" => $percent, "fileType" => $fileType];
}
/**
* Processing document received from the editing service.
*
* @param Response $document_response The result from editing service
* @param string $response_uri Uri to the converted document
*
* @throws Exception if an error occurs
*
* @return int percentage of completion of conversion
*/
function getResponseUri($document_response, &$response_uri)
{
$response_uri = "";
$resultPercent = 0;
if (!$document_response) {
$errs = "Invalid answer format";
}
// if an error occurs, then display an error message
$errorElement = $document_response->Error;
if ($errorElement != null && $errorElement != "") {
processConvServResponceError($document_response->Error);
}
$endConvert = $document_response->EndConvert;
if ($endConvert != null && $endConvert == "") {
throw new Exception("Invalid answer format");
}
// if the conversion is completed successfully
if ($endConvert != null && mb_strtolower($endConvert) == true) {
$fileUrl = $document_response->FileUrl;
if ($fileUrl == null || $fileUrl == "") {
throw new Exception("Invalid answer format");
}
// get the response file url
$response_uri = $fileUrl;
$resultPercent = 100;
} else { // otherwise, get the percentage of conversion completion
$percent = $document_response->Percent;
if ($percent != null && $percent != "") {
$resultPercent = $percent;
}
if ($resultPercent >= 100) {
$resultPercent = 99;
}
}
return $resultPercent;
}
/**
* Get demo file name by the extension
*
@ -773,7 +827,6 @@ function tryGetDefaultByType($createExt, $user)
$sample = isset($_GET["sample"]) && $_GET["sample"];
$demoName = ($sample ? "sample." : "new.") . $createExt;
$demoPath =
'..' . DIRECTORY_SEPARATOR .
"assets" . DIRECTORY_SEPARATOR .
"document-templates" . DIRECTORY_SEPARATOR .
($sample ? "sample" : "new") . DIRECTORY_SEPARATOR;

View File

@ -0,0 +1,106 @@
<?php
namespace OnlineEditorsExamplePhp\Helpers;
/**
* (c) Copyright Ascensio System SIA 2023
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class ConfigManager
{
private mixed $configFormats;
public function __construct()
{
$this->configFormats = json_decode($this->getConfigurationFromatsJson());
}
private function getConfigurationFromatsJson(): bool|string
{
return file_exists("./assets/document-formats/onlyoffice-docs-formats.json")
? file_get_contents("./assets/document-formats/onlyoffice-docs-formats.json")
: false;
}
public function getSuppotredFormats(): mixed
{
return $this->configFormats;
}
public function getSuppotredExtensions(): mixed
{
return array_reduce(
$this->configFormats,
function ($extensions, $format) {
$extensions[] = $format->name;
return $extensions;
}
);
}
public function getViewExtensions(): mixed
{
return array_reduce(
$this->configFormats,
function ($extensions, $format) {
if (in_array("view", $format->actions)) {
$extensions[] = $format->name;
}
return $extensions;
}
);
}
public function getEditExtensions(): mixed
{
return array_reduce(
$this->configFormats,
function ($extensions, $format) {
if (in_array("edit", $format->actions) || in_array("lossy-edit", $format->actions)) {
$extensions[] = $format->name;
}
return $extensions;
}
);
}
public function getFillExtensions(): mixed
{
return array_reduce(
$this->configFormats,
function ($extensions, $format) {
if (in_array("fill", $format->actions)) {
$extensions[] = $format->name;
}
return $extensions;
}
);
}
public function getConvertExtensions(): mixed
{
return array_reduce(
$this->configFormats,
function ($extensions, $format) {
if ($format->type === "word" && in_array("docx", $format->convert)
|| $format->type === "cell" && in_array("xlsx", $format->convert)
|| $format->type === "slide" && in_array("pptx", $format->convert)) {
$extensions[] = $format->name;
}
return $extensions;
}
);
}
}

View File

@ -1,8 +1,8 @@
<?php
namespace Example\Helpers;
namespace OnlineEditorsExamplePhp\Helpers;
use function Example\sendlog;
use function OnlineEditorsExamplePhp\sendlog;
/**
* (c) Copyright Ascensio System SIA 2023

View File

@ -1,6 +1,6 @@
<?php
namespace Example\Helpers;
namespace OnlineEditorsExamplePhp\Helpers;
/**
* (c) Copyright Ascensio System SIA 2023
@ -20,7 +20,7 @@ namespace Example\Helpers;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Example\Configuration\ConfigurationManager;
use OnlineEditorsExamplePhp\Configuration\ConfigurationManager;
final class JwtManager
{

View File

@ -1,6 +1,6 @@
<?php
namespace Example\Helpers;
namespace OnlineEditorsExamplePhp\Helpers;
/**
* (c) Copyright Ascensio System SIA 2023

View File

@ -15,18 +15,18 @@
* limitations under the License.
*/
namespace Example;
namespace OnlineEditorsExamplePhp;
require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/src/ajax.php';
require_once __DIR__ . '/src/functions.php';
require_once __DIR__ . '/src/trackmanager.php';
require_once __DIR__ . '/ajax.php';
require_once __DIR__ . '/functions.php';
require_once __DIR__ . '/trackmanager.php';
use Example\Common\HTTPStatus;
use Example\Common\URL;
use Example\Configuration\ConfigurationManager;
use Example\Views\DocEditorView;
use Example\Views\IndexView;
use OnlineEditorsExamplePhp\Common\HTTPStatus;
use OnlineEditorsExamplePhp\Common\URL;
use OnlineEditorsExamplePhp\Configuration\ConfigurationManager;
use OnlineEditorsExamplePhp\Views\DocEditorView;
use OnlineEditorsExamplePhp\Views\IndexView;
function configure() {
$config_manager = new ConfigurationManager();

Some files were not shown because too many files have changed in this diff Show More