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
@@ -0,0 +1,39 @@
1
+ module Effective
2
+ class LearndashCourse < ActiveRecord::Base
3
+ has_many :learndash_enrollments
4
+ has_many :learndash_users, through: :learndash_enrollments
5
+
6
+ effective_resource do
7
+ # This user the wordpress credentials
8
+ course_id :integer
9
+ status :string
10
+ title :string
11
+
12
+ timestamps
13
+ end
14
+
15
+ scope :deep, -> { all }
16
+ scope :sorted, -> { order(:title) }
17
+
18
+ validates :course_id, presence: true
19
+ validates :status, presence: true
20
+ validates :title, presence: true
21
+
22
+ # Syncs all courses
23
+ def self.refresh!
24
+ courses = all()
25
+
26
+ EffectiveLearndash.api.courses.each do |data|
27
+ course = courses.find { |course| course.course_id == data[:id] } || new()
28
+ course.update!(course_id: data[:id], title: data.dig(:title, :rendered), status: data[:status], link: data[:link])
29
+ end
30
+
31
+ true
32
+ end
33
+
34
+ def to_s
35
+ title.presence || 'learndash course'
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,73 @@
1
+ module Effective
2
+ class LearndashEnrollment < ActiveRecord::Base
3
+ belongs_to :owner, polymorphic: true
4
+ log_changes(to: :owner) if respond_to?(:log_changes)
5
+
6
+ belongs_to :learndash_course
7
+ belongs_to :learndash_user
8
+
9
+ PROGRESS_STATUSES = ['not-started', 'in-progress', 'completed']
10
+
11
+ effective_resource do
12
+ last_synced_at :string
13
+
14
+ # Wordpress
15
+ progress_status :string
16
+
17
+ last_step :integer
18
+ steps_completed :integer
19
+ steps_total :integer
20
+
21
+ date_started :datetime
22
+ date_completed :datetime
23
+
24
+ timestamps
25
+ end
26
+
27
+ scope :completed, -> { where(progress_status: 'completed') }
28
+ scope :in_progress, -> { where(progress_status: 'in-progress') }
29
+ scope :not_started, -> { where(progress_status: 'not-started') }
30
+
31
+ scope :deep, -> { all }
32
+ scope :sorted, -> { order(:id) }
33
+
34
+ before_validation do
35
+ self.owner ||= learndash_user&.owner
36
+ end
37
+
38
+ validates :learndash_user_id, uniqueness: { scope: :learndash_course_id, message: 'already enrolled in this course' }
39
+
40
+ # First time sync only for creating a new enrollment from the form
41
+ validate(if: -> { last_synced_at.blank? && learndash_user.present? && learndash_course.present? && errors.blank? }) do
42
+ assign_api_attributes
43
+ end
44
+
45
+ def to_s
46
+ persisted? ? "#{learndash_user} #{learndash_course}" : 'learndash enrollment'
47
+ end
48
+
49
+ def completed?
50
+ progress_status == 'completed'
51
+ end
52
+
53
+ def refresh!
54
+ assign_api_attributes
55
+ save!
56
+ end
57
+
58
+ def assign_api_attributes(data = nil)
59
+ data ||= EffectiveLearndash.api.find_enrollment(self) || EffectiveLearndash.api.create_enrollment(self)
60
+
61
+ assign_attributes(
62
+ last_synced_at: Time.zone.now,
63
+ progress_status: data[:progress_status],
64
+ last_step: data[:last_step],
65
+ steps_completed: data[:steps_completed],
66
+ steps_total: data[:steps_total],
67
+ date_started: Time.use_zone('UTC') { Time.zone.parse(data[:date_started]) },
68
+ date_completed: (Time.use_zone('UTC') { Time.zone.parse(data[:date_completed]) } if data[:date_completed].present?)
69
+ )
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,97 @@
1
+ module Effective
2
+ class LearndashUser < ActiveRecord::Base
3
+ belongs_to :owner, polymorphic: true
4
+ log_changes(to: :owner) if respond_to?(:log_changes)
5
+
6
+ has_many :learndash_enrollments
7
+ accepts_nested_attributes_for :learndash_enrollments
8
+
9
+ has_many :learndash_courses, through: :learndash_enrollments
10
+
11
+ effective_resource do
12
+ last_synced_at :string
13
+
14
+ # This user the wordpress credentials
15
+ user_id :integer
16
+ email :string
17
+ username :string
18
+ password :string
19
+
20
+ timestamps
21
+ end
22
+
23
+ scope :deep, -> { includes(:owner) }
24
+ scope :sorted, -> { order(:id) }
25
+
26
+ # I want to validate owner uniqueness, and only then create the learndash user on wordpress
27
+ validate(if: -> { new_record? && owner.present? }) do
28
+ self.errors.add(:owner, "already exists") if self.class.where(owner: owner).exists?
29
+ end
30
+
31
+ # First time sync only for creating a new user from the form
32
+ validate(if: -> { last_synced_at.blank? && owner.present? && errors.blank? }) do
33
+ assign_api_attributes
34
+ end
35
+
36
+ with_options(if: -> { last_synced_at.present? }) do
37
+ validates :user_id, presence: true
38
+ validates :email, presence: true
39
+ validates :username, presence: true
40
+ validates :password, presence: true
41
+ end
42
+
43
+ def to_s
44
+ owner&.to_s || username.presence || 'learndash user'
45
+ end
46
+
47
+ def refresh!
48
+ assign_api_course_enrollments
49
+ save!
50
+ end
51
+
52
+ # Find
53
+ def enrollment(course:)
54
+ learndash_enrollments.find { |enrollment| enrollment.learndash_course_id == course.id }
55
+ end
56
+
57
+ # Find or build
58
+ def build_enrollment(course:)
59
+ enrollment(course: course) || learndash_enrollments.build(learndash_course: course)
60
+ end
61
+
62
+ # Create
63
+ def create_enrollment(course:)
64
+ enrollment = build_enrollment(course: course)
65
+ enrollment.save!
66
+ enrollment
67
+ end
68
+
69
+ # Assign my model attributes from API. These should never change.
70
+ def assign_api_attributes(data = nil)
71
+ data ||= EffectiveLearndash.api.find_user(owner) || EffectiveLearndash.api.create_user(owner)
72
+
73
+ # Take special care not to overwrite password. We only get password once.
74
+ self.password ||= (data[:password].presence || 'unknown')
75
+
76
+ assign_attributes(email: data[:email], user_id: data[:id], username: data[:username], last_synced_at: Time.zone.now)
77
+ end
78
+
79
+ # This synchronizes all the course enrollments from the API down locally.
80
+ def assign_api_course_enrollments
81
+ raise('must be persisted') unless persisted?
82
+
83
+ courses = LearndashCourse.all()
84
+
85
+ EffectiveLearndash.api.user_enrollments(self).each do |data|
86
+ course = courses.find { |course| course.course_id == data[:course] }
87
+ raise("unable to find local persisted learndash course for id #{data[:course]}. Run Effective::LearndashCourse.sync!") unless course.present?
88
+
89
+ enrollment = build_enrollment(course: course)
90
+ enrollment.assign_api_attributes(data)
91
+ end
92
+
93
+ assign_attributes(last_synced_at: Time.zone.now)
94
+ end
95
+
96
+ end
97
+ end
@@ -0,0 +1,12 @@
1
+ = card('Learndash Course') do
2
+ %p= learndash_course.title
3
+ %p= link_to(learndash_course.link, learndash_course.link, target: '_blank')
4
+
5
+ %p= link_to "Learndash LMS Course Admin", EffectiveLearndash.learndash_url.chomp('/') + "/wp-admin/post.php?post=#{learndash_course.course_id}}&action=edit", target: '_blank'
6
+
7
+ %h3 Learndash User Enrollments
8
+
9
+ %p Click the New button from the below table to enroll a user into this course.
10
+
11
+ - datatable = Admin::EffectiveLearndashEnrollmentsDatatable.new(learndash_course: learndash_course)
12
+ = render_datatable(datatable, inline: true)
@@ -0,0 +1,16 @@
1
+ = effective_form_with(model: [:admin, learndash_enrollment], engine: true) do |f|
2
+ = f.hidden_field :learndash_course_id
3
+ = f.hidden_field :learndash_user_id
4
+
5
+ - if inline_datatable?
6
+ - if inline_datatable.attributes[:learndash_course_id].blank?
7
+ - collection = Effective::LearndashCourse.all.deep.sorted.where.not(id: f.object.learndash_user&.learndash_courses)
8
+ = f.select :learndash_course_id, collection
9
+
10
+ - if inline_datatable.attributes[:learndash_user_id].blank?
11
+ = f.select :learndash_user_id, Effective::LearndashUser.all.deep.sorted
12
+ - else
13
+ = f.select :learndash_course_id, Effective::LearndashCourse.all.deep.sorted
14
+ = f.select :learndash_user_id, Effective::LearndashUser.all.deep.sorted
15
+
16
+ = f.submit
@@ -0,0 +1,5 @@
1
+ - if owner.learndash_user.blank?
2
+ %p #{owner} does not yet have an account on Learndash.
3
+
4
+ - if owner.learndash_user.present?
5
+ = render('admin/learndash_users/learndash_user', learndash_user: owner.learndash_user)
@@ -0,0 +1,6 @@
1
+ = effective_form_with(model: [:admin, learndash_user], engine: true) do |f|
2
+
3
+ = f.select :owner, {'Owners' => current_user.class.sorted }, polymorphic: true,
4
+ hint: 'Create a Learndash user account for this website user'
5
+
6
+ = f.submit
@@ -0,0 +1,21 @@
1
+ = card('Learndash User') do
2
+ %p
3
+ = link_to(learndash_user.owner, "/admin/users/#{learndash_user.owner.to_param}/edit")
4
+ has an existing account on Learndash.
5
+
6
+ %ul
7
+ %li Email: #{learndash_user.email}
8
+ %li Username: #{learndash_user.username}
9
+ %li Password: #{learndash_user.password}
10
+
11
+ %p= link_to "Learndash LMS User Admin", EffectiveLearndash.learndash_url.chomp('/') + "/wp-admin/user-edit.php?user_id=#{learndash_user.user_id}", target: '_blank'
12
+
13
+ %p
14
+ %small Last refreshed #{time_ago_in_words(learndash_user.last_synced_at)} ago.
15
+
16
+ %h3 Learndash Course Enrollments
17
+
18
+ %p Click the New button from the below table to manually enroll this user into a new course.
19
+
20
+ - datatable = Admin::EffectiveLearndashEnrollmentsDatatable.new(learndash_user: learndash_user)
21
+ = render_datatable(datatable, inline: true)
@@ -0,0 +1,18 @@
1
+ EffectiveLearndash.setup do |config|
2
+ # Layout Settings
3
+ # Configure the Layout per controller, or all at once
4
+ # config.layout = { application: 'application', admin: 'admin' }
5
+
6
+ # Application Password
7
+ # This is an application password from your Wordpress install hosting Learndash plugin.
8
+ # https://make.wordpress.org/core/2020/11/05/application-passwords-integration-guide/
9
+ #
10
+ config.learndash_url = ENV['LEARNDASH_URL']
11
+ config.learndash_username = ENV['LEARNDASH_USERNAME']
12
+ config.learndash_password = ENV['LEARNDASH_PASSWORD']
13
+
14
+ # Customize the method used to assign Wordpress username and passwords
15
+ # Usernames can only contain lowercase letters (a-z) and numbers.
16
+ # config.wp_username = Proc.new { |user| "user#{user.id}" }
17
+ # config.wp_password = Proc.new { |user| SecureRandom.base64(12) }
18
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ Rails.application.routes.draw do
4
+ mount EffectiveLearndash::Engine => '/', as: 'effective_learndash'
5
+ end
6
+
7
+ EffectiveLearndash::Engine.routes.draw do
8
+ namespace :admin do
9
+ get '/learndash', to: 'learndash#index', as: :learndash
10
+
11
+ resources :learndash_users, only: [:index, :show, :new, :create, :update] do
12
+ post :refresh, on: :member
13
+ end
14
+
15
+ resources :learndash_enrollments, only: [:index, :new, :create, :update] do
16
+ post :refresh, on: :member
17
+ end
18
+
19
+ resources :learndash_courses, only: [:index, :show, :update] do
20
+ get :refresh, on: :collection
21
+ end
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,48 @@
1
+ class CreateEffectiveLearndash < ActiveRecord::Migration[6.1]
2
+ create_table :learndash_users do |t|
3
+ t.integer :owner_id
4
+ t.string :owner_type
5
+
6
+ t.datetime :last_synced_at
7
+
8
+ # Wordpress
9
+ t.integer :user_id
10
+ t.string :email
11
+ t.string :username
12
+ t.string :password
13
+
14
+ t.timestamps
15
+ end
16
+
17
+ create_table :learndash_courses do |t|
18
+ # Wordpress
19
+ t.integer :course_id
20
+ t.string :status
21
+ t.string :title
22
+ t.string :link
23
+
24
+ t.timestamps
25
+ end
26
+
27
+ create_table :learndash_enrollments do |t|
28
+ t.integer :owner_id
29
+ t.string :owner_type
30
+
31
+ t.integer :learndash_course_id
32
+ t.integer :learndash_user_id
33
+
34
+ t.datetime :last_synced_at
35
+
36
+ # Wordpress
37
+ t.string :progress_status
38
+
39
+ t.integer :last_step
40
+ t.integer :steps_completed
41
+ t.integer :steps_total
42
+
43
+ t.datetime :date_started
44
+ t.datetime :date_completed
45
+
46
+ t.timestamps
47
+ end
48
+ end
data/db/seeds.rb ADDED
@@ -0,0 +1 @@
1
+ puts "Running effective_learndash seeds"
@@ -0,0 +1,22 @@
1
+ module EffectiveLearndash
2
+ class Engine < ::Rails::Engine
3
+ engine_name 'effective_learndash'
4
+
5
+ # Set up our default configuration options.
6
+ initializer 'effective_learndash.defaults', before: :load_config_initializers do |app|
7
+ eval File.read("#{config.root}/config/effective_learndash.rb")
8
+ end
9
+
10
+ initializer 'effective_learndash.assets' do |app|
11
+ app.config.assets.precompile += ['effective_learndash_manifest.js', 'effective_learndash/*']
12
+ end
13
+
14
+ # Include acts_as_addressable concern and allow any ActiveRecord object to call it
15
+ initializer 'effective_learndash.active_record' do |app|
16
+ ActiveSupport.on_load :active_record do
17
+ ActiveRecord::Base.extend(EffectiveLearndashOwner::Base)
18
+ end
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,3 @@
1
+ module EffectiveLearndash
2
+ VERSION = '0.1.0'.freeze
3
+ end
@@ -0,0 +1,52 @@
1
+ require 'effective_resources'
2
+ require 'effective_datatables'
3
+ require 'effective_learndash/engine'
4
+ require 'effective_learndash/version'
5
+
6
+ module EffectiveLearndash
7
+ WP_USERNAME_PROC = Proc.new { |user| "user#{user.id}" }
8
+ WP_PASSWORD_PROC = Proc.new { |user| SecureRandom.base64(12) }
9
+
10
+ def self.config_keys
11
+ [
12
+ :learndash_url, :learndash_username, :learndash_password,
13
+ :wp_username, :wp_password,
14
+ :layout
15
+ ]
16
+ end
17
+
18
+ include EffectiveGem
19
+
20
+ def self.api
21
+ raise('please set learndash_url in config/initializers/effective_learndash.rb') unless learndash_url.present?
22
+ raise('please set learndash_username in config/initializers/effective_learndash.rb') unless learndash_username.present?
23
+ raise('please set learndash_password in config/initializers/effective_learndash.rb') unless learndash_password.present?
24
+
25
+ Effective::LearndashApi.new(
26
+ url: learndash_url,
27
+ username: learndash_username,
28
+ password: learndash_password
29
+ )
30
+ end
31
+
32
+ def self.wp_username
33
+ config[:wp_username] || WP_USERNAME_PROC
34
+ end
35
+
36
+ def self.wp_password
37
+ config[:wp_password] || WP_PASSWORD_PROC
38
+ end
39
+
40
+ # The user.learndash_username is the source of truth
41
+ # This is the backup to generate a new username
42
+ def self.wp_username_for(owner)
43
+ raise('expecting a learndash owner') unless owner.class.respond_to?(:effective_learndash_owner?)
44
+ owner.instance_exec(owner, &wp_username)
45
+ end
46
+
47
+ def self.wp_password_for(owner)
48
+ raise('expecting a learndash owner') unless owner.class.respond_to?(:effective_learndash_owner?)
49
+ owner.instance_exec(owner, &wp_password)
50
+ end
51
+
52
+ end
@@ -0,0 +1,29 @@
1
+ module EffectiveLearndash
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ include Rails::Generators::Migration
5
+
6
+ desc 'Creates an EffectiveLearndash initializer in your application.'
7
+
8
+ source_root File.expand_path('../../templates', __FILE__)
9
+
10
+ def self.next_migration_number(dirname)
11
+ if not ActiveRecord::Base.timestamped_migrations
12
+ Time.new.utc.strftime("%Y%m%d%H%M%S")
13
+ else
14
+ '%.3d' % (current_migration_number(dirname) + 1)
15
+ end
16
+ end
17
+
18
+ def copy_initializer
19
+ template ('../' * 3) + 'config/effective_learndash.rb', 'config/initializers/effective_learndash.rb'
20
+ end
21
+
22
+ def create_migration_file
23
+ @learndash_courses_table_name = ':' + EffectiveLearndash.learndash_courses_table_name.to_s
24
+ migration_template ('../' * 3) + 'db/migrate/01_create_effective_learndash.rb.erb', 'db/migrate/create_effective_learndash.rb'
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,4 @@
1
+ # Visit http://localhost:3000/rails/mailers
2
+
3
+ class EffectiveLearndashMailerPreview < ActionMailer::Preview
4
+ end
@@ -0,0 +1,8 @@
1
+ namespace :effective_learndash do
2
+
3
+ # bundle exec rake effective_learndash:seed
4
+ task seed: :environment do
5
+ load "#{__dir__}/../../db/seeds.rb"
6
+ end
7
+
8
+ end