moodleQuizDownloader 0.0.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-gemset +1 -0
  3. data/.ruby-version +1 -0
  4. data/README.md +33 -8
  5. data/lib/moodleQuizDownloader.rb +15 -2
  6. data/lib/moodleQuizDownloader/attempt_selector.rb +37 -0
  7. data/lib/moodleQuizDownloader/file_name_creator.rb +3 -3
  8. data/lib/moodleQuizDownloader/moodle_parser.rb +42 -5
  9. data/lib/moodleQuizDownloader/option_handler.rb +11 -4
  10. data/lib/moodleQuizDownloader/quiz_downloader.rb +87 -39
  11. data/lib/moodleQuizDownloader/version.rb +1 -1
  12. data/moodleQuizDownloader.gemspec +10 -6
  13. data/spec/anonymize.rb +26 -0
  14. data/spec/api/asciify_spec.rb +2 -2
  15. data/spec/api/faker_spec.rb +12 -0
  16. data/spec/api/pdfkit/issue_cmap_table_spec.rb +43 -0
  17. data/spec/api/pdfkit/testfiles/notworking.html +1364 -0
  18. data/spec/api/pdfkit/testfiles/notworkingimagesremoved.html +1364 -0
  19. data/spec/api/pdfkit/testfiles/working.html +1910 -0
  20. data/spec/api/pdfkit_spec.rb +2 -2
  21. data/spec/fileNameCreator_spec.rb +12 -3
  22. data/spec/moodleparsing/attempt_selector_spec.rb +52 -0
  23. data/spec/moodleparsing/config_spec.rb +10 -0
  24. data/spec/moodleparsing/irb_commands.md +13 -0
  25. data/spec/moodleparsing/moodleparsing_spec.rb +17 -28
  26. data/spec/moodleparsing/testfiles/attempts-17-shown.html +635 -0
  27. data/spec/moodleparsing/testfiles/attempts-17-shown.html.temp +635 -0
  28. data/spec/moodleparsing/testfiles/attempts-fewer-shown.html +467 -0
  29. data/spec/moodleparsing/{exam-overview-moodle-ss2013.html → testfiles/exam-overview-moodle-ss2013.html} +0 -0
  30. data/spec/moodleparsing/{exam-overview.html → testfiles/exam-overview.html} +0 -0
  31. data/spec/moodleparsing/testfiles/login_failed.html +159 -0
  32. data/spec/moodleparsing/testfiles/login_successful.html +307 -0
  33. data/spec/moodleparsing/testfiles/occ +1 -0
  34. data/spec/moodleparsing/{review-page-nutzerbild.html → testfiles/review-page-nutzerbild.html} +0 -0
  35. data/spec/moodleparsing/{review-page.html → testfiles/review-page.html} +0 -0
  36. data/spec/option_handler_spec.rb +9 -1
  37. data/spec/smoketest_connection_spec.rb +21 -0
  38. data/spec/spec_helper.rb +9 -0
  39. metadata +133 -36
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a8c17d2cdd565630203b2eaacaaf27fe47fe3110
4
- data.tar.gz: de31f285dd09c3cfd339c01a3761067169f08bbf
3
+ metadata.gz: a7e868ece8269a98c5a62ff40f9438412ed7f068
4
+ data.tar.gz: 5f9c904e12793af94eae0398fd24cbd3f8501e34
5
5
  SHA512:
6
- metadata.gz: 5d2fa647058a7534f13518b88ab538d1a195a57090f8293b4803b4044b134e256d20a396c2ac8a85d1d130fdea9c3ca88eb64dd3ca2424966062dfd72855470f
7
- data.tar.gz: b862718b20c6ae363765f1dae1d6ac1b97104375f87d20af8b471f46605a791f96f77fc6cae2c2f0565c5fdcfd76992820f84353fdde8cf49abe296bba71fb99
6
+ metadata.gz: 2c52ebce025f19243e3d224f2a3f1312f416585f6cf828b838626107f234f344e1e1a36ab584f90e58654f2ef4591a8aaf563acfb40cae731fdc5e6a0bdbaa10
7
+ data.tar.gz: 36d8382a844414c0ad59a5608d39f06c038d93882264173872b66bb85cd7eb8bc5bba6f1f728ab0ace98b9fe2f272306ec356eed9073ccffd1d82f20fdd6fce0
@@ -0,0 +1 @@
1
+ myrubystack
@@ -0,0 +1 @@
1
+ ruby-2.2.0
data/README.md CHANGED
@@ -1,20 +1,25 @@
1
1
  # MoodleQuizDownloader
2
2
 
3
- TODO: Write a gem description
3
+ Automatically downloads all attempts of a Moodle Quiz and stores them as PDF or HTML.
4
4
 
5
5
  ## Installation
6
6
 
7
- Add this line to your application's Gemfile:
7
+ $ gem install moodleQuizDownloader
8
8
 
9
- gem 'moodleQuizDownloader'
9
+ ## What it does
10
10
 
11
- And then execute:
11
+ This script downloads all attempts of a moodle quiz and saves them in pdfs named with the student names.
12
+ For this it logs into moodle via the http/html web site, goes to the quiz and scrapes all information like student names etc. from the html it finds.
12
13
 
13
- $ bundle
14
+ To get started, you first need the moodle id of the quiz you want to download. E.g. if the url shown in the browser was
14
15
 
15
- Or install it yourself as:
16
+ https://moodle.htw-berlin.de/mod/quiz/report.php?id=4711&mode=overview
16
17
 
17
- $ gem install moodleQuizDownloader
18
+ If you view the quiz's attempts in the browser,
19
+
20
+ 4711
21
+
22
+ is the id.
18
23
 
19
24
  ## Usage
20
25
 
@@ -25,7 +30,7 @@ for example, run
25
30
  to see a list of all attempts in exam 4711 on the specified server,
26
31
  and
27
32
 
28
- $ moodleQuizDownloader.rb -e 4711 -u drblinken -s http://moodle2.htw-berlin.de/ -p geheim --verbose download
33
+ $ moodleQuizDownloader.rb -e 4711 -u drblinken -s http://moodle.someschool.de/ -p geheim --verbose download
29
34
 
30
35
  to download them.
31
36
 
@@ -33,6 +38,26 @@ to see all options:
33
38
 
34
39
  $ moodleQuizDownloader.rb help
35
40
 
41
+ ## try it out in irb
42
+
43
+ e.g.:
44
+
45
+ irb
46
+ load 'lib/moodleQuizDownloader.rb'
47
+
48
+ arguments = "-o /Users/kleinen/Documents/ss2013/exam- ckup/info3/pz1/exam_a/raw -e 23843".split(" ")
49
+ options = OptionHandler.new(arguments).parse
50
+ q = QuizDownloader.new
51
+ agent = Mechanize.new
52
+ q.smoketest(options.moodle_server)
53
+ q.attemptlist(options,agent)
54
+ page = q.smoketest(options.moodle_server)
55
+ username_input = page.search('#username')
56
+
57
+ ## Todos / New Features
58
+
59
+ * Add an option to be prompted for the password ( or just do it automatically if it is not provided)
60
+
36
61
  ## Contributing
37
62
 
38
63
  1. Fork it
@@ -1,15 +1,28 @@
1
1
  require_relative "moodleQuizDownloader/version.rb"
2
2
  require_relative "moodleQuizDownloader/option_handler.rb"
3
3
  require_relative "moodleQuizDownloader/quiz_downloader.rb"
4
+ require_relative "moodleQuizDownloader/attempt_selector.rb"
4
5
 
5
6
  module MoodleQuizDownloader
7
+
6
8
  def run_script(arguments)
7
- options = OptionHandler.new(arguments).parse
9
+ option_handler = OptionHandler.new(arguments)
10
+ options = option_handler.options
8
11
  if options.usage
9
12
  puts options.usage
10
13
  exit
11
14
  end
12
- QuizDownloader.new.run(options)
15
+ options.moodle_password ||= prompt_for_password
16
+ begin
17
+ QuizDownloader.new(options).run
18
+ rescue PageException => e
19
+ puts "#{e.message}"
20
+ end
21
+ end
22
+
23
+ def prompt_for_password(prompt="Enter Password")
24
+ require 'highline/import'
25
+ ask(prompt) {|q| q.echo = false}
13
26
  end
14
27
  end
15
28
 
@@ -0,0 +1,37 @@
1
+ #<select id="id_attempts" name="attempts">
2
+ #
3
+ # <option selected="selected" value="enrolled_with"><#/option>
4
+ # <option value="enrolled_without"></option>
5
+ # <option value="enrolled_any"></option>
6
+ # <option value="all_with"></option>
7
+ #
8
+ #</select>
9
+ #
10
+ #
11
+ # <input id="id_submitbutton" type="submit" #value="Bericht anzeigen" name="submitbutton"></input#>
12
+ #
13
+ #
14
+
15
+
16
+ # moodle selecty "students who take the quiz and are enrolled"
17
+ # as default -
18
+ # to archive quizzes later on, this Module selects
19
+ # all attempts.
20
+
21
+
22
+ # form
23
+ #mform1
24
+ module MoodleAttemptSelector
25
+
26
+ def select_all_attempts_done(page)
27
+ form = page.form_with(id: 'mform1')
28
+ selectlist = form.field_with(id: 'id_attempts')
29
+ selectlist.value="all_with"
30
+ page = form.submit
31
+ end
32
+
33
+
34
+
35
+ end
36
+
37
+
@@ -5,8 +5,8 @@ class FileNameCreator
5
5
  @@regexp = /(\w*)( (\w*))? (\w*)/
6
6
  @@map = Asciify::Mapping.new(:default)
7
7
 
8
- def self.fileNameFor(outputdir,name)
9
- File.join(outputdir,name.asciify(@@map).gsub(@@regexp,"\\1\\3\\4.pdf"))
8
+ def self.file_name_for(outputdir,name,extension = 'pdf')
9
+ name = name.gsub("-","")
10
+ File.join(outputdir,name.asciify(@@map).gsub(@@regexp,"\\1\\3\\4.#{extension}"))
10
11
  end
11
-
12
12
  end
@@ -1,17 +1,29 @@
1
+
2
+ # This file contains Moodle settings
3
+ # like urls and xml paths
4
+ # decided against putting them in a config file,
5
+ # as they can be adapted here.
6
+ require_relative 'attempt_selector.rb'
1
7
  module MoodleParser
8
+ include MoodleAttemptSelector
2
9
  @@user_view_regexp = Regexp.new('http://moodle2.htw-berlin.de/moodle/user/view.php')
3
10
 
4
- def moodle_login_page(server)
5
- "#{server}/moodle/login/index.php"
11
+ def moodle_login_page(server)
12
+ "#{server}"
13
+ # oder vielleicht besser:
14
+ #https://moodle.htw-berlin.de/login/index.php
15
+ # "#{server}/moodle/login/index.php"
6
16
  end
7
17
  def moodle_item(server)
8
18
  "#{server}/moodle/mod/quiz/view.php?id="
9
19
  end
10
20
  def moodle_quiz_report(server)
11
- "#{server}/moodle/mod/quiz/report.php?mode=overview&id="
21
+ # https://moodle.htw-berlin.de/mod/quiz/report.php?id=46982&mode=overview
22
+ "#{server}/mod/quiz/report.php?mode=overview&id="
12
23
  end
13
24
 
14
25
  def login(agent,moodle_login_page,moodle_username,moodle_password)
26
+ chatter "connecting to moodle"
15
27
  puts "++++#{moodle_login_page}"
16
28
  page = agent.get(moodle_login_page)
17
29
  form = page.forms[1]
@@ -22,11 +34,22 @@ module MoodleParser
22
34
 
23
35
  def selectReviewLinks(page)
24
36
  page.links.select do |ll|
25
- cls = ll.attributes.attributes['class']
26
- cls && cls.value == 'reviewlink'
37
+ css_class = ll.attributes.attributes['class']
38
+ css_class && css_class.value == 'reviewlink'
27
39
  end
28
40
  end
29
41
 
42
+ def extract_complete_attempt_list(page)
43
+ page = select_all_attempts_done(page)
44
+ attempt_list = extract_attempt_list(page)
45
+ if (announced = extract_attempt_count(page)) != attempt_list.size
46
+ puts "#### WARNING: There was a different number of "
47
+ puts "#### attempts announced on the page - #{announced}"
48
+ puts "#### but only #{attempt_list.size} could be downloaded"
49
+ end
50
+ attempt_list
51
+ end
52
+
30
53
  def extract_attempt_list(page)
31
54
  i = 1
32
55
  result_list = []
@@ -39,6 +62,20 @@ module MoodleParser
39
62
  result_list
40
63
  end
41
64
 
65
+ def extract_attempt_count(page)
66
+ ##class: quizattemptcounts
67
+ quizattemptcounts_div = page.at('.quizattemptcounts')
68
+ m = quizattemptcounts_div.content.match(/Attempts: (\d+)/)
69
+ m[1].to_i
70
+ end
71
+
72
+ def all_attempts_shown(page,number_of_attempts_shown = nil)
73
+ unless number_of_attempts_shown
74
+ number_of_attempts_shown = extract_attempt_list(page).size
75
+ end
76
+ number_of_attempts_shown == extract_attempt_count(page)
77
+ end
78
+
42
79
  def extractUserName(page)
43
80
 
44
81
  l = page.links.select {|x| @@user_view_regexp.match(x.href)}
@@ -3,7 +3,7 @@ require 'ostruct'
3
3
 
4
4
  class OptionHandler
5
5
  def valid_commands
6
- [:list, :download, :connect]
6
+ [:list, :download, :connect, :options]
7
7
  end
8
8
  attr_reader :options, :optionparser
9
9
  def initialize(arguments = [])
@@ -20,7 +20,7 @@ class OptionHandler
20
20
  def valid?(options)
21
21
  options.valid &&
22
22
  options.moodle_username &&
23
- options.moodle_password &&
23
+ #options.moodle_password &&
24
24
  options.moodle_server &&
25
25
  options.exam_id != 0
26
26
  end
@@ -38,6 +38,7 @@ class OptionHandler
38
38
  options.exam_id = 0
39
39
  options.command = :list
40
40
  options.usage = nil
41
+ options.html = false
41
42
 
42
43
 
43
44
  opt_parser = OptionParser.new do |opts|
@@ -57,18 +58,23 @@ DELIM
57
58
  "Output Dir (defaults to . )") do | dir |
58
59
  options.outputdir = dir
59
60
  end
61
+ opts.on("-h", "--html",
62
+ "Save Exams in HTML instead as PDFs") do | html |
63
+ options.html = html
64
+ end
65
+
60
66
  opts.on("-u", "--user USERNAME",
61
67
  "Your Moodle User Name",
62
68
  "(can also be set via MOODLE_USERNAME environment variable)") do |username|
63
69
  options.moodle_username = username
64
70
  end
65
- opts.on("-p", "--password PASSWORD]",
71
+ opts.on("-p", "--password PASSWORD",
66
72
  "Your Moodle Password",
67
73
  "(can also be set via MOODLE_PASSWORD environment variable)") do |password|
68
74
  options.moodle_password = password
69
75
  end
70
76
 
71
- opts.on("-s", "--server SERVER]",
77
+ opts.on("-s", "--server SERVER",
72
78
  "Your Moodle Server",
73
79
  "(can also be set via MOODLE_SERVER environment variable)") do |server|
74
80
  options.moodle_server = server
@@ -101,4 +107,5 @@ DELIM
101
107
  validate(options,opt_parser)
102
108
  options
103
109
  end # parse()
110
+
104
111
  end
@@ -3,76 +3,124 @@ require 'pdfkit'
3
3
  require 'asciify'
4
4
  require "bundler/setup"
5
5
  require 'fileutils'
6
- require_relative 'file_name_creator'
7
6
 
7
+ require_relative 'file_name_creator'
8
8
  require_relative 'moodle_parser'
9
9
 
10
-
10
+ class PageException < Exception
11
+ end
11
12
 
12
13
  class QuizDownloader
14
+
13
15
  include MoodleParser
14
16
 
15
- def smoketest(server)
17
+ attr_reader :options
18
+ def initialize(options)
19
+ @options = options
20
+ end
21
+
22
+ def run
23
+ agent = Mechanize.new
24
+ case options.command
25
+ when :connect
26
+ smoketest(options.moodle_server, options.moodle_username, options.moodle_password)
27
+ when :list
28
+ attempt_list = attemptlist(agent)
29
+ puts "found #{attempt_list.length} attempts:"
30
+ puts attempt_list.map{|a,b| a}
31
+ when :download
32
+ chatter "options #{options.inspect}"
33
+ attempt_list = attemptlist(agent)
34
+ download(attempt_list,agent)
35
+ when :options
36
+ puts "#{options.inspect}"
37
+
38
+ else
39
+ puts "command #{options.command} not recognized"
40
+ end
41
+ end
42
+
43
+ def chatter(message)
44
+ puts "==== #{message}" if options.verbose
45
+ end
46
+
47
+ def smoketest(server, username = "xxx", password ="xxx")
16
48
  begin
17
49
  agent = Mechanize.new
18
- page = login(agent,moodle_login_page(server),"xxx","xxx")
19
- if page.nil?
20
- puts "could not connect to server #{server}"
50
+ page = login(agent,moodle_login_page(server),username,password)
51
+ unless server_reachable?(page)
52
+ puts "Could not connect to server #{server}"
21
53
  else
22
- puts "connection to server seems to be working"
54
+ puts "Connection to server seems to be working..."
55
+ if login_successful?(page)
56
+ puts "and login has been successful."
57
+ else
58
+ puts "but could not login with the given credentials."
59
+ end
23
60
  end
24
61
  rescue Exception => e
25
62
  puts "could not connect to server #{server}"
26
63
  #puts e.message
27
64
  end
65
+ page
28
66
  end
29
- def attemptlist(options,agent)
30
- puts "connecting to moodle" if options.verbose
31
- page = login(agent,moodle_login_page(options.moodle_server),options.moodle_username,options.moodle_password)
67
+
68
+ def login_successful?(page)
69
+ page.search('#username').empty?
70
+ end
71
+ def server_reachable?(page)
72
+ !page.nil?
73
+ end
74
+
75
+ def attemptlist(agent)
76
+
77
+ login(agent,moodle_login_page(options.moodle_server),options.moodle_username,options.moodle_password)
78
+
32
79
  moodle_overview_url = moodle_quiz_report(options.moodle_server)+options.exam_id.to_s
33
- puts "==== downloading overview" if options.verbose
34
- puts moodle_overview_url if options.verbose
35
- page = agent.get(moodle_overview_url)
36
- #attempts = selectReviewLinks(page)
37
- attempt_list = extract_attempt_list(page)
80
+ chatter "downloading overview"
81
+ chatter "url:#{moodle_overview_url}"
82
+ begin
83
+ page = agent.get(moodle_overview_url)
84
+ attempt_list = extract_attempt_list(page)
85
+ rescue Mechanize::ResponseCodeError => e
86
+ raise e unless (e.response_code == "404")
87
+ raise PageException, "Exam not found - maybe the wrong id: #{options.exam_id} ?\n(while trying to access: #{moodle_overview_url})"
88
+ end
89
+
38
90
  end
39
91
 
40
- def download(attempt_list,options,agent)
92
+ def download(attempt_list,agent)
41
93
  FileUtils.mkdir_p(options.outputdir)
94
+ count = 0
42
95
  attempt_list.each do | attempt|
43
96
  student_name, attempt_url = attempt
44
97
 
45
98
  attempt_url = attempt_url+"&showall=1"
46
99
  page = agent.get(attempt_url)
47
- puts "==== downloading attempt" if options.verbose
48
- puts attempt_url if options.verbose
100
+ chatter "downloading attempt"
101
+ chatter "url #{attempt_url}"
102
+ chatter attempt_url
49
103
  #student = extractUserName(page)
50
104
  student = student_name
51
- outputfile = FileNameCreator.fileNameFor(options.outputdir,student)
52
- puts "Loading: #{student}"
53
- kit = PDFKit.new(page.body)
54
- kit.to_file(outputfile)
55
- end
56
- end
57
105
 
106
+ puts "Loading: #{student}"
58
107
 
59
- def run(options)
60
- agent = Mechanize.new
61
- case options.command
62
- when :connect
63
- smoketest(options.moodle_server)
64
- when :list
65
- attempt_list = attemptlist(options,agent)
66
- puts "found #{attempt_list.length} attempts:"
67
- puts attempt_list.map{|a,b| a}
68
- when :download
69
- attempt_list = attemptlist(options,agent)
70
- download(attempt_list,options,agent)
108
+ chatter "downloading overview"
109
+ chatter "outputfile:#{options.outputdir}"
110
+ kit = PDFKit.new(page.body)
111
+ if options.html
112
+ chatter "save html"
113
+ outputfile_html = FileNameCreator.file_name_for(options.outputdir,student,'html')
114
+ File.open(outputfile_html, 'w') { |file| file.write(page.body) }
71
115
  else
72
- puts "command #{options.command} not recognized"
116
+ chatter "kit to file"
117
+ outputfile = FileNameCreator.file_name_for(options.outputdir,student)
118
+ kit.to_file(outputfile)
119
+ chatter "x"
120
+ end
121
+ count += 1
73
122
  end
123
+ puts "downloaded #{count} attempts"
74
124
  end
75
-
76
-
77
125
  end
78
126