wordpress_client 0.0.1 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +30 -0
  3. data/.codeclimate.yml +23 -0
  4. data/.gitignore +1 -0
  5. data/.rubocop.yml +147 -90
  6. data/.yardopts +1 -0
  7. data/Changelog.md +16 -0
  8. data/Gemfile +6 -1
  9. data/README.md +48 -14
  10. data/Rakefile +37 -0
  11. data/lib/wordpress_client/category.rb +2 -0
  12. data/lib/wordpress_client/client.rb +235 -79
  13. data/lib/wordpress_client/connection.rb +10 -10
  14. data/lib/wordpress_client/errors.rb +41 -6
  15. data/lib/wordpress_client/media.rb +49 -1
  16. data/lib/wordpress_client/media_parser.rb +3 -0
  17. data/lib/wordpress_client/paginated_collection.rb +61 -4
  18. data/lib/wordpress_client/post.rb +63 -16
  19. data/lib/wordpress_client/post_parser.rb +12 -39
  20. data/lib/wordpress_client/rest_parser.rb +4 -0
  21. data/lib/wordpress_client/tag.rb +2 -0
  22. data/lib/wordpress_client/term.rb +30 -0
  23. data/lib/wordpress_client/version.rb +5 -1
  24. data/lib/wordpress_client.rb +21 -5
  25. data/spec/client_spec.rb +17 -181
  26. data/spec/connection_spec.rb +15 -14
  27. data/spec/docker/Dockerfile +35 -10
  28. data/spec/docker/README.md +53 -16
  29. data/spec/docker/dbdump.sql.gz +0 -0
  30. data/spec/docker/restore-dbdump.sh +2 -2
  31. data/spec/docker/yum.repos.d/CentOS-Base.repo +25 -0
  32. data/spec/fixtures/image-media.json +1 -1
  33. data/spec/fixtures/post-with-metadata.json +99 -1
  34. data/spec/fixtures/simple-post.json +324 -1
  35. data/spec/integration/attachments_crud_spec.rb +1 -1
  36. data/spec/integration/posts_crud_spec.rb +1 -1
  37. data/spec/integration/posts_finding_spec.rb +0 -69
  38. data/spec/integration/posts_metadata_spec.rb +11 -11
  39. data/spec/integration/posts_with_attachments_spec.rb +20 -6
  40. data/spec/media_spec.rb +14 -0
  41. data/spec/post_spec.rb +5 -31
  42. data/spec/spec_helper.rb +1 -0
  43. data/spec/support/docker_runner.rb +33 -13
  44. data/spec/support/wordpress_server.rb +112 -74
  45. data/wordpress_client.gemspec +17 -17
  46. metadata +43 -31
  47. data/.hound.yml +0 -2
  48. data/.ruby-version +0 -1
  49. data/circle.yml +0 -3
  50. data/lib/wordpress_client/replace_metadata.rb +0 -81
  51. data/lib/wordpress_client/replace_terms.rb +0 -62
  52. data/spec/fixtures/post-with-forbidden-metadata.json +0 -1
  53. data/spec/integration/category_assignment_spec.rb +0 -29
  54. data/spec/integration/tag_assignment_spec.rb +0 -29
  55. data/spec/replace_metadata_spec.rb +0 -56
  56. data/spec/replace_terms_spec.rb +0 -51
data/README.md CHANGED
@@ -1,24 +1,31 @@
1
1
  # WordpressClient
2
2
 
3
- WordpressClient is a very simple client to the Wordpress API, version 2 beta 8.0.
3
+ > :warning: **This project is in "maintenance mode" meaning that no patches will be accepted unless security related. We do not recommend using this gem, current users are recommended to migrate to another solution.**
4
4
 
5
- **NOTE:** The repository is still named `wpclient` as we're in the middle of a rename. Some references might persist until it's completed.
5
+ WordpressClient is a very simple client for the Wordpress [REST API][api].
6
6
 
7
- [![Circle CI](https://circleci.com/gh/hemnet/wpclient.svg?style=svg)](https://circleci.com/gh/hemnet/wpclient) [![Code Climate](https://codeclimate.com/repos/5645938269568041da00cded/badges/5e870b57428f23c1f2ff/gpa.svg)](https://codeclimate.com/repos/5645938269568041da00cded/feed) [![Test Coverage](https://codeclimate.com/repos/5645938269568041da00cded/badges/5e870b57428f23c1f2ff/coverage.svg)](https://codeclimate.com/repos/5645938269568041da00cded/coverage)
7
+ [![Circle CI](https://circleci.com/gh/hemnet/wordpress_client.svg?style=svg)](https://circleci.com/gh/hemnet/wordpress_client) [![Code Climate](https://codeclimate.com/repos/5645938269568041da00cded/badges/5e870b57428f23c1f2ff/gpa.svg)](https://codeclimate.com/repos/5645938269568041da00cded/feed) [![Gem Version](https://badge.fury.io/rb/wordpress_client.svg)](https://badge.fury.io/rb/wordpress_client)
8
8
 
9
9
  ## Usage
10
10
 
11
- Initialize a client with a username, password and API URL. You can then search for posts.
11
+ **[Read the full API documentation][docs]**
12
+
13
+ Initialize a client with a user name, password and API URL. You can then search
14
+ for posts.
12
15
 
13
16
  ```ruby
14
- client = WordpressClient.new(url: "https://example.com/wp-json/", username: "example", password: "example")
17
+ client = WordpressClient.new(
18
+ url: "https://example.com/wp-json/",
19
+ username: "example",
20
+ password: "example",
21
+ )
15
22
 
16
23
  client.posts(per_page: 5) # => [WordpressClient::Post, WordpressClient::Post]
17
24
  ```
18
25
 
19
26
  ### Creating a post
20
27
 
21
- You can create posts by calling `create_post`. If you supply a ID, the article will be created using `PUT` instead of `POST`.
28
+ You can create posts by calling `create_post`.
22
29
 
23
30
  ```ruby
24
31
  data = {
@@ -29,20 +36,24 @@ data = {
29
36
  post = client.create_post(data) # => WordpressClient::Post
30
37
  updated_post = client.update_post(post.id, title: "Updated") # => WordpressClient::Post
31
38
 
39
+ updated_post.author # => "Name"
32
40
  updated_post.title_html # => "Updated"
33
41
  ```
34
42
 
35
43
  ## Running tests
36
44
 
37
- You need to install Docker and set it up for your machine. Note that you need `docker-machine` to run Docker on OS X.
45
+ You need to install Docker and set it up for your machine.
38
46
 
39
- Run tests using the normal `rspec` command after installing all bundles. The first time the integration tests are run, a docker image will be built that hosts a Wordpress installation, but the image will be re-used on subsequent runs.
47
+ Run tests using the normal `rspec` command after installing all bundles.
40
48
 
41
49
  ```
42
50
  bundle exec rspec
43
51
  ```
44
52
 
45
- You can also run `bundle exec guard` to have tests run automatically when you change files in the repo. If you tag your examples with `focus: true`, Guard will only run those tests. This can help when doing very focused coding, but remember to remove the filter before you commit and let the entire suite run.
53
+ You can also run `bundle exec guard` to have tests run automatically when you
54
+ change files in the repo. If you tag your examples with `focus: true`, Guard
55
+ will only run those tests. This can help when doing very focused coding, but
56
+ remember to remove the filter before you commit and let the entire suite run.
46
57
 
47
58
  ```ruby
48
59
  describe Foo, focus: true do
@@ -50,14 +61,37 @@ describe Foo, focus: true do
50
61
  end
51
62
  ```
52
63
 
53
- The normal `rspec` command will *not* use this filter in case it is ever committed accidentally, so CI can catch any problems.
64
+ The normal `rspec` command will *not* use this filter in case it is ever
65
+ committed accidentally, so CI can catch any problems.
66
+
67
+ ## Releasing a new version
68
+
69
+ The normal gem release cycle works using `rake release`.
70
+
71
+ If you make changes to the docker image, you can release it using `rake
72
+ docker:release`.
54
73
 
55
74
  ## Copyright & License
56
75
 
57
- Copyright © 2015 Hemnet Service HNS AB
76
+ Copyright © 2015-2017 Hemnet Service HNS AB
77
+
78
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
79
+ this software and associated documentation files (the "Software"), to deal in
80
+ the Software without restriction, including without limitation the rights to
81
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
82
+ of the Software, and to permit persons to whom the Software is furnished to do
83
+ so, subject to the following conditions:
58
84
 
59
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
85
+ The above copyright notice and this permission notice shall be included in all
86
+ copies or substantial portions of the Software.
60
87
 
61
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
88
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
89
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
90
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
91
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
92
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
93
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
94
+ SOFTWARE.
62
95
 
63
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
96
+ [api]: https://developer.wordpress.org/rest-api/
97
+ [docs]: http://www.rubydoc.info/gems/wordpress_client/
data/Rakefile CHANGED
@@ -1 +1,38 @@
1
1
  require "bundler/gem_tasks"
2
+ require "yard"
3
+ require "wordpress_client/version"
4
+
5
+ YARD::Rake::YardocTask.new
6
+
7
+ namespace :docker do
8
+ DOCKER_DIR = File.expand_path("../spec/docker", __FILE__).freeze
9
+ IMAGE_NAME = "hemnet/wordpress_client_test".freeze
10
+ DEV_IMAGE = [IMAGE_NAME, "dev"].join(":").freeze
11
+
12
+ desc "Build the docker image"
13
+ task :build do
14
+ sh "docker", "build", "-t", DEV_IMAGE, DOCKER_DIR
15
+ end
16
+
17
+ desc "Release current dev build"
18
+ task release: :build do
19
+ version = prompt "Which version do you want to release"
20
+ raise "Invalid version string" unless version =~ /\A[\d.]+\z/
21
+
22
+ latest = prompt "Do you want this to be the :latest release? [Y/n]"
23
+ latest = (latest.empty? || latest.casecmp("y").zero?)
24
+
25
+ sh "docker", "tag", DEV_IMAGE, "#{IMAGE_NAME}:#{version}"
26
+ sh "docker", "push", "#{IMAGE_NAME}:#{version}"
27
+
28
+ if latest
29
+ sh "docker", "tag", DEV_IMAGE, "#{IMAGE_NAME}:latest"
30
+ sh "docker", "push", "#{IMAGE_NAME}:latest"
31
+ end
32
+ end
33
+ end
34
+
35
+ def prompt(message)
36
+ print "#{message} > "
37
+ STDIN.gets.strip
38
+ end
@@ -1,4 +1,6 @@
1
1
  module WordpressClient
2
+ # Represents a category from Wordpress.
3
+ # @see Term
2
4
  class Category < Term
3
5
  end
4
6
  end
@@ -4,108 +4,267 @@ module WordpressClient
4
4
  @connection = connection
5
5
  end
6
6
 
7
- def posts(per_page: 10, page: 1, category_slug: nil, tag_slug: nil)
8
- filter = {}
9
- filter[:category_name] = category_slug if category_slug
10
- filter[:tag] = tag_slug if tag_slug
7
+ # @!group Posts
8
+
9
+ # Find {Post Posts} matching given parameters.
10
+ #
11
+ # @example Finding 5 posts
12
+ # posts = client.posts(per_page: 5)
13
+ #
14
+ # @param page [Fixnum] Current page for pagination. Defaults to 1.
15
+ # @param per_page [Fixnum] Posts per page. Defaults to 10.
16
+ #
17
+ # @return {PaginatedCollection[Post]} Paginated collection of the found posts.
18
+ def posts(per_page: 10, page: 1)
11
19
  connection.get_multiple(
12
- Post, "posts", per_page: per_page, page: page, _embed: nil, context: "edit", filter: filter
20
+ Post,
21
+ "posts",
22
+ per_page: per_page,
23
+ page: page,
24
+ _embed: nil,
13
25
  )
14
26
  end
15
27
 
16
- def categories(per_page: 10, page: 1)
17
- connection.get_multiple(Category, "terms/category", page: page, per_page: per_page)
28
+ # Find the {Post} with the given ID, or raises an error if not found.
29
+ #
30
+ # @return {Post}
31
+ # @raise {NotFoundError}
32
+ # @raise {subclasses of Error} on other unexpected errors
33
+ def find_post(id)
34
+ connection.get(Post, "posts/#{id.to_i}", _embed: nil)
18
35
  end
19
36
 
20
- def tags(per_page: 10, page: 1)
21
- connection.get_multiple(Tag, "terms/tag", page: page, per_page: per_page)
37
+ # Create a new {Post} with the given attributes in Wordpress and return it.
38
+ #
39
+ # In addition to {http://v2.wp-api.org/reference/posts/ the accepted
40
+ # parameters of the API}, this method also takes the following keys:
41
+ # * +:meta+
42
+ # * +:category_ids+
43
+ # * +:tag_ids+
44
+ #
45
+ # @see http://v2.wp-api.org/reference/posts/ List of accepted parameters
46
+ # @param attributes [Hash<Symbol,Object>] attribute list, containing
47
+ # accepted parameters or the custom parameters listed
48
+ # above.
49
+ # @option attributes [Hash<String,String>] meta Hash of meta values.
50
+ # @option attributes [Array<Fixnum>] category_ids List of category IDs the
51
+ # Post should belong to.
52
+ # @option attributes [Array<Fixnum>] tag_ids List of tag IDs the Post
53
+ # should have.
54
+ #
55
+ # @return {Post}
56
+ # @raise {ValidationError}
57
+ # @raise {subclasses of Error} on other unexpected errors
58
+ def create_post(attributes)
59
+ connection.create(Post, "posts", attributes, redirect_params: {_embed: nil})
22
60
  end
23
61
 
24
- def media(per_page: 10, page: 1)
25
- connection.get_multiple(Media, "media", page: page, per_page: per_page)
62
+ # Update the {Post} with the given id, setting the supplied attributes in
63
+ # Wordpress and returning an updated Post.
64
+ #
65
+ # In addition to {http://v2.wp-api.org/reference/posts/ the accepted
66
+ # parameters of the API}, this method also takes the following keys:
67
+ # * +:meta+
68
+ # * +:category_ids+
69
+ # * +:tag_ids+
70
+ #
71
+ # @example Changing the title of a Post
72
+ # new_post = client.update_post(post.id, title: "A better title")
73
+ # new_post.title_html #=> "A better title"
74
+ #
75
+ # @see http://v2.wp-api.org/reference/posts/ List of accepted parameters
76
+ # @param id [Fixnum] ID of the post to update.
77
+ # @param attributes [Hash<Symbol,Object>] attribute list, containing
78
+ # accepted parameters or the custom parameters listed
79
+ # above.
80
+ # @option attributes [Hash<String,String>] meta Hash of meta values.
81
+ # @option attributes [Array<Fixnum>] category_ids List of category IDs the
82
+ # Post should belong to.
83
+ # @option attributes [Array<Fixnum>] tag_ids List of tag IDs the Post
84
+ # should have.
85
+ #
86
+ # @return {Post}
87
+ # @raise {NotFoundError}
88
+ # @raise {ValidationError}
89
+ # @raise {subclasses of Error} on other unexpected errors
90
+ def update_post(id, attributes)
91
+ connection.put(Post, "posts/#{id.to_i}", attributes)
26
92
  end
27
93
 
28
- def find_post(id)
29
- connection.get(Post, "posts/#{id.to_i}", _embed: nil, context: "edit")
94
+ # Deletes the {Post} with the given ID.
95
+ #
96
+ # @param id [Fixnum] The {Post} ID.
97
+ # @param force [Boolean] When +false+, the Post will be put in the "Trash"
98
+ # of Wordpress. +true+ causes the Post to be irrevocably deleted.
99
+ #
100
+ # @return true always
101
+ def delete_post(id, force: false)
102
+ connection.delete("posts/#{id.to_i}", {"force" => force})
30
103
  end
31
104
 
32
- def find_by_slug(slug)
33
- posts = connection.get_multiple(
34
- Post, "posts", per_page: 1, page: 1, filter: {name: slug}, _embed: nil
35
- )
36
- if posts.size > 0
37
- posts.first
38
- else
39
- raise NotFoundError, "Could not find post with slug #{slug.to_s.inspect}"
40
- end
105
+ # @!group Categories
106
+
107
+ # Find {Category Categories} in the Wordpress install.
108
+ #
109
+ # @return {PaginatedCollection[Category]}
110
+ def categories(per_page: 10, page: 1)
111
+ connection.get_multiple(Category, "categories", page: page, per_page: per_page)
41
112
  end
42
113
 
114
+ # Find {Category} with the given ID.
115
+ #
116
+ # @return {Category}
117
+ # @raise {NotFoundError}
118
+ # @raise {subclasses of Error} on other unexpected errors
43
119
  def find_category(id)
44
- connection.get(Category, "terms/category/#{id.to_i}")
120
+ connection.get(Category, "categories/#{id.to_i}")
45
121
  end
46
122
 
47
- def find_tag(id)
48
- connection.get(Tag, "terms/tag/#{id.to_i}")
123
+ # Create a new {Category} with the given attributes.
124
+ #
125
+ # @see http://v2.wp-api.org/reference/taxonomies/ List of accepted parameters
126
+ # @param attributes [Hash<Symbol,Object>] attribute list, containing
127
+ # parameters accepted by the API.
128
+ # @option attributes [String] name Name of the category (required).
129
+ # @option attributes [String] slug Slug of the category (optional).
130
+ # @option attributes [String] description Description of the category (optional).
131
+ #
132
+ # @return {Category} the new Category
133
+ # @raise {ValidationError}
134
+ # @raise {subclasses of Error} on other unexpected errors
135
+ def create_category(attributes)
136
+ connection.create(Category, "categories", attributes)
49
137
  end
50
138
 
51
- def find_media(id)
52
- connection.get(Media, "media/#{id.to_i}")
139
+ # Update the {Category} with the given id, setting the supplied attributes.
140
+ #
141
+ # @see http://v2.wp-api.org/reference/taxonomies/ List of accepted parameters
142
+ # @param attributes [Hash<Symbol,Object>] attribute list, containing
143
+ # parameters accepted by the API.
144
+ # @option attributes [String] name Name of the category.
145
+ # @option attributes [String] slug Slug of the category.
146
+ # @option attributes [String] description Description of the category.
147
+ #
148
+ # @return {Category} the updated Category
149
+ # @raise {NotFoundError}
150
+ # @raise {ValidationError}
151
+ # @raise {subclasses of Error} on other unexpected errors
152
+ def update_category(id, attributes)
153
+ connection.put(Category, "categories/#{id.to_i}", attributes)
53
154
  end
54
155
 
55
- def create_post(attributes)
56
- post = connection.create(Post, "posts", attributes, redirect_params: {_embed: nil})
57
-
58
- changes = 0
59
- changes += assign_meta(post, attributes[:meta])
60
- changes += assign_categories(post, attributes[:category_ids])
61
- changes += assign_tags(post, attributes[:tag_ids])
156
+ # @!group Tags
62
157
 
63
- if changes > 0
64
- find_post(post.id)
65
- else
66
- post
67
- end
158
+ # Find {Tag Tags} in the Wordpress install.
159
+ #
160
+ # @return {PaginatedCollection[Tag]}
161
+ def tags(per_page: 10, page: 1)
162
+ connection.get_multiple(Tag, "tags", page: page, per_page: per_page)
68
163
  end
69
164
 
70
- def create_category(attributes)
71
- connection.create(Category, "terms/category", attributes)
165
+ # Find {Tag} with the given ID.
166
+ #
167
+ # @return {Tag}
168
+ # @raise {NotFoundError}
169
+ # @raise {subclasses of Error} on other unexpected errors
170
+ def find_tag(id)
171
+ connection.get(Tag, "tags/#{id.to_i}")
72
172
  end
73
173
 
174
+ # Create a new {Tag} with the given attributes.
175
+ #
176
+ # @see http://v2.wp-api.org/reference/taxonomies/ List of accepted parameters
177
+ # @param attributes [Hash<Symbol,Object>] attribute list, containing
178
+ # parameters accepted by the API.
179
+ # @option attributes [String] name Name of the tag (required).
180
+ # @option attributes [String] slug Slug of the tag (optional).
181
+ # @option attributes [String] description Description of the tag (optional).
182
+ #
183
+ # @return {Tag} the new Tag
184
+ # @raise {ValidationError}
185
+ # @raise {subclasses of Error} on other unexpected errors
74
186
  def create_tag(attributes)
75
- connection.create(Tag, "terms/tag", attributes)
187
+ connection.create(Tag, "tags", attributes)
76
188
  end
77
189
 
78
- def update_post(id, attributes)
79
- post = connection.patch(Post, "posts/#{id.to_i}?_embed", attributes)
80
-
81
- changes = 0
82
- changes += assign_meta(post, attributes[:meta])
83
- changes += assign_categories(post, attributes[:category_ids])
84
- changes += assign_tags(post, attributes[:tag_ids])
85
-
86
- if changes > 0
87
- find_post(post.id)
88
- else
89
- post
90
- end
190
+ # Update the {Tag} with the given id, setting the supplied attributes.
191
+ #
192
+ # @see http://v2.wp-api.org/reference/taxonomies/ List of accepted parameters
193
+ # @param attributes [Hash<Symbol,Object>] attribute list, containing
194
+ # parameters accepted by the API.
195
+ # @option attributes [String] name Name of the tag.
196
+ # @option attributes [String] slug Slug of the tag.
197
+ # @option attributes [String] description Description of the tag.
198
+ #
199
+ # @return {Tag} the updated Tag
200
+ # @raise {NotFoundError}
201
+ # @raise {ValidationError}
202
+ # @raise {subclasses of Error} on other unexpected errors
203
+ def update_tag(id, attributes)
204
+ connection.put(Tag, "tags/#{id.to_i}", attributes)
91
205
  end
92
206
 
93
- def update_category(id, attributes)
94
- connection.patch(Category, "terms/category/#{id.to_i}", attributes)
95
- end
207
+ # @!group Media
96
208
 
97
- def update_tag(id, attributes)
98
- connection.patch(Tag, "terms/tag/#{id.to_i}", attributes)
209
+ # Find {Media} in the Wordpress install.
210
+ #
211
+ # @return {PaginatedCollection[Media]}
212
+ def media(per_page: 10, page: 1)
213
+ connection.get_multiple(Media, "media", page: page, per_page: per_page)
99
214
  end
100
215
 
101
- def update_media(id, attributes)
102
- connection.patch(Media, "media/#{id.to_i}", attributes)
216
+ # Find {Media} with the given ID.
217
+ #
218
+ # @return {Media}
219
+ # @raise {NotFoundError}
220
+ # @raise {subclasses of Error} on other unexpected errors
221
+ def find_media(id)
222
+ connection.get(Media, "media/#{id.to_i}")
103
223
  end
104
224
 
225
+ # Create a new {Media} by uploading a IO stream.
226
+ #
227
+ # You need to provide both MIME type and filename for Wordpress to accept
228
+ # the file.
229
+ #
230
+ # @example Uploading a JPEG from a request
231
+ # media = client.upload(
232
+ # request.body_stream, filename: "foo.jpg", mime_type: "image/jpeg"
233
+ # )
234
+ #
235
+ # @param io [IO-like object] IO stream (for example an open file) that will
236
+ # be the body of the media.
237
+ # @param mime_type [String] the MIME type of the IO stream
238
+ # @param filename [String] the filename that Wordpress should see. Requires
239
+ # a file extension to make Wordpress happy.
240
+ #
241
+ # @return {Media} the new Media
242
+ # @raise {ValidationError}
243
+ # @raise {subclasses of Error} on other unexpected errors
244
+ # @see #upload_file #upload_file - a shortcut for uploading files on disk
105
245
  def upload(io, mime_type:, filename:)
106
246
  connection.upload(Media, "media", io, mime_type: mime_type, filename: filename)
107
247
  end
108
248
 
249
+ # Create a new {Media} by uploading a file from disk.
250
+ #
251
+ # You need to provide MIME type for Wordpress to accept the file. The
252
+ # filename that Wordpress sees will automatically be derived from the
253
+ # passed path.
254
+ #
255
+ # @example Uploading a JPEG from disk
256
+ # media = client.upload_file(
257
+ # "assets/ocean.jpg", mime_type: "image/jpeg"
258
+ # )
259
+ #
260
+ # @param filename [String] a path to a readable file.
261
+ # @param mime_type [String] the MIME type of the file.
262
+ #
263
+ # @return {Media} the new Media
264
+ # @raise {ValidationError}
265
+ # @raise {subclasses of Error} on other unexpected errors
266
+ # @see #upload #upload - for when you want to upload something that isn't a
267
+ # file on disk, or need extra flexibility
109
268
  def upload_file(filename, mime_type:)
110
269
  path = filename.to_s
111
270
  File.open(path, 'r') do |file|
@@ -113,30 +272,27 @@ module WordpressClient
113
272
  end
114
273
  end
115
274
 
116
- def delete_post(id, force: false)
117
- connection.delete("posts/#{id.to_i}", {"force" => force})
275
+ # Update the {Media} with the given id, setting the supplied attributes.
276
+ #
277
+ # @see http://v2.wp-api.org/reference/media/ List of accepted parameters
278
+ # @param attributes [Hash<Symbol,Object>] attribute list, containing
279
+ # parameters accepted by the API.
280
+ #
281
+ # @return {Media} The updated Media
282
+ # @raise {NotFoundError}
283
+ # @raise {ValidationError}
284
+ # @raise {subclasses of Error} on other unexpected errors
285
+ def update_media(id, attributes)
286
+ connection.put(Media, "media/#{id.to_i}", attributes)
118
287
  end
119
288
 
289
+ # @!endgroup
290
+
120
291
  def inspect
121
292
  "#<WordpressClient::Client #{connection.inspect}>"
122
293
  end
123
294
 
124
295
  private
125
296
  attr_reader :connection
126
-
127
- def assign_categories(post, ids)
128
- return 0 unless ids
129
- ReplaceTerms.apply_categories(connection, post, ids)
130
- end
131
-
132
- def assign_tags(post, ids)
133
- return 0 unless ids
134
- ReplaceTerms.apply_tags(connection, post, ids)
135
- end
136
-
137
- def assign_meta(post, meta)
138
- return 0 unless meta
139
- ReplaceMetadata.apply(connection, post, meta)
140
- end
141
297
  end
142
298
  end
@@ -2,6 +2,7 @@ require "faraday"
2
2
  require "json"
3
3
 
4
4
  module WordpressClient
5
+ # @private
5
6
  class Connection
6
7
  attr_reader :url, :username
7
8
 
@@ -49,14 +50,14 @@ module WordpressClient
49
50
  true
50
51
  end
51
52
 
52
- def patch(model, path, attributes)
53
+ def put(model, path, attributes)
53
54
  model.parse(
54
- parse_json_response(send_json(path, attributes, method: :patch))
55
+ parse_json_response(send_json(path, attributes, method: :put))
55
56
  )
56
57
  end
57
58
 
58
- def patch_without_response(path, attributes)
59
- handle_status_code(send_json(path, attributes, method: :patch))
59
+ def put_without_response(path, attributes)
60
+ handle_status_code(send_json(path, attributes, method: :put))
60
61
  true
61
62
  end
62
63
 
@@ -65,9 +66,7 @@ module WordpressClient
65
66
  response = post_data(path, body, {
66
67
  "Content-Length" => body.size.to_s,
67
68
  "Content-Type" => mime_type,
68
- # WP API does not parse normal Content-Disposition and instead ops to using their own format
69
- # https://github.com/WP-API/WP-API/issues/1744
70
- "Content-Disposition" => "filename=#{filename || "unnamed"}",
69
+ "Content-Disposition" => 'attachment; filename="' + (filename || "unnamed") + '"',
71
70
  })
72
71
 
73
72
  if response.status == 201 # Created
@@ -88,7 +87,7 @@ module WordpressClient
88
87
  end
89
88
 
90
89
  def setup_network_connection
91
- Faraday.new(url: "#{url}/wp/v2") do |conn|
90
+ Faraday.new(url: File.join(url, "wp/v2")) do |conn|
92
91
  conn.request :basic_auth, username, @password
93
92
  conn.adapter :net_http
94
93
  end
@@ -111,8 +110,9 @@ module WordpressClient
111
110
  def get_json_and_response(path, params = {})
112
111
  response = net.get(path, params)
113
112
  [parse_json_response(response), response]
114
- rescue Faraday::TimeoutError
115
- raise TimeoutError
113
+ rescue Faraday::ConnectionFailed => error
114
+ raise TimeoutError if error.cause.class == Net::OpenTimeout
115
+ raise
116
116
  end
117
117
 
118
118
  def send_json(path, data, method: :post)
@@ -1,10 +1,45 @@
1
1
  module WordpressClient
2
- Error = Class.new(::StandardError)
2
+ # Base class for all errors emitted from this gem. Rescue this in order to
3
+ # catch everything.
4
+ #
5
+ # See the list of subclasses for more specific error types.
6
+ class Error < ::StandardError; end
3
7
 
4
- UnauthorizedError = Class.new(Error)
8
+ # Raised when the clients attempt to do something that the user isn't
9
+ # authorized to do.
10
+ #
11
+ # This could happen if you try to delete a post and the user only has
12
+ # read-only access, for example.
13
+ # It would also happen if you provide bad authentication details.
14
+ #
15
+ # @see Error
16
+ class UnauthorizedError < Error; end
5
17
 
6
- TimeoutError = Class.new(Error)
7
- ServerError = Class.new(Error)
8
- NotFoundError = Class.new(Error)
9
- ValidationError = Class.new(Error)
18
+ # Raised when a request times out.
19
+ #
20
+ # @see Error
21
+ class TimeoutError < Error; end
22
+
23
+ # Raised when the server had an error, or when the server returned something
24
+ # unexpected, despite saying everything went okay. It's the most generic
25
+ # "Something went wrong with the request" error.
26
+ #
27
+ # @see Error
28
+ class ServerError < Error; end
29
+
30
+ # Raised when trying to find a resource that doesn't exist. It will also
31
+ # happen when you try to update a resource that doesn't exist.
32
+ #
33
+ # Lack of authorization can also mask actual resources so it appears that
34
+ # they don't exist.
35
+ #
36
+ # @see Error
37
+ class NotFoundError < Error; end
38
+
39
+ # Raised when the server rejects the body of a request. The error message
40
+ # will often include information about why the body was rejected, but it is
41
+ # not guaranteed.
42
+ #
43
+ # @see Error
44
+ class ValidationError < Error; end
10
45
  end