cmis_server 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +3 -0
  4. data/Rakefile +51 -0
  5. data/app/assets/javascripts/cmis_server/application.js +13 -0
  6. data/app/assets/stylesheets/cmis_server/application.css +15 -0
  7. data/app/controllers/cmis_server/application_controller.rb +19 -0
  8. data/app/controllers/cmis_server/atom_pub/base_controller.rb +23 -0
  9. data/app/controllers/cmis_server/atom_pub/bulk_controller.rb +302 -0
  10. data/app/controllers/cmis_server/atom_pub/content_controller.rb +178 -0
  11. data/app/controllers/cmis_server/atom_pub/entries_controller.rb +41 -0
  12. data/app/controllers/cmis_server/atom_pub/folder_collection_controller.rb +70 -0
  13. data/app/controllers/cmis_server/atom_pub/query_controller.rb +138 -0
  14. data/app/controllers/cmis_server/atom_pub/repository_controller.rb +75 -0
  15. data/app/controllers/cmis_server/atom_pub/secondary_types_controller.rb +149 -0
  16. data/app/controllers/cmis_server/atom_pub/service_documents_controller.rb +34 -0
  17. data/app/controllers/cmis_server/atom_pub/types_controller.rb +229 -0
  18. data/app/controllers/cmis_server/atom_pub/uri_templates_controller.rb +58 -0
  19. data/app/controllers/concerns/cmis_server/atom_pub/repository_scopable.rb +18 -0
  20. data/app/helpers/cmis_server/application_helper.rb +31 -0
  21. data/app/models/cmis_server/application_record.rb +5 -0
  22. data/app/services/cmis_server/bulk_update_service.rb +69 -0
  23. data/app/services/cmis_server/content_stream_service.rb +75 -0
  24. data/app/services/cmis_server/discovery_service.rb +31 -0
  25. data/app/services/cmis_server/navigation_service.rb +43 -0
  26. data/app/services/cmis_server/object_service.rb +176 -0
  27. data/app/services/cmis_server/repository_service.rb +40 -0
  28. data/app/services/cmis_server/secondary_type_service.rb +120 -0
  29. data/app/services/cmis_server/type_management_service.rb +117 -0
  30. data/app/views/cmis_server/atom_pub/bulk_update_feed.atom.builder +47 -0
  31. data/app/views/cmis_server/atom_pub/entries/_cmis_document_links.atom_entry.builder +0 -0
  32. data/app/views/cmis_server/atom_pub/entries/_cmis_folder_links.atom_entry.builder +3 -0
  33. data/app/views/cmis_server/atom_pub/entries/_object_entry.atom_entry.builder +64 -0
  34. data/app/views/cmis_server/atom_pub/entries/_property.atom_entry.builder +8 -0
  35. data/app/views/cmis_server/atom_pub/entries/object_entry.atom_entry.builder +1 -0
  36. data/app/views/cmis_server/atom_pub/entries/type_entry.atom_entry.builder +59 -0
  37. data/app/views/cmis_server/atom_pub/feeds/_feed_entry.atom_feed.builder +1 -0
  38. data/app/views/cmis_server/atom_pub/feeds/feed.atom_feed.builder +30 -0
  39. data/app/views/cmis_server/atom_pub/service_documents/_uri_template.atom_service.builder +5 -0
  40. data/app/views/cmis_server/atom_pub/service_documents/_workspace.atom_service.builder +89 -0
  41. data/app/views/cmis_server/atom_pub/service_documents/service_document.atom_service.builder +1 -0
  42. data/app/views/cmis_server/atom_pub/shared/_collection.xml.builder +7 -0
  43. data/app/views/cmis_server/atom_pub/shared/atom_entry_layout.atom_entry.builder +0 -0
  44. data/app/views/cmis_server/atom_pub/type_entry.atom.builder +69 -0
  45. data/app/views/cmis_server/atom_pub/types_feed.atom.builder +91 -0
  46. data/app/views/layouts/cmis_server/application.atom_entry.builder +2 -0
  47. data/app/views/layouts/cmis_server/application.atom_feed.builder +3 -0
  48. data/app/views/layouts/cmis_server/application.atom_service.builder +10 -0
  49. data/app/views/layouts/cmis_server/application.html.erb +14 -0
  50. data/config/routes.rb +49 -0
  51. data/lib/cmis_server/atom_pub/entry_parser.rb +72 -0
  52. data/lib/cmis_server/base_objects/base_type.rb +128 -0
  53. data/lib/cmis_server/base_objects/document.rb +68 -0
  54. data/lib/cmis_server/base_objects/folder.rb +104 -0
  55. data/lib/cmis_server/base_objects/item.rb +25 -0
  56. data/lib/cmis_server/base_types.rb +123 -0
  57. data/lib/cmis_server/cmis_object.rb +190 -0
  58. data/lib/cmis_server/configuration.rb +43 -0
  59. data/lib/cmis_server/constants.rb +8 -0
  60. data/lib/cmis_server/content_stream.rb +52 -0
  61. data/lib/cmis_server/context.rb +11 -0
  62. data/lib/cmis_server/document_object.rb +12 -0
  63. data/lib/cmis_server/document_type.rb +67 -0
  64. data/lib/cmis_server/engine.rb +60 -0
  65. data/lib/cmis_server/exceptions.rb +185 -0
  66. data/lib/cmis_server/folder_object.rb +18 -0
  67. data/lib/cmis_server/folder_type.rb +34 -0
  68. data/lib/cmis_server/id.rb +18 -0
  69. data/lib/cmis_server/item_adapter.rb +26 -0
  70. data/lib/cmis_server/item_object.rb +15 -0
  71. data/lib/cmis_server/item_type.rb +7 -0
  72. data/lib/cmis_server/object_adapter.rb +79 -0
  73. data/lib/cmis_server/property.rb +29 -0
  74. data/lib/cmis_server/property_definition.rb +118 -0
  75. data/lib/cmis_server/query/parser.output +2250 -0
  76. data/lib/cmis_server/query/parser.racc +222 -0
  77. data/lib/cmis_server/query/parser.racc.rb +1039 -0
  78. data/lib/cmis_server/query/parser.rex +114 -0
  79. data/lib/cmis_server/query/parser.rex.rb +238 -0
  80. data/lib/cmis_server/query/statement.rb +395 -0
  81. data/lib/cmis_server/query.rb +2 -0
  82. data/lib/cmis_server/renderable_collection.rb +45 -0
  83. data/lib/cmis_server/renderable_object.rb +49 -0
  84. data/lib/cmis_server/repository.rb +91 -0
  85. data/lib/cmis_server/secondary_type.rb +11 -0
  86. data/lib/cmis_server/type.rb +62 -0
  87. data/lib/cmis_server/type_registry.rb +115 -0
  88. data/lib/cmis_server/version.rb +4 -0
  89. data/lib/cmis_server.rb +22 -0
  90. data/lib/tasks/cmis_server_tasks.rake +4 -0
  91. data/test/cmis_server_test.rb +7 -0
  92. data/test/dummy/README.rdoc +28 -0
  93. data/test/dummy/Rakefile +6 -0
  94. data/test/dummy/app/assets/javascripts/application.js +13 -0
  95. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  96. data/test/dummy/app/controllers/application_controller.rb +5 -0
  97. data/test/dummy/app/helpers/application_helper.rb +2 -0
  98. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  99. data/test/dummy/bin/bundle +3 -0
  100. data/test/dummy/bin/rails +4 -0
  101. data/test/dummy/bin/rake +4 -0
  102. data/test/dummy/bin/setup +29 -0
  103. data/test/dummy/config/application.rb +26 -0
  104. data/test/dummy/config/boot.rb +5 -0
  105. data/test/dummy/config/database.yml +25 -0
  106. data/test/dummy/config/environment.rb +5 -0
  107. data/test/dummy/config/environments/development.rb +41 -0
  108. data/test/dummy/config/environments/production.rb +79 -0
  109. data/test/dummy/config/environments/test.rb +42 -0
  110. data/test/dummy/config/initializers/assets.rb +11 -0
  111. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  112. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  113. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  114. data/test/dummy/config/initializers/inflections.rb +16 -0
  115. data/test/dummy/config/initializers/mime_types.rb +4 -0
  116. data/test/dummy/config/initializers/session_store.rb +3 -0
  117. data/test/dummy/config/initializers/to_time_preserves_timezone.rb +10 -0
  118. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  119. data/test/dummy/config/locales/en.yml +23 -0
  120. data/test/dummy/config/routes.rb +4 -0
  121. data/test/dummy/config/secrets.yml +22 -0
  122. data/test/dummy/config.ru +4 -0
  123. data/test/dummy/public/404.html +67 -0
  124. data/test/dummy/public/422.html +67 -0
  125. data/test/dummy/public/500.html +66 -0
  126. data/test/dummy/public/favicon.ico +0 -0
  127. data/test/integration/navigation_test.rb +8 -0
  128. data/test/services/bulk_update_service_test.rb +121 -0
  129. data/test/services/content_stream_service_test.rb +176 -0
  130. data/test/services/secondary_type_service_test.rb +174 -0
  131. data/test/services/type_management_service_test.rb +146 -0
  132. data/test/test_helper.rb +16 -0
  133. metadata +326 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5a06882af4391857d952ae571fe62d9cecd8b189448b7a74da1f9aedc7d936fb
4
+ data.tar.gz: defe5fdf0356330dccaaa0d7819f9f5f0fccda34315ae9d056980eb5b9b10bb9
5
+ SHA512:
6
+ metadata.gz: e322038687939ec2b099c76366dd2bc646af018e803f60b35861c2d71ca5ddff14ff275d009de7977be59c2185e12aaed72e6c1bce3b8fa4df1191ad8aa65680
7
+ data.tar.gz: b2f428f15608eb7aa583f42a00271ccac21f8b0b19ba8c9c82f3202113ab56622b249d0fd9799c7329e6699c32da8ec2c0e59c9e2e3fa23af6eb7054389483af
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2017 Alexandre Narbonne
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,3 @@
1
+ = CmisServer
2
+
3
+ This project rocks and uses MIT-LICENSE.
data/Rakefile ADDED
@@ -0,0 +1,51 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'CmisServer'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+ load 'rails/tasks/statistics.rake'
22
+
23
+
24
+
25
+ Bundler::GemHelper.install_tasks
26
+
27
+ require 'rake/testtask'
28
+
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.libs << 'lib'
31
+ t.libs << 'test'
32
+ t.pattern = 'test/**/*_test.rb'
33
+ t.verbose = false
34
+ end
35
+
36
+
37
+ GENERATED_PARSER = 'lib/cmis_server/query/parser.racc.rb'
38
+ GENERATED_LEXER = 'lib/cmis_server/query/parser.rex.rb'
39
+
40
+ file GENERATED_LEXER => 'lib/cmis_server/query/parser.rex' do |t|
41
+ sh "rex -do #{t.name} #{t.prerequisites.first}"
42
+ end
43
+
44
+ file GENERATED_PARSER => 'lib/cmis_server/query/parser.racc' do |t|
45
+ sh "racc -t -g -v -o #{t.name} #{t.prerequisites.first}"
46
+ end
47
+
48
+ task :parser => [GENERATED_LEXER, GENERATED_PARSER]
49
+
50
+ # Make sure the parser's up-to-date when we test.
51
+ Rake::Task['test'].prerequisites << :parser
@@ -0,0 +1,13 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any styles
10
+ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11
+ * file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,19 @@
1
+ module CmisServer
2
+ class ApplicationController < ActionController::Base
3
+ protect_from_forgery with: :exception, unless: -> { request.format.symbol.in?([:atom_xml, :atom_feed, :atom_entry, :atom_service]) }
4
+
5
+ skip_before_action :verify_authenticity_token
6
+ before_action :check_auth
7
+
8
+ def context
9
+ @context ||= Hash.new
10
+ end
11
+
12
+ def check_auth
13
+ #ToDo Proc to be provided in gem config by the final application
14
+
15
+ #auth_proc= Proc.new {|username,password| (resource=User.find_by(login: username))&&resource.valid_password?(password)&&(context.current_user=resource)}
16
+ context[:current_user] = authenticate_or_request_with_http_basic &CmisServer.configuration.http_basic_auth_procedure
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,23 @@
1
+ module CmisServer
2
+ module AtomPub
3
+ class BaseController < CmisServer::ApplicationController
4
+ rescue_from StandardError, with: :render_standard_error
5
+ # skip_before_action :verify_authenticity_token # Déjà défini dans ApplicationController
6
+
7
+ def raw_post_body
8
+ @raw_body ||= request.raw_post
9
+ end
10
+
11
+ def request_entry_parser
12
+ @r_entry_parser ||= CmisServer::EntryParser.new(raw_post_body)
13
+ end
14
+
15
+ def render_standard_error(error)
16
+ cmis_error = error.is_a?(CmisServer::CmisStandardError) ? error : CmisServer::RuntimeError.new(trigger: error)
17
+ cmis_error.log(Rails.logger)
18
+
19
+ render xml: cmis_error.to_xml, content_type: 'application/xml', status: cmis_error.http_status_code
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,302 @@
1
+ module CmisServer
2
+ module AtomPub
3
+ class BulkController < BaseController
4
+ include CmisServer::AtomPub::RepositoryScopable
5
+
6
+ before_action :check_repository_capabilities
7
+
8
+ # POST /bulk - bulkUpdateProperties
9
+ def update_properties
10
+ bulk_data = parse_bulk_data_from_request
11
+ validate_bulk_data(bulk_data)
12
+
13
+ # Utiliser le service dédié pour la mise à jour en masse
14
+ bulk_service = CmisServer::BulkUpdateService.new(context: context)
15
+ results = bulk_service.bulk_update(bulk_data[:object_ids], bulk_data[:properties])
16
+
17
+ respond_to do |format|
18
+ format.atom_feed { render "cmis_server/atom_pub/bulk_update_feed", locals: { results: results } }
19
+ format.json { render json: format_bulk_results_for_json(results) }
20
+ end
21
+ rescue CmisServer::BulkUpdateService::CapabilityError => e
22
+ render_error(CmisServer::NotSupported.new(e.message))
23
+ rescue => e
24
+ render_error(e)
25
+ end
26
+
27
+ # POST /bulk/delete - bulkDeleteProperties (nouvelle fonctionnalité CMIS 1.1)
28
+ def delete_properties
29
+ bulk_data = parse_bulk_delete_from_request
30
+ validate_bulk_delete_data(bulk_data)
31
+
32
+ results = delete_properties_from_objects(bulk_data)
33
+
34
+ respond_to do |format|
35
+ format.atom_feed { render "cmis_server/atom_pub/bulk_delete_feed", locals: { results: results } }
36
+ format.json { render json: format_bulk_results_for_json(results) }
37
+ end
38
+ rescue => e
39
+ render_error(e)
40
+ end
41
+
42
+ # POST /bulk/move - bulkMoveObjects (extension utile)
43
+ def move_objects
44
+ bulk_data = parse_bulk_move_from_request
45
+ validate_bulk_move_data(bulk_data)
46
+
47
+ results = move_objects_bulk(bulk_data)
48
+
49
+ respond_to do |format|
50
+ format.atom_feed { render "cmis_server/atom_pub/bulk_move_feed", locals: { results: results } }
51
+ format.json { render json: format_bulk_results_for_json(results) }
52
+ end
53
+ rescue => e
54
+ render_error(e)
55
+ end
56
+
57
+ private
58
+
59
+ def check_repository_capabilities
60
+ repository = repository_from_request
61
+
62
+ case action_name
63
+ when 'update_properties'
64
+ unless repository.capabilities[:bulk_update]
65
+ raise CmisServer::NotSupported.new("Repository does not support bulk update operations")
66
+ end
67
+ when 'delete_properties'
68
+ unless repository.capabilities[:bulk_delete_properties]
69
+ raise CmisServer::NotSupported.new("Repository does not support bulk delete properties operations")
70
+ end
71
+ when 'move_objects'
72
+ unless repository.capabilities[:bulk_move]
73
+ raise CmisServer::NotSupported.new("Repository does not support bulk move operations")
74
+ end
75
+ end
76
+ end
77
+
78
+ def parse_bulk_data_from_request
79
+ if supports_browser_binding?
80
+ parse_browser_binding_bulk_request
81
+ elsif request.content_type =~ /application\/atom\+xml/
82
+ parse_atom_pub_bulk_request
83
+ else
84
+ raise CmisServer::InvalidOperation.new("Unsupported content type: #{request.content_type}")
85
+ end
86
+ end
87
+
88
+ def parse_atom_pub_bulk_request
89
+ doc = Nokogiri::XML(request.body.read)
90
+ doc.remove_namespaces! # Simplifier le parsing
91
+
92
+ object_ids = doc.xpath("//objectId").map(&:text)
93
+ properties = parse_properties_from_xml(doc)
94
+
95
+ { object_ids: object_ids, properties: properties }
96
+ end
97
+
98
+ def parse_browser_binding_bulk_request
99
+ json_data = JSON.parse(request.body.read)
100
+
101
+ object_ids = json_data["objectIds"] || []
102
+ properties = json_data["properties"] || {}
103
+
104
+ { object_ids: object_ids, properties: properties }
105
+ end
106
+
107
+ def parse_properties_from_xml(doc)
108
+ properties = {}
109
+
110
+ doc.xpath("//properties/*").each do |prop_node|
111
+ prop_id = prop_node.attributes["propertyDefinitionId"]&.value
112
+ next unless prop_id
113
+
114
+ prop_type = prop_node.name.split(":").last
115
+ value_nodes = prop_node.xpath(".//value")
116
+
117
+ if value_nodes.empty?
118
+ properties[prop_id] = nil
119
+ elsif value_nodes.length == 1
120
+ properties[prop_id] = convert_property_value(value_nodes.first.text, prop_type)
121
+ else
122
+ properties[prop_id] = value_nodes.map { |n| convert_property_value(n.text, prop_type) }
123
+ end
124
+ end
125
+
126
+ properties
127
+ end
128
+
129
+ def validate_bulk_data(bulk_data)
130
+ if bulk_data[:object_ids].nil? || bulk_data[:object_ids].empty?
131
+ raise CmisServer::InvalidOperation.new("No object IDs specified for bulk update")
132
+ end
133
+
134
+ if bulk_data[:properties].nil? || bulk_data[:properties].empty?
135
+ raise CmisServer::InvalidOperation.new("No properties specified for bulk update")
136
+ end
137
+
138
+ # Valider que tous les object_ids sont des chaînes non vides
139
+ bulk_data[:object_ids].each do |id|
140
+ if id.nil? || id.to_s.strip.empty?
141
+ raise CmisServer::InvalidOperation.new("Invalid object ID: #{id}")
142
+ end
143
+ end
144
+ end
145
+
146
+ def parse_bulk_delete_from_request
147
+ if supports_browser_binding?
148
+ json_data = JSON.parse(request.body.read)
149
+ {
150
+ object_ids: json_data["objectIds"] || [],
151
+ property_ids: json_data["propertyIds"] || []
152
+ }
153
+ else
154
+ doc = Nokogiri::XML(request.body.read)
155
+ doc.remove_namespaces!
156
+
157
+ object_ids = doc.xpath("//objectId").map(&:text)
158
+ property_ids = doc.xpath("//propertyId").map(&:text)
159
+
160
+ { object_ids: object_ids, property_ids: property_ids }
161
+ end
162
+ end
163
+
164
+ def validate_bulk_delete_data(bulk_data)
165
+ if bulk_data[:object_ids].empty?
166
+ raise CmisServer::InvalidOperation.new("No object IDs specified for bulk delete")
167
+ end
168
+
169
+ if bulk_data[:property_ids].empty?
170
+ raise CmisServer::InvalidOperation.new("No property IDs specified for bulk delete")
171
+ end
172
+ end
173
+
174
+ def parse_bulk_move_from_request
175
+ if supports_browser_binding?
176
+ json_data = JSON.parse(request.body.read)
177
+ {
178
+ object_ids: json_data["objectIds"] || [],
179
+ target_folder_id: json_data["targetFolderId"],
180
+ source_folder_id: json_data["sourceFolderId"]
181
+ }
182
+ else
183
+ doc = Nokogiri::XML(request.body.read)
184
+ doc.remove_namespaces!
185
+
186
+ object_ids = doc.xpath("//objectId").map(&:text)
187
+ target_folder_id = doc.at_xpath("//targetFolderId")&.text
188
+ source_folder_id = doc.at_xpath("//sourceFolderId")&.text
189
+
190
+ { object_ids: object_ids, target_folder_id: target_folder_id, source_folder_id: source_folder_id }
191
+ end
192
+ end
193
+
194
+ def validate_bulk_move_data(bulk_data)
195
+ if bulk_data[:object_ids].empty?
196
+ raise CmisServer::InvalidOperation.new("No object IDs specified for bulk move")
197
+ end
198
+
199
+ if bulk_data[:target_folder_id].nil? || bulk_data[:target_folder_id].strip.empty?
200
+ raise CmisServer::InvalidOperation.new("Target folder ID is required for bulk move")
201
+ end
202
+ end
203
+
204
+ def delete_properties_from_objects(bulk_data)
205
+ results = []
206
+
207
+ bulk_data[:object_ids].each do |object_id|
208
+ begin
209
+ object = find_object(object_id)
210
+
211
+ bulk_data[:property_ids].each do |property_id|
212
+ object.properties.delete(property_id)
213
+ end
214
+
215
+ object.save
216
+ results << { id: object_id, status: "success" }
217
+ rescue => e
218
+ results << { id: object_id, status: "error", message: e.message }
219
+ end
220
+ end
221
+
222
+ results
223
+ end
224
+
225
+ def move_objects_bulk(bulk_data)
226
+ results = []
227
+
228
+ bulk_data[:object_ids].each do |object_id|
229
+ begin
230
+ object = find_object(object_id)
231
+ target_folder = find_object(bulk_data[:target_folder_id])
232
+
233
+ object.move_to(target_folder)
234
+ results << { id: object_id, status: "success" }
235
+ rescue => e
236
+ results << { id: object_id, status: "error", message: e.message }
237
+ end
238
+ end
239
+
240
+ results
241
+ end
242
+
243
+ def format_bulk_results_for_json(results)
244
+ {
245
+ results: results,
246
+ totalCount: results.length,
247
+ successCount: results.count { |r| r[:status] == "success" },
248
+ errorCount: results.count { |r| r[:status] == "error" }
249
+ }
250
+ end
251
+
252
+ def supports_browser_binding?
253
+ request.content_type.include?('application/json') ||
254
+ params[:cmisbrowserbinding] == 'true'
255
+ end
256
+
257
+
258
+ def update_objects(bulk_data)
259
+ # Cette méthode est maintenant remplacée par l'utilisation du BulkUpdateService
260
+ # Conservée pour compatibilité si nécessaire
261
+ bulk_service = CmisServer::BulkUpdateService.new(context: context)
262
+ results = bulk_service.bulk_update(bulk_data[:object_ids], bulk_data[:properties])
263
+
264
+ # Formatter pour correspondre à l'ancien format attendu
265
+ formatted_results = []
266
+ results[:succeeded].each { |id| formatted_results << { id: id, status: "success" } }
267
+ results[:failed].each { |failure| formatted_results << { id: failure[:id], status: "error", message: failure[:message] } }
268
+
269
+ formatted_results
270
+ end
271
+
272
+ def find_object(object_id)
273
+ # Cette méthode doit être implémentée selon votre système de stockage
274
+ # Elle devrait retourner l'objet CMIS correspondant à l'ID ou lever une exception
275
+ object = repository_from_request.get_object(object_id)
276
+ raise CmisServer::ObjectNotFound.new unless object
277
+ object
278
+ end
279
+
280
+ def update_object_properties(object, properties)
281
+ # Cette méthode doit être implémentée selon votre système de stockage
282
+ # Elle devrait mettre à jour les propriétés de l'objet
283
+ object.update_properties(properties)
284
+ end
285
+
286
+ def convert_property_value(value, type)
287
+ case type
288
+ when "propertyInteger"
289
+ value.to_i
290
+ when "propertyDecimal", "propertyDouble"
291
+ value.to_f
292
+ when "propertyBoolean"
293
+ value.downcase == "true"
294
+ when "propertyDateTime"
295
+ DateTime.parse(value)
296
+ else
297
+ value
298
+ end
299
+ end
300
+ end
301
+ end
302
+ end
@@ -0,0 +1,178 @@
1
+ module CmisServer
2
+ module AtomPub
3
+ class ContentController < CmisServer::AtomPub::BaseController
4
+
5
+ include CmisServer::AtomPub::RepositoryScopable
6
+ before_action :check_appendable_capability, only: [:append]
7
+
8
+ def show
9
+ content_id=params[:id]
10
+ doc_id =content_id[3..-1]
11
+ document =CmisServer::DocumentAdapter::ClassAdapter.new(context: context).find(doc_id)
12
+ if document&&(cs=document.content_stream)
13
+ send_data(cs.content,
14
+ filename: cs.filename,
15
+ type: cs.media_type
16
+ )
17
+ else
18
+ head :not_found
19
+ end
20
+ end
21
+
22
+ # POST /content/{id}/append
23
+ # Implémentation de appendContentStream pour CMIS 1.1
24
+ def append
25
+ content_id = params[:id]
26
+ doc_id = content_id[3..-1]
27
+ document = CmisServer::DocumentAdapter::ClassAdapter.new(context: context).find(doc_id)
28
+
29
+ raise CmisServer::ObjectNotFound.new unless document
30
+
31
+ # Vérifier que l'objet est un document
32
+ raise CmisServer::InvalidOperation.new("Object is not a document") unless document.is_a?(CmisServer::DocumentObject)
33
+
34
+ # Vérifier que le document a déjà un content stream
35
+ raise CmisServer::InvalidOperation.new("Document has no content stream") unless document.content_stream
36
+
37
+ # Utiliser le service dédié pour l'opération
38
+ content_service = CmisServer::ContentStreamService.new(context: context)
39
+ new_content = parse_content_from_request
40
+
41
+ updated_document = content_service.append_content_stream(document, new_content)
42
+
43
+ respond_to do |format|
44
+ format.atom_entry { render "cmis_server/atom_pub/document_entry", locals: { document: updated_document } }
45
+ format.json { render json: updated_document.to_json }
46
+ end
47
+ rescue => e
48
+ render_error(e)
49
+ end
50
+
51
+ # PUT /content/{id} - setContentStream
52
+ def set_content_stream
53
+ content_id = params[:id]
54
+ doc_id = content_id[3..-1]
55
+ document = CmisServer::DocumentAdapter::ClassAdapter.new(context: context).find(doc_id)
56
+
57
+ raise CmisServer::ObjectNotFound.new unless document
58
+
59
+ overwrite_flag = params[:overwriteFlag] != 'false'
60
+ new_content = parse_content_from_request
61
+
62
+ content_service = CmisServer::ContentStreamService.new(context: context)
63
+ updated_document = content_service.set_content_stream(document, new_content, overwrite_flag)
64
+
65
+ respond_to do |format|
66
+ format.atom_entry { render "cmis_server/atom_pub/document_entry", locals: { document: updated_document } }
67
+ format.json { render json: updated_document.to_json }
68
+ end
69
+ rescue => e
70
+ render_error(e)
71
+ end
72
+
73
+ # DELETE /content/{id} - deleteContentStream
74
+ def delete_content_stream
75
+ content_id = params[:id]
76
+ doc_id = content_id[3..-1]
77
+ document = CmisServer::DocumentAdapter::ClassAdapter.new(context: context).find(doc_id)
78
+
79
+ raise CmisServer::ObjectNotFound.new unless document
80
+
81
+ content_service = CmisServer::ContentStreamService.new(context: context)
82
+ updated_document = content_service.delete_content_stream(document)
83
+
84
+ respond_to do |format|
85
+ format.atom_entry { render "cmis_server/atom_pub/document_entry", locals: { document: updated_document } }
86
+ format.json { render json: updated_document.to_json }
87
+ end
88
+ rescue => e
89
+ render_error(e)
90
+ end
91
+
92
+ private
93
+
94
+ def check_appendable_capability
95
+ repository = repository_from_request
96
+
97
+ unless repository.capabilities[:append_content_stream]
98
+ raise CmisServer::NotSupported.new(
99
+ "Repository does not support appending to content streams"
100
+ )
101
+ end
102
+ end
103
+
104
+ def parse_content_from_request
105
+ # Détection améliorée du type de contenu pour CMIS 1.1
106
+ if request.content_type =~ /multipart\/form-data/
107
+ # Contenu provenant d'un formulaire multipart
108
+ file = params[:file]
109
+
110
+ validate_content_stream(file)
111
+
112
+ CmisServer::ContentStream.new(
113
+ id: SecureRandom.uuid,
114
+ base64: Base64.strict_encode64(file.read),
115
+ media_type: file.content_type,
116
+ filename: file.original_filename,
117
+ length: file.size
118
+ )
119
+ else
120
+ # Contenu brut avec validation
121
+ body_content = request.body.read
122
+ validate_raw_content(body_content)
123
+
124
+ CmisServer::ContentStream.new(
125
+ id: SecureRandom.uuid,
126
+ base64: Base64.strict_encode64(body_content),
127
+ media_type: request.content_type || 'application/octet-stream',
128
+ filename: params[:filename] || detect_filename_from_headers || "unnamed",
129
+ length: body_content.length
130
+ )
131
+ end
132
+ end
133
+
134
+ def validate_content_stream(file)
135
+ # Validation des limites de taille et types MIME
136
+ max_size = repository_from_request.capabilities[:max_content_size_kb] || 100_000 # 100MB par défaut
137
+
138
+ if file.size > max_size * 1024
139
+ raise CmisServer::StreamNotSupported.new("Content size exceeds maximum allowed size")
140
+ end
141
+
142
+ allowed_mime_types = repository_from_request.capabilities[:allowed_mime_types]
143
+ if allowed_mime_types && !allowed_mime_types.include?(file.content_type)
144
+ raise CmisServer::StreamNotSupported.new("MIME type '#{file.content_type}' not allowed")
145
+ end
146
+ end
147
+
148
+ def validate_raw_content(content)
149
+ max_size = repository_from_request.capabilities[:max_content_size_kb] || 100_000
150
+
151
+ if content.length > max_size * 1024
152
+ raise CmisServer::StreamNotSupported.new("Content size exceeds maximum allowed size")
153
+ end
154
+ end
155
+
156
+ def detect_filename_from_headers
157
+ # Essayer d'extraire le nom de fichier des headers HTTP
158
+ content_disposition = request.headers['Content-Disposition']
159
+ return nil unless content_disposition
160
+
161
+ match = content_disposition.match(/filename=["']?([^"'\s;]+)["']?/)
162
+ match[1] if match
163
+ end
164
+
165
+ def merge_content_streams(existing, new_content)
166
+ # Créer un nouveau ContentStream avec le contenu fusionné
167
+ merged_content = CmisServer::ContentStream.new(
168
+ id: existing.id,
169
+ base64: Base64.strict_encode64(existing.content + new_content.content),
170
+ media_type: existing.media_type,
171
+ filename: existing.filename
172
+ )
173
+
174
+ merged_content
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,41 @@
1
+ module CmisServer
2
+ module AtomPub
3
+ class EntriesController < CmisServer::AtomPub::BaseController
4
+
5
+ include CmisServer::AtomPub::RepositoryScopable
6
+
7
+ def show
8
+ @object=object_service.get_object(id, with_object: true, filter: filter)
9
+ render 'cmis_server/atom_pub/entries/object_entry', formats: :atom_entry
10
+ end
11
+
12
+ def update
13
+ @object=object_service.update_properties(id, request_entry_parser.object_properties, with_object: true)
14
+ render 'cmis_server/atom_pub/entries/object_entry', formats: :atom_entry, status: 200
15
+ end
16
+
17
+ def destroy
18
+
19
+ end
20
+
21
+ protected
22
+
23
+ def filter
24
+ params[:filter].to_s.split(',')
25
+ end
26
+
27
+ def id
28
+ params.require(:id)
29
+ end
30
+
31
+ def object_service
32
+ ObjectService.new(@repository, context)
33
+ end
34
+
35
+ def set_format
36
+ request.format = :atom_entry
37
+ end
38
+
39
+ end
40
+ end
41
+ end