effective_learndash 0.9.0 → 0.11.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5137c9bb0e056c81ec1cf547ecf71fbbf07199b5a37d015fdadf6a7987857397
4
- data.tar.gz: 76de32ccdad600bc2ea5b124d6686d8284451934c0257af68bb264f01926e0ea
3
+ metadata.gz: 59aa5f415bca0ab3c2cdfa34896bb1e25632bc517b752a3531324c397f7ae2df
4
+ data.tar.gz: a9749e2b97d0b6fda661fc5979d6127504e4cc921c42187766dabfe769d200d5
5
5
  SHA512:
6
- metadata.gz: 8c873ed4d4ab4c21d61863f0ecf455d608df6fbc9afaada4044cee9276cd4ab027cfa78ca1b30db3748fe806ea89a6c5c513af7f6bdf14fefbfaa93a2e75ede4
7
- data.tar.gz: 2d1e4a58440f11211fc27a67fdd4e923eedda689f1bbe99308c9a48b4eaa467128e35bbfb8f8c07e6c4ae376ff6ca24f7d23a755113df007e1a9db8d9dd707f2
6
+ metadata.gz: 1a7b1b381548714df217d64bc91022b70d4f535d9ff942e4cbeb78a57429781e86ad9348bcabce8e795686ee72bc31eba472335abe2102330ec19795306fd767
7
+ data.tar.gz: a5150ba0d68e755e6a91cda4a27fd4d90560f3446d77ef5ab0f82517cff0d6e6d459193d9e8c23fb9a0ef0ffb31402bdef7f240a708dfc3dea57367837ffd5a2
data/README.md CHANGED
@@ -101,6 +101,25 @@ https://make.wordpress.org/core/2020/11/05/application-passwords-integration-gui
101
101
 
102
102
  and fill in the username/password details in config/initializers/effective_learndash.rb
103
103
 
104
+ ## LearnDash API compatibility
105
+
106
+ The integration uses WordPress REST API v2 and LearnDash `ldlms/v2` endpoints.
107
+ LearnDash 4 and 5 course-progress responses are supported: incoming underscore
108
+ statuses such as `in_progress` are stored using the existing `in-progress` format.
109
+ The `date_started_gmt` and `date_completed_gmt` fields are used when supplied;
110
+ older responses without those fields retain their previous UTC interpretation.
111
+ Both object and legacy array responses are supported for individual progress records.
112
+ No database migration is required.
113
+
114
+ Users, courses, user searches, and course-progress collections follow the
115
+ `X-WP-TotalPages` response header with up to 100 records per request. An individual
116
+ resource returning HTTP 404 is treated as missing; other unsuccessful GET requests
117
+ raise `Effective::LearndashApi::Error`, rather than triggering user or enrollment
118
+ creation after an authentication or server failure.
119
+
120
+ See the [LearnDash 5 API changes](https://docs.nexcess.com/software/changelogs/learndash/sfwd-lms/5-0-0/)
121
+ and [WordPress pagination reference](https://developer.wordpress.org/rest-api/using-the-rest-api/pagination/).
122
+
104
123
  ## Working with LearnDash
105
124
 
106
125
  Visit `/admin/learndash_courses` and Refresh the list of Courses.
@@ -20,7 +20,7 @@ class EffectiveCourseRegistrationsDatatable < Effective::Datatable
20
20
 
21
21
  actions_col(actions: []) do |registration|
22
22
  if registration.draft?
23
- dropdown_link_to('Continue', effective_learndash.learndash_course_course_registration_build_path(registration.learndash_course, registration, registration.next_step), 'data-turbolinks' => false)
23
+ dropdown_link_to('Continue', effective_learndash.learndash_course_course_registration_build_path(registration.learndash_course, registration, registration.next_step), 'data-turbolinks' => false, 'data-turbo' => false)
24
24
  dropdown_link_to('Delete', effective_learndash.learndash_course_course_registration_path(registration.learndash_course, registration), 'data-confirm': "Really delete #{registration}?", 'data-method': :delete)
25
25
  else
26
26
  dropdown_link_to('Show', effective_learndash.learndash_course_course_registration_path(registration.learndash_course, registration))
@@ -2,6 +2,7 @@ require 'net/http'
2
2
 
3
3
  module Effective
4
4
  class LearndashApi
5
+ class Error < StandardError; end
5
6
 
6
7
  attr_accessor :url
7
8
  attr_accessor :username
@@ -22,7 +23,7 @@ module Effective
22
23
 
23
24
  # List users
24
25
  def users
25
- get('/wp/v2/users')
26
+ get_collection('/wp/v2/users')
26
27
  end
27
28
 
28
29
  # Returns a WP Hash of User or nil
@@ -90,14 +91,14 @@ module Effective
90
91
 
91
92
  # List Courses
92
93
  def courses
93
- get('/ldlms/v2/sfwd-courses')
94
+ get_collection('/ldlms/v2/sfwd-courses')
94
95
  end
95
96
 
96
97
  # List User Course Progress
97
98
  def user_enrollments(user)
98
99
  user = user_id(user) || raise('expected a user')
99
100
 
100
- get("/ldlms/v2/users/#{user}/course-progress")
101
+ get_collection("/ldlms/v2/users/#{user}/course-progress")
101
102
  end
102
103
 
103
104
  # Helper methods for enrollments
@@ -195,33 +196,41 @@ module Effective
195
196
  raise('expected a symbol key') unless key.kind_of?(Symbol)
196
197
  raise('expected a value') unless value.present?
197
198
 
198
- response = get(endpoint, { search: value, context: :edit })
199
-
200
- collection = Array(
201
- if response == false
202
- nil
203
- elsif response.kind_of?(Hash) && response.dig(:data, :status) == 404
204
- nil
205
- elsif response.kind_of?(Hash)
206
- response
207
- elsif response.kind_of?(Array)
208
- response
209
- else
210
- raise("unexpected LearnDash API find_by response #{response}")
199
+ get_collection(endpoint, search: value, context: :edit).find { |data| data[key] == value }
200
+ end
201
+
202
+ # Follow WordPress pagination headers for collection endpoints only.
203
+ def get_collection(endpoint, params = nil)
204
+ collection = []
205
+ page = 1
206
+ total_pages = 1
207
+
208
+ loop do
209
+ response = get(endpoint, (params || {}).merge(per_page: 100, page: page)) do |http_response|
210
+ total_pages = http_response['X-WP-TotalPages'].to_i if http_response['X-WP-TotalPages'].present?
211
+ end
212
+
213
+ unless response.kind_of?(Array)
214
+ raise(Error, "Unexpected LearnDash API collection: #{endpoint}")
211
215
  end
212
- )
213
216
 
214
- resource = collection.find { |data| data[key] == value }
215
- resource
217
+ collection.concat(response)
218
+ break if page >= total_pages
219
+
220
+ page += 1
221
+ end
222
+
223
+ collection
216
224
  end
217
225
 
218
226
  def get(endpoint, params = nil)
219
- query = ('?' + params.compact.map { |k, v| "#{k}=#{v}" }.join('&')) if params.present?
227
+ query = ('?' + URI.encode_www_form(params.compact)) if params.present?
220
228
 
221
229
  uri = URI.parse(api_url + endpoint + query.to_s)
222
230
 
223
231
  http = Net::HTTP.new(uri.host, uri.port)
224
232
  http.use_ssl = (uri.scheme == 'https')
233
+ http.open_timeout = 10
225
234
  http.read_timeout = 10
226
235
 
227
236
  response = with_retries do
@@ -229,11 +238,10 @@ module Effective
229
238
  http.get(uri, headers)
230
239
  end
231
240
 
232
- unless response.code.start_with?('2')
233
- puts("Response code: #{response.code} #{response.body}") if Rails.env.development?
234
- return false
235
- end
241
+ return false if response.code == '404'
242
+ raise(Error, "Invalid LearnDash API response: HTTP #{response.code}") unless response.code.start_with?('2')
236
243
 
244
+ yield(response) if block_given?
237
245
  JSON.parse(response.body, symbolize_names: true)
238
246
  end
239
247
 
@@ -242,10 +250,11 @@ module Effective
242
250
 
243
251
  http = Net::HTTP.new(uri.host, uri.port)
244
252
  http.use_ssl = (uri.scheme == 'https')
253
+ http.open_timeout = 10
245
254
  http.read_timeout = 10
246
255
 
247
256
  response = with_retries do
248
- puts("[POST] #{uri} #{params}") if Rails.env.development?
257
+ puts("[POST] #{uri}") if Rails.env.development?
249
258
  http.post(uri.path, (params || {}).to_json, headers)
250
259
  end
251
260
 
@@ -106,16 +106,18 @@ module Effective
106
106
  steps_total: data[:steps_total],
107
107
  )
108
108
 
109
- if (date = data[:date_started]).present?
109
+ date = data.key?(:date_started_gmt) ? data[:date_started_gmt] : data[:date_started]
110
+ if date.present?
110
111
  assign_attributes(date_started: Time.use_zone('UTC') { Time.zone.parse(date) })
111
112
  end
112
113
 
113
- if (date = data[:date_completed]).present?
114
+ date = data.key?(:date_completed_gmt) ? data[:date_completed_gmt] : data[:date_completed]
115
+ if date.present?
114
116
  assign_attributes(date_completed: Time.use_zone('UTC') { Time.zone.parse(date) })
115
117
  end
116
118
 
117
119
  unless completed?
118
- assign_attributes(progress_status: data[:progress_status])
120
+ assign_attributes(progress_status: data[:progress_status]&.tr('_', '-'))
119
121
  end
120
122
 
121
123
  true
@@ -12,7 +12,7 @@
12
12
 
13
13
  %p
14
14
  Please
15
- = link_to("Continue registration for #{wizard.learndash_course}", effective_learndash.learndash_course_course_registration_build_path(wizard.learndash_course, wizard, wizard.next_step), 'data-turbolinks' => false, class: 'btn btn-primary')
15
+ = link_to("Continue registration for #{wizard.learndash_course}", effective_learndash.learndash_course_course_registration_build_path(wizard.learndash_course, wizard, wizard.next_step), 'data-turbolinks' => false, 'data-turbo' => false, class: 'btn btn-primary')
16
16
  or you can
17
17
  = link_to('Abandon course registration', effective_learndash.learndash_course_course_registration_path(wizard.learndash_course, wizard), 'data-confirm': "Really delete #{wizard}?", 'data-method': :delete, class: 'btn btn-danger')
18
18
  to register for another course.
@@ -1,3 +1,3 @@
1
1
  module EffectiveLearndash
2
- VERSION = '0.9.0'.freeze
2
+ VERSION = '0.11.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: effective_learndash
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Code and Effect
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-26 00:00:00.000000000 Z
11
+ date: 2026-10-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails