forked from openwebwork/pg
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPGloadfiles.pm
280 lines (229 loc) · 9.61 KB
/
PGloadfiles.pm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
################################################################################
# WeBWorK Online Homework Delivery System
# Copyright © 2000-2024 The WeBWorK Project, https://github.com/openwebwork
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of either: (a) the GNU General Public License as published by the
# Free Software Foundation; either version 2, or (at your option) any later
# version, or (b) the "Artistic License" which comes with this package.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the
# Artistic License for more details.
################################################################################
=head2 loadMacros
loadMacros(@macroFiles)
loadMacros takes a list of file names and evaluates the contents of each file.
This is used to load macros which define and augment the PG language. The macro
files are searched for in the directories specified by the array referenced by
$macrosPath, which by default is the current course's macros directory followed
by WeBWorK's pg/macros directory. The latter is where the default behaviour of
the PG language is defined. The default path is set in the global.conf file.
Macro files named PG.pl or IO.pl will be loaded with no opcode restrictions,
hence any code in those files will be able to execute privileged operations.
This is true no matter which macro directory the file is in. For example,
if $macrosPath contains the path to a problem library macros directory which
contains a PG.pl file, this file will be loaded and allowed to engage in
privileged behavior.
=head3 Overloading macro files
An individual course can modify the PG language, for that course only, by
duplicating one of the macro files in the system-wide macros directory and
placing this file in the macros directory for the course. The new file in the
course's macros directory will now be used instead of the file in the
system-wide macros directory.
The new file in the course macros directory can by modified by adding macros or
modifying existing macros.
=head3 Modifying existing macros
I<Modifying macros is for users with some experience.>
Modifying existing macros might break other standard macros or problems which
depend on the unmodified behavior of these macors so do this with great caution.
In addition problems which use new macros defined in these files or which depend
on the modified behavior of existing macros will not work in other courses
unless the macros are also transferred to the new course. It helps to document
the problems by indicating any special macros which the problems require.
There is no facility for modifying or overloading a single macro. The entire
file containing the macro must be overloaded.
Modifications to files in the course macros directory affect only that course,
they will not interfere with the normal behavior of WeBWorK in other courses.
=cut
our $debugON = 0;
package PGloadfiles;
use strict;
#use Encode(qw(encode decode));
use Exporter;
use PGcore;
use WeBWorK::PG::Translator;
use WeBWorK::PG::IO;
our @ISA = qw ( PGcore ); # look up features in PGcore -- in this case we want the environment.
# new
# Create one loadfiles object per question (and per PGcore object)
# Process macro files
# Keep list of macro files processed.
sub new {
my $class = shift;
my $envir = shift; #pointer to environment hash
warn "PGloadmacros must be called with an environment" unless ref($envir) eq 'HASH';
my $self = {
envir => $envir,
macroFileList => {}, # records macros used in compilation
macrosPath => '',
pwd => '', # current directory -- defined in initialize
};
bless $self, $class;
$self->initialize;
#$self->check_parameters;
return $self;
}
sub initialize {
my $self = shift;
my $templateDirectory = $self->{envir}->{templateDirectory};
my $pwd = $self->{envir}->{probFileName};
$pwd =~ s!/[^/]*$!!;
$pwd = $templateDirectory . $pwd unless substr($pwd, 0, 1) eq '/';
# FIXME: This shouldn't be here. See the note in PGalias.pm in the initialize subroutine.
$pwd =~ s!/tmpEdit/!/!;
$self->{pwd} = $pwd;
$self->{macrosPath} = $self->{envir}{macrosPath};
}
sub PG_restricted_eval {
my $self = shift;
WeBWorK::PG::Translator::PG_restricted_eval(@_);
}
sub PG_macro_file_eval {
my $self = shift;
WeBWorK::PG::Translator::PG_macro_file_eval(@_);
}
# ^function loadMacros
# ^uses $debugON
# ^uses $externalTTHPath
# ^uses findMacroFile
sub loadMacros {
my $self = shift;
my @files = @_;
my $fileName;
my $macrosPath = $self->{envir}->{macrosPath};
###############################################################################
# At this point the directories have been defined from %envir and we can define
# the directories for this file
###############################################################################
while (@files) {
$fileName = shift @files;
next if ($fileName =~ /^PG\.pl$/); # the PG.pl macro package is already loaded.
unless ($fileName =~ /\.(pl|pg)$/) { # dont try to parse files without macro extensions
warn "Can't load file |$fileName|. Can't load a macro file unless it has a .pl or .pg extension";
next;
}
my $macro_file_name = $fileName;
$macro_file_name =~ s/\.pl//; # trim off the extension
$macro_file_name =~ s/\.pg//; # sometimes the extension is .pg (e.g. CAPA files)
my $init_subroutine_name = "_${macro_file_name}_init";
$init_subroutine_name =~ s![^a-zA-Z0-9_]!_!g; # remove dangerous chars
my $init_subroutine = eval { \&{ 'main::' . $init_subroutine_name } };
###############################################################################
# macros are searched for in the directories listed in the $macrosPath array reference.
my $macro_file_loaded = defined($init_subroutine) && defined(&$init_subroutine);
warn "PGloadfiles: macro init $init_subroutine_name defined |$init_subroutine| |$macro_file_loaded|"
if $debugON;
unless ($macro_file_loaded) {
warn "loadMacros: loading macro file $fileName" if $debugON;
my $filePath = $self->findMacroFile($fileName);
#### (check for renamed files here?) ####
warn "loadMacros: look for $fileName at |$filePath|" if $debugON;
if ($filePath) {
$self->compile_file($filePath);
warn "loadMacros is compiling $filePath" if $debugON;
} else {
my $pgDirectory = $self->{envir}{pgMacrosDir};
my $templateDirectory = $self->{envir}{templateDirectory};
my @shortenedPaths = @{$macrosPath};
@shortenedPaths = map { $_ =~ s|^$templateDirectory|[TMPL]/|; $_ } @shortenedPaths;
@shortenedPaths = map { $_ =~ s|^$pgDirectory|[PG]/macros/|; $_ } @shortenedPaths;
warn "Can't locate macro file |$fileName| via path: |" . join("|,<br/> |", @shortenedPaths) . "|\n";
}
$init_subroutine = eval { \&{ 'main::' . $init_subroutine_name } };
$macro_file_loaded = defined($init_subroutine) && defined(&$init_subroutine);
warn "PGloadfiles: macro init $init_subroutine_name defined |$init_subroutine| |$macro_file_loaded|"
if $debugON;
if ($macro_file_loaded) {
warn "PGloadfiles: $macro_file_name loaded, initializing $macro_file_name\n" if $debugON;
&$init_subroutine();
}
}
}
}
# ^function findMacroFile
# ^uses $macrosPath
# ^uses $pwd
sub findMacroFile {
my $self = shift;
my $macroFileName = shift;
my $macroFilePath;
my $pwd = $self->{pwd};
my @macrosPath = @{ $self->{envir}->{macrosPath} };
warn "in findMacroFile" if $debugON;
# foreach my $dir (@{$self->{macrosPath} } ) { # why did this ever work?
foreach my $dir (@macrosPath) {
$macroFilePath = "$dir/$macroFileName";
$macroFilePath =~ s!^\.\.?/!$pwd/!;
return $macroFilePath if (-r $macroFilePath);
}
return 0; # no file found
}
# errors in compiling macros is not always being reported.
# ^function compile_file
# ^uses @__eval__
# ^uses PG_restricted_eval
# ^uses $__files__
sub compile_file {
my $self = shift;
my $filePath = shift;
warn "loading $filePath" if $debugON;
local $/ = undef; # allows us to treat the file as a single line
open(my $MACROFILE, "<:raw", $filePath) || die "Cannot open file: $filePath";
my $string = <$MACROFILE>;
close $MACROFILE;
utf8::decode($string); # can't yet use :encoding(UTF-8)
my ($result, $error, $fullerror) = $self->PG_macro_file_eval($string, $filePath);
if ($error) {
# The $fullerror report has formatting and is never empty when there is an error.
# The die message is handled by PG_errorMessage() in the PG translator.
die "Error detected while loading $filePath:\n$fullerror";
}
$self->{macroFileList}{$filePath} = 1;
}
=head2 sourceAlias
sourceAlias($path_to_PG_file);
Returns a relative URL to the F<source.pl> script, which may be installed in a
course's F<html> directory to allow formatted viewing of the problem source.
=cut
# ^function sourceAlias
# ^uses PG_restricted_eval
# ^uses %envir
# ^uses $envir{inputs_ref}
# ^uses $envir{psvn}
# ^uses $envir{probNum}
# ^uses $envir{displayMode}
# ^uses $envir{courseName}
sub sourceAlias {
my $self = shift;
my $path_to_file = shift;
my $envir = PG_restricted_eval(q!\%main::envir!);
my $user = $envir->{inputs_ref}->{user};
$user = " " unless defined($user);
my $out =
'source.pl?probSetKey='
. $envir->{psvn}
. '&probNum='
. $envir->{probNum}
. '&Mode='
. $envir->{displayMode}
. '&course='
. $envir->{courseName}
. '&user='
. $user
. '&displayPath='
. $path_to_file;
$out;
}
1;