effective_learndash 0.1.0

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 (37) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +126 -0
  4. data/Rakefile +18 -0
  5. data/app/assets/config/effective_learndash_manifest.js +3 -0
  6. data/app/assets/javascripts/effective_learndash/base.js +0 -0
  7. data/app/assets/javascripts/effective_learndash.js +1 -0
  8. data/app/assets/stylesheets/effective_learndash/base.scss +0 -0
  9. data/app/assets/stylesheets/effective_learndash.scss +1 -0
  10. data/app/controllers/admin/learndash_courses_controller.rb +15 -0
  11. data/app/controllers/admin/learndash_enrollments_controller.rb +9 -0
  12. data/app/controllers/admin/learndash_users_controller.rb +11 -0
  13. data/app/datatables/admin/effective_learndash_courses_datatable.rb +26 -0
  14. data/app/datatables/admin/effective_learndash_enrollments_datatable.rb +39 -0
  15. data/app/datatables/admin/effective_learndash_users_datatable.rb +25 -0
  16. data/app/helpers/effective_learndash_helper.rb +2 -0
  17. data/app/models/concerns/effective_learndash_owner.rb +64 -0
  18. data/app/models/effective/learndash_api.rb +269 -0
  19. data/app/models/effective/learndash_course.rb +39 -0
  20. data/app/models/effective/learndash_enrollment.rb +73 -0
  21. data/app/models/effective/learndash_user.rb +97 -0
  22. data/app/views/admin/learndash_courses/_learndash_course.html.haml +12 -0
  23. data/app/views/admin/learndash_enrollments/_form.html.haml +16 -0
  24. data/app/views/admin/learndash_owners/_form.html.haml +5 -0
  25. data/app/views/admin/learndash_users/_form.html.haml +6 -0
  26. data/app/views/admin/learndash_users/_learndash_user.html.haml +21 -0
  27. data/config/effective_learndash.rb +18 -0
  28. data/config/routes.rb +25 -0
  29. data/db/migrate/01_create_effective_learndash.rb.erb +48 -0
  30. data/db/seeds.rb +1 -0
  31. data/lib/effective_learndash/engine.rb +22 -0
  32. data/lib/effective_learndash/version.rb +3 -0
  33. data/lib/effective_learndash.rb +52 -0
  34. data/lib/generators/effective_learndash/install_generator.rb +29 -0
  35. data/lib/generators/templates/effective_learndash_mailer_preview.rb +4 -0
  36. data/lib/tasks/effective_learndash_tasks.rake +8 -0
  37. metadata +232 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: '0970ec691c0993f6c77e5131eba3da9ed11ef1c74a5cd00f0cdba5373cf1f5b4'
4
+ data.tar.gz: dd0229161ba6030e9711202259063af16e506ec6d029647d43a112b4ac4f8150
5
+ SHA512:
6
+ metadata.gz: a6ff74e9f0842c69d6f69886a8a7ef86b1196bbfd83e803afdd58e075c8ac99371a4b125d22b67e136268b4b5d4ac6718371b3ff6db936abddcb123ac3d8b28a
7
+ data.tar.gz: e742085fcf413769c5936203091663b95d704a98d4b26b3778d77668f62d7ec79a53c52ffade1e9cf0d4cf56941ab83797d67a3efac1905ae30755f416958ff2
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2022 Code and Effect Inc.
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.md ADDED
@@ -0,0 +1,126 @@
1
+ # Effective Learndash
2
+
3
+ Create Wordpress users and read Learndash course progress. This is an unoffocial integration that is not supported or affiliated with WordPress or Learndash.
4
+
5
+ ## Getting Started
6
+
7
+ This requires Rails 6+ and Twitter Bootstrap 4 and just works with Devise.
8
+
9
+ Please first install the [effective_datatables](https://github.com/code-and-effect/effective_datatables) gem.
10
+
11
+ Please download and install the [Twitter Bootstrap4](http://getbootstrap.com)
12
+
13
+ Add to your Gemfile:
14
+
15
+ ```ruby
16
+ gem 'haml-rails' # or try using gem 'hamlit-rails'
17
+ gem 'effective_learndash'
18
+ ```
19
+
20
+ Run the bundle command to install it:
21
+
22
+ ```console
23
+ bundle install
24
+ ```
25
+
26
+ Then run the generator:
27
+
28
+ ```ruby
29
+ rails generate effective_learndash:install
30
+ ```
31
+
32
+ The generator will install an initializer which describes all configuration options and creates a database migration.
33
+
34
+ If you want to tweak the table names, manually adjust both the configuration file and the migration now.
35
+
36
+ Then migrate the database:
37
+
38
+ ```ruby
39
+ rake db:migrate
40
+ ```
41
+
42
+ Add to your user class:
43
+
44
+ ```
45
+ class User < ApplicationRecord
46
+ effective_learndash_owner
47
+ end
48
+ ```
49
+
50
+ Add a link to the admin menu:
51
+
52
+ ```haml
53
+ - if can? :admin, :effective_learndash
54
+ = nav_dropdown 'Learndash' do
55
+ - if can? :index, Effective::LearndashUser
56
+ = nav_link_to 'Learndash Users', effective_learndash.admin_learndash_users_path
57
+
58
+ - if can? :index, Effective::LearndashCourse
59
+ = nav_link_to 'Learndash Courses', effective_learndash.admin_learndash_courses_path
60
+
61
+ - if can? :index, Effective::LearndashEnrollment
62
+ = nav_link_to 'Learndash Enrollments', effective_learndash.admin_learndash_enrollments_path
63
+ ```
64
+
65
+ ## Authorization
66
+
67
+ All authorization checks are handled via the effective_resources gem found in the `config/initializers/effective_resources.rb` file.
68
+
69
+ ## Permissions
70
+
71
+ The permissions you actually want to define are as follows (using CanCan):
72
+
73
+ ```ruby
74
+ if user.admin?
75
+ can :admin, :effective_learndash
76
+
77
+ can(crud + [:refresh], Effective::LearndashUser)
78
+ can(crud + [:refresh], Effective::LearndashCourse)
79
+
80
+ can(crud, Effective::LearndashEnrollment)
81
+ can(:refresh, Effective::LearndashEnrollment) { |enrollment| !enrollment.completed? }
82
+ end
83
+ ```
84
+
85
+ ## Configuring Learndash
86
+
87
+ Your Wordpress should be configured ahead of time with the Learndash plugin.
88
+
89
+ Please generate an application password via:
90
+
91
+ https://make.wordpress.org/core/2020/11/05/application-passwords-integration-guide/
92
+
93
+ and fill in the username/password details in config/initializers/effective_leardash.rb
94
+
95
+ ## Working with Learndash
96
+
97
+ Visit `/admin/learndash_courses` and Refresh the list of Courses.
98
+
99
+ Create a New Learndash User will create a new Wordpress/Learndash account with a username/password according to the settings in the config file.
100
+
101
+ When you create a user, you only get access to the password once. So any existing users will have an unknown password.
102
+
103
+ Create a new Learndash Enrollment to enroll a learndash user into a course. This will begin tracking their progress.
104
+
105
+ There are no webhooks or callbacks from Learndash, everything is a GET request that updates the local database.
106
+
107
+ You can refresh an entire learndash user in one operation and it will sync the entire user at once, `user.learndash_user.refresh!`
108
+
109
+ ## License
110
+
111
+ MIT License. Copyright [Code and Effect Inc.](http://www.codeandeffect.com/)
112
+
113
+ ## Testing
114
+
115
+ ```ruby
116
+ rails test
117
+ ```
118
+
119
+ ## Contributing
120
+
121
+ 1. Fork it
122
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
123
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
124
+ 4. Push to the branch (`git push origin my-new-feature`)
125
+ 5. Bonus points for test coverage
126
+ 6. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"
9
+
10
+ require "rake/testtask"
11
+
12
+ Rake::TestTask.new(:test) do |t|
13
+ t.libs << 'test'
14
+ t.pattern = 'test/**/*_test.rb'
15
+ t.verbose = false
16
+ end
17
+
18
+ task default: :test
@@ -0,0 +1,3 @@
1
+ //= link_directory ../javascripts .js
2
+ //= link_directory ../stylesheets .css
3
+ //= link_directory ../images/effective_learndash
@@ -0,0 +1 @@
1
+ //= require_tree ./effective_learndash
@@ -0,0 +1 @@
1
+ @import 'effective_learndash/base';
@@ -0,0 +1,15 @@
1
+ module Admin
2
+ class LearndashCoursesController < ApplicationController
3
+ before_action(:authenticate_user!) if defined?(Devise)
4
+ before_action { EffectiveResources.authorize!(self, :admin, :effective_learndash) }
5
+
6
+ include Effective::CrudController
7
+
8
+ def refresh
9
+ resource_scope.refresh!
10
+ flash[:success] = "Successfully refreshed Courses from Learndash"
11
+ redirect_to effective_learndash.admin_learndash_courses_path
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ module Admin
2
+ class LearndashEnrollmentsController < ApplicationController
3
+ before_action(:authenticate_user!) if defined?(Devise)
4
+ before_action { EffectiveResources.authorize!(self, :admin, :effective_learndash) }
5
+
6
+ include Effective::CrudController
7
+
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ module Admin
2
+ class LearndashUsersController < ApplicationController
3
+ before_action(:authenticate_user!) if defined?(Devise)
4
+ before_action { EffectiveResources.authorize!(self, :admin, :effective_learndash) }
5
+
6
+ include Effective::CrudController
7
+
8
+ on :refresh, success: -> { "Successfully refreshed #{resource} and all course enrollments" }
9
+
10
+ end
11
+ end
@@ -0,0 +1,26 @@
1
+ module Admin
2
+ class EffectiveLearndashCoursesDatatable < Effective::Datatable
3
+ datatable do
4
+ order :title
5
+
6
+ col :id, visible: false
7
+ col :course_id, label: 'Learndash Id', visible: false
8
+
9
+ col :title
10
+ col :status
11
+
12
+ col :link do |course|
13
+ link_to(course.link, course.link, target: '_blank')
14
+ end
15
+
16
+ col :learndash_users, visible: false
17
+
18
+ actions_col
19
+ end
20
+
21
+ collection do
22
+ Effective::LearndashCourse.deep.all
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,39 @@
1
+ module Admin
2
+ class EffectiveLearndashEnrollmentsDatatable < Effective::Datatable
3
+ filters do
4
+ scope :all
5
+ scope :completed
6
+ scope :in_progress
7
+ scope :not_started
8
+ end
9
+
10
+ datatable do
11
+ col :id, visible: false
12
+
13
+ col :last_refreshed, visible: false do |enrollment|
14
+ time_ago_in_words(enrollment.last_synced_at) + ' ago'
15
+ end
16
+
17
+ col :owner, visible: false
18
+
19
+ col :learndash_course
20
+ col :learndash_user
21
+
22
+ col :progress_status
23
+
24
+ col :last_step, visible: false
25
+ col :steps_completed, visible: false
26
+ col :steps_total, visible: false
27
+
28
+ col :date_started, as: :date
29
+ col :date_completed, as: :date
30
+
31
+ actions_col
32
+ end
33
+
34
+ collection do
35
+ Effective::LearndashEnrollment.deep.all
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,25 @@
1
+ module Admin
2
+ class EffectiveLearndashUsersDatatable < Effective::Datatable
3
+ datatable do
4
+ col :id, visible: false
5
+ col :user_id, label: 'Learndash Id', visible: false
6
+
7
+ col :last_refreshed, visible: true do |user|
8
+ time_ago_in_words(user.last_synced_at) + ' ago'
9
+ end
10
+
11
+ col :owner
12
+ col :email
13
+ col :username
14
+ col :password
15
+ col :learndash_courses
16
+
17
+ actions_col
18
+ end
19
+
20
+ collection do
21
+ Effective::LearndashUser.deep.all
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,2 @@
1
+ module EffectiveLearndashHelper
2
+ end
@@ -0,0 +1,64 @@
1
+ # EffectiveLearndashOwner
2
+ #
3
+ # Mark your user model with effective_learndash_owner to get all the includes
4
+
5
+ module EffectiveLearndashOwner
6
+ extend ActiveSupport::Concern
7
+
8
+ module Base
9
+ def effective_learndash_owner
10
+ include ::EffectiveLearndashOwner
11
+ end
12
+ end
13
+
14
+ module ClassMethods
15
+ def effective_learndash_owner?; true; end
16
+ end
17
+
18
+ included do
19
+ # Effective Scoped - this is a has_one in preactice
20
+ has_many :learndash_users, class_name: 'Effective::LearndashUser', as: :owner, inverse_of: :owner, dependent: :delete_all
21
+ accepts_nested_attributes_for :learndash_users, allow_destroy: true
22
+
23
+ # Not used
24
+ has_many :learndash_enrollments, class_name: 'Effective::LearndashEnrollment', as: :owner, inverse_of: :owner, dependent: :delete_all
25
+ accepts_nested_attributes_for :learndash_enrollments, allow_destroy: true
26
+ end
27
+
28
+ # Find
29
+ def learndash_user
30
+ learndash_users.first
31
+ end
32
+
33
+ # Find or create
34
+ def create_learndash_user
35
+ learndash_user || learndash_users.create!(owner: self)
36
+ end
37
+
38
+ # Find
39
+ def learndash_enrollment(course:)
40
+ learndash_user&.enrollment(course: course)
41
+ end
42
+
43
+ # Find or create
44
+ def create_learndash_enrollment(course:)
45
+ raise('expected a persisted learndash_user') unless learndash_user&.persisted?
46
+ learndash_user.create_enrollment(course: course)
47
+ end
48
+
49
+ # Find or sync and check completed?
50
+ def learndash_completed?(course:)
51
+ enrollment = learndash_enrollment(course: course)
52
+
53
+ # We haven't been enrolled
54
+ return false if enrollment.blank?
55
+
56
+ # Return completed right away if previously marked completed
57
+ return true if enrollment.completed?
58
+
59
+ # Check the API
60
+ enrollment.sync!
61
+ enrollment.completed?
62
+ end
63
+
64
+ end
@@ -0,0 +1,269 @@
1
+ require 'net/http'
2
+
3
+ module Effective
4
+ class LearndashApi
5
+
6
+ attr_accessor :url
7
+ attr_accessor :username
8
+ attr_accessor :password
9
+
10
+ def initialize(url:, username:, password:)
11
+ @url = url
12
+ @username = username
13
+ @password = password
14
+ end
15
+
16
+ # Methods
17
+ # https://developer.wordpress.org/rest-api/reference/users/#definition
18
+ # curl --user username:password http://www.example.com/wp-json/wp/v2/users/me
19
+ def me
20
+ get('/wp/v2/users/me')
21
+ end
22
+
23
+ # List users
24
+ def users
25
+ get('/wp/v2/users')
26
+ end
27
+
28
+ # Returns a WP Hash of User or nil
29
+ # This find by EMAIL doesn't work reliably
30
+ def find_user(value)
31
+ # Find by email
32
+ if value.kind_of?(String) && value.include?('@')
33
+ return find_by("/wp/v2/users", :email, value)
34
+ end
35
+
36
+ # Fetch by saved param value
37
+ user_id = user_id(value)
38
+ user = find("/wp/v2/users/#{user_id}", context: :edit) if user_id
39
+ return user if user.present?
40
+
41
+ # Find by email
42
+ email = value.try(:email)
43
+ user = find_by("/wp/v2/users", :email, email) if email
44
+ return user if user.present?
45
+
46
+ # Find by username
47
+ username = username_for(value) if value.class.respond_to?(:effective_learndash_owner?)
48
+ user = find_by("/wp/v2/users", :username, username) if username
49
+ return user if user.present?
50
+
51
+ # Otherwise none
52
+ nil
53
+ end
54
+
55
+ # Create User
56
+ # Usernames can only contain lowercase letters (a-z) and numbers.
57
+ def create_user(owner)
58
+ raise ('expected a leardash owner') unless owner.class.respond_to?(:effective_learndash_owner?)
59
+ raise('owner must have an email') unless owner.try(:email).present?
60
+
61
+ username = username_for(owner)
62
+ password = password_for(owner)
63
+
64
+ payload = {
65
+ username: username,
66
+ password: password,
67
+
68
+ name: owner.to_s,
69
+ email: owner.email,
70
+ roles: ['subscriber'],
71
+
72
+ first_name: owner.try(:first_name),
73
+ last_name: owner.try(:last_name)
74
+ }.compact
75
+
76
+ post("/wp/v2/users", payload.stringify_keys).merge(password: password)
77
+ end
78
+
79
+ # List Courses
80
+ def courses
81
+ get('/ldlms/v2/sfwd-courses')
82
+ end
83
+
84
+ # List User Course Progress
85
+ def user_enrollments(user)
86
+ user = user_id(user) || raise('expected a user')
87
+
88
+ get("/ldlms/v2/users/#{user}/course-progress")
89
+ end
90
+
91
+ # Helper methods for enrollments
92
+ def find_enrollment(enrollment)
93
+ find_user_course(enrollment.learndash_user, enrollment.learndash_course)
94
+ end
95
+
96
+ def create_enrollment(enrollment)
97
+ create_user_course(enrollment.learndash_user, enrollment.learndash_course)
98
+ end
99
+
100
+ # Find User Course Progress
101
+ def find_user_course(user, course)
102
+ user = user_id(user) || raise('expected a user')
103
+ course = course_id(course) || raise('expected a course')
104
+
105
+ find("/ldlms/v2/users/#{user}/course-progress/#{course}")
106
+ end
107
+
108
+ # Crete Course User
109
+ def create_user_course(user, course)
110
+ user = user_id(user) || raise('expected a user')
111
+ course = course_id(course) || raise('expected a course')
112
+
113
+ response = post("/ldlms/v2/sfwd-courses/#{course}/users", user_ids: [user])
114
+
115
+ unless (response.first.fetch(:code) rescue nil) == 'learndash_rest_enroll_success'
116
+ raise("unsuccessful course creation: #{response}")
117
+ end
118
+
119
+ find_user_course(user, course)
120
+ end
121
+
122
+ # private under this point
123
+
124
+
125
+ def user_id(resource)
126
+ if resource.class.respond_to?(:effective_learndash_owner?) # This is a user
127
+ resource.learndash_user&.user_id
128
+ elsif resource.kind_of?(LearndashEnrollment)
129
+ resource.learndash_user&.user_id
130
+ elsif resource.kind_of?(LearndashUser)
131
+ resource.user_id
132
+ else
133
+ resource
134
+ end
135
+ end
136
+
137
+ def course_id(resource)
138
+ if resource.kind_of?(LearndashCourse)
139
+ resource.course_id
140
+ elsif resource.kind_of?(LearndashEnrollment)
141
+ resource.learndash_course&.course_id
142
+ else
143
+ resource
144
+ end
145
+ end
146
+
147
+ def username_for(resource)
148
+ raise('expected a learndash owner') unless resource.class.respond_to?(:effective_learndash_owner?) # This is a user
149
+
150
+ name = EffectiveLearndash.wp_username_for(resource)
151
+ name = "test#{name}" unless Rails.env.production?
152
+ name
153
+ end
154
+
155
+ def password_for(resource)
156
+ raise('expected a learndash owner') unless resource.class.respond_to?(:effective_learndash_owner?) # This is a user
157
+ EffectiveLearndash.wp_password_for(resource)
158
+ end
159
+
160
+ def find(endpoint, params = nil)
161
+ response = get(endpoint, params)
162
+
163
+ if response == false
164
+ nil
165
+ elsif response.kind_of?(Hash) && response.dig(:data, :status) == 404
166
+ nil
167
+ elsif response.kind_of?(Hash)
168
+ response
169
+ elsif response.kind_of?(Array)
170
+ response.first
171
+ else
172
+ raise("unexpected Learndash API response #{response}")
173
+ end
174
+ end
175
+
176
+ # We can't just like, find by email, so we gotta search then filter on our side
177
+ def find_by(endpoint, key, value)
178
+ raise('expected a symbol key') unless key.kind_of?(Symbol)
179
+ raise('expected a value') unless value.present?
180
+
181
+ response = get(endpoint, { search: value, context: :edit })
182
+
183
+ collection = Array(
184
+ if response == false
185
+ nil
186
+ elsif response.kind_of?(Hash) && response.dig(:data, :status) == 404
187
+ nil
188
+ elsif response.kind_of?(Hash)
189
+ response
190
+ elsif response.kind_of?(Array)
191
+ response
192
+ else
193
+ raise("unexpected Learndash API find_by response #{response}")
194
+ end
195
+ )
196
+
197
+ resource = collection.find { |data| data[key] == value }
198
+ resource
199
+ end
200
+
201
+ def get(endpoint, params = nil)
202
+ query = ('?' + params.compact.map { |k, v| "#{k}=#{v}" }.join('&')) if params.present?
203
+
204
+ uri = URI.parse(api_url + endpoint + query.to_s)
205
+
206
+ http = Net::HTTP.new(uri.host, uri.port)
207
+ http.use_ssl = (uri.scheme == 'https')
208
+ http.read_timeout = 10
209
+
210
+ response = with_retries do
211
+ puts("[GET] #{uri}") if Rails.env.development?
212
+ http.get(uri, headers)
213
+ end
214
+
215
+ unless response.code.start_with?('2')
216
+ puts("Response code: #{response.code} #{response.body}") if Rails.env.development?
217
+ return false
218
+ end
219
+
220
+ JSON.parse(response.body, symbolize_names: true)
221
+ end
222
+
223
+ def post(endpoint, params = nil)
224
+ uri = URI.parse(api_url + endpoint)
225
+
226
+ http = Net::HTTP.new(uri.host, uri.port)
227
+ http.use_ssl = (uri.scheme == 'https')
228
+ http.read_timeout = 10
229
+
230
+ response = with_retries do
231
+ puts("[POST] #{uri} #{params}") if Rails.env.development?
232
+ http.post(uri.path, (params || {}).to_json, headers)
233
+ end
234
+
235
+ unless response.code.start_with?('2')
236
+ raise("Invalid Learndash API request: #{response.body}")
237
+ end
238
+
239
+ JSON.parse(response.body, symbolize_names: true)
240
+ end
241
+
242
+ def api_url
243
+ url.chomp('/') + '/wp-json'
244
+ end
245
+
246
+ def headers
247
+ {
248
+ 'Authorization': "Basic #{Base64.strict_encode64("#{username}:#{password}")}",
249
+ 'Accept': 'application/json',
250
+ 'Content-Type': 'application/json'
251
+ }
252
+ end
253
+
254
+ def with_retries(retries: 3, wait: 2, &block)
255
+ raise('expected a block') unless block_given?
256
+
257
+ begin
258
+ return yield
259
+ rescue Exception => e
260
+ if (retries -= 1) > 0
261
+ sleep(wait); retry
262
+ else
263
+ raise
264
+ end
265
+ end
266
+ end
267
+
268
+ end
269
+ end