~kosova/+junk/tuxfamily-twiki

« back to all changes in this revision

Viewing changes to foswiki/lib/Foswiki/Form.pm

  • Committer: James Michael DuPont
  • Date: 2009-07-18 19:58:49 UTC
  • Revision ID: jamesmikedupont@gmail.com-20090718195849-vgbmaht2ys791uo2
added foswiki

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# See bottom of file for license and copyright details
 
2
 
 
3
=begin TML
 
4
 
 
5
---+ package Foswiki::Form
 
6
 
 
7
Object representing a single form definition.
 
8
 
 
9
Form definitions are mainly used to control rendering of a form for
 
10
editing, though there is some application login there that handles
 
11
transferring values between edits and saves.
 
12
 
 
13
A form definition consists of a Foswiki::Form object, which has a list
 
14
of field definitions. Each field definition is an object of a type
 
15
derived from Foswiki::Form::FieldDefinition. These objects are responsible
 
16
for the actual syntax and semantics of the field type. Form definitions
 
17
are parsed from Foswiki tables, and the types are mapped by name to a
 
18
class declared in Foswiki::Form::* - for example, the =text= type is mapped
 
19
to =Foswiki::Form::Text= and the =checkbox= type to =Foswiki::Form::Checkbox=.
 
20
 
 
21
The =Foswiki::Form::FieldDefinition= class declares default behaviours for
 
22
types that accept a single value in their definitions. The
 
23
=Foswiki::Form::ListFieldDefinition= extends this for types that have lists
 
24
of possible values.
 
25
 
 
26
=cut
 
27
 
 
28
# The bulk of this object is a parser for form definitions. All the
 
29
# intelligence is in the individual field types.
 
30
 
 
31
package Foswiki::Form;
 
32
 
 
33
use strict;
 
34
use Assert;
 
35
use Error qw( :try );
 
36
 
 
37
# The following are reserved as URL parameters to scripts and may not be
 
38
# used as field names in forms.
 
39
my %reservedFieldNames = map { $_ => 1 }
 
40
  qw( action breaklock contenttype cover dontnotify editaction
 
41
  forcenewrevision formtemplate onlynewtopic onlywikiname
 
42
  originalrev skin templatetopic text topic topicparent user );
 
43
 
 
44
=begin TML
 
45
 
 
46
---++ ClassMethod new ( $session, $web, $form, \@def )
 
47
 
 
48
Looks up a form in the session object or, if it hasn't been read yet,
 
49
reads it from the form definition topic on disc.
 
50
   * =$web= - default web to recover form from, if =$form= doesn't
 
51
     specify a web
 
52
   * =$form= - name of the form
 
53
   * =\@def= - optional. A reference to a list of field definitions.
 
54
     If present, these definitions will be used, rather than any read from
 
55
     the form definition topic. Note that this array should not be modified
 
56
     again after being passed into this constructor (it is not copied).
 
57
 
 
58
If the form cannot be read, will return undef to allow the caller to take
 
59
appropriate action.
 
60
 
 
61
=cut
 
62
 
 
63
sub new {
 
64
    my ( $class, $session, $web, $form, $def ) = @_;
 
65
 
 
66
    ( $web, $form ) = $session->normalizeWebTopicName( $web, $form );
 
67
 
 
68
    # Validate
 
69
    $web =
 
70
      Foswiki::Sandbox::untaint( $web, \&Foswiki::Sandbox::validateWebName );
 
71
    $form =
 
72
      Foswiki::Sandbox::untaint( $form, \&Foswiki::Sandbox::validateTopicName );
 
73
 
 
74
    unless ( $web && $form ) {
 
75
        return undef;
 
76
    }
 
77
 
 
78
    my $this = $session->{forms}->{"$web.$form"};
 
79
    unless ($this) {
 
80
 
 
81
        $this = bless(
 
82
            {
 
83
                session => $session,
 
84
                web     => $web,
 
85
                topic   => $form,
 
86
            },
 
87
            $class
 
88
        );
 
89
 
 
90
        $session->{forms}->{"$web.$form"} = $this;
 
91
 
 
92
        unless ($def) {
 
93
 
 
94
            my $store = $session->{store};
 
95
 
 
96
            # Read topic that defines the form
 
97
            if ( $store->topicExists( $web, $form ) ) {
 
98
                my ( $meta, $text ) =
 
99
                  $store->readTopic( $session->{user}, $web, $form, undef );
 
100
 
 
101
                $this->{fields} = _parseFormDefinition( $this, $meta, $text );
 
102
            }
 
103
            else {
 
104
                delete $session->{forms}->{"$web.$form"};
 
105
                return undef;
 
106
            }
 
107
        }
 
108
        elsif ( ref($def) eq 'ARRAY' ) {
 
109
            $this->{fields} = $def;
 
110
        }
 
111
        else {
 
112
 
 
113
            # Foswiki::Meta object
 
114
            $this->{fields} = $this->_extractPseudoFieldDefs($def);
 
115
        }
 
116
    }
 
117
 
 
118
    return $this;
 
119
}
 
120
 
 
121
=begin TML
 
122
 
 
123
---++ ObjectMethod finish()
 
124
Break circular references.
 
125
 
 
126
=cut
 
127
 
 
128
# Note to developers; please undef *all* fields in the object explicitly,
 
129
# whether they are references or not. That way this method is "golden
 
130
# documentation" of the live fields in the object.
 
131
sub finish {
 
132
    my $this = shift;
 
133
    undef $this->{web};
 
134
    undef $this->{topic};
 
135
    foreach ( @{ $this->{fields} } ) {
 
136
        $_->finish();
 
137
    }
 
138
    undef $this->{fields};
 
139
    undef $this->{session};
 
140
}
 
141
 
 
142
=begin TML
 
143
 
 
144
---++ StaticMethod fieldTitle2FieldName($title) -> $name
 
145
Chop out all except A-Za-z0-9_. from a field name to create a
 
146
valid "name" for storing in meta-data
 
147
 
 
148
=cut
 
149
 
 
150
sub fieldTitle2FieldName {
 
151
    my ($text) = @_;
 
152
    return '' unless defined($text);
 
153
    $text =~ s/<nop>//g;             # support <nop> character in title
 
154
    $text =~ s/[^A-Za-z0-9_\.]//g;
 
155
    return $text;
 
156
}
 
157
 
 
158
# Get definition from supplied topic text
 
159
# Returns array of arrays
 
160
#   1st - list fields
 
161
#   2nd - name, title, type, size, vals, tooltip, attributes
 
162
#   Possible attributes are "M" (mandatory field)
 
163
sub _parseFormDefinition {
 
164
    my ( $this, $meta, $text ) = @_;
 
165
 
 
166
    my $store   = $this->{session}->{store};
 
167
    my @fields  = ();
 
168
    my $inBlock = 0;
 
169
    $text =~ s/\r//g;
 
170
    $text =~ s/\\\n//g;    # remove trailing '\' and join continuation lines
 
171
 
 
172
# | *Name:* | *Type:* | *Size:* | *Value:*  | *Tooltip message:* | *Attributes:* |
 
173
# Tooltip and attributes are optional
 
174
    foreach my $line ( split( /\n/, $text ) ) {
 
175
        if ( $line =~ /^\s*\|.*Name[^|]*\|.*Type[^|]*\|.*Size[^|]*\|/ ) {
 
176
            $inBlock = 1;
 
177
            next;
 
178
        }
 
179
 
 
180
       # Only insist on first field being present FIXME - use oops page instead?
 
181
        if ( $inBlock && $line =~ s/^\s*\|\s*// ) {
 
182
            $line =~ s/\\\|/\007/g;    # protect \| from split
 
183
            my ( $title, $type, $size, $vals, $tooltip, $attributes ) =
 
184
              map { s/\007/|/g; $_ } split( /\s*\|\s*/, $line );
 
185
 
 
186
            $title ||= '';
 
187
 
 
188
            $type ||= '';
 
189
            $type = lc($type);
 
190
            $type =~ s/^\s*//go;
 
191
            $type =~ s/\s*$//go;
 
192
            $type = 'text' if ( !$type );
 
193
 
 
194
            $size ||= '';
 
195
 
 
196
            $vals ||= '';
 
197
            $vals =
 
198
              $this->{session}
 
199
              ->handleCommonTags( $vals, $this->{web}, $this->{topic}, $meta );
 
200
            $vals =~ s/<\/?(nop|noautolink)\/?>//go;
 
201
            $vals =~ s/^\s+//g;
 
202
            $vals =~ s/\s+$//g;
 
203
 
 
204
            $tooltip ||= '';
 
205
 
 
206
            $attributes ||= '';
 
207
            $attributes =~ s/\s*//go;
 
208
            $attributes = '' if ( !$attributes );
 
209
 
 
210
            my $definingTopic = "";
 
211
            if ( $title =~ /\[\[(.+)\]\[(.+)\]\]/ ) {
 
212
 
 
213
                # use common defining topics with different field titles
 
214
                $definingTopic = fieldTitle2FieldName($1);
 
215
                $title         = $2;
 
216
            }
 
217
 
 
218
            my $name = fieldTitle2FieldName($title);
 
219
 
 
220
            # Rename fields with reserved names
 
221
            if ( $reservedFieldNames{$name} ) {
 
222
                $name .= '_';
 
223
            }
 
224
 
 
225
            my $fieldDef = $this->createField(
 
226
                $type,
 
227
                name          => $name,
 
228
                title         => $title,
 
229
                size          => $size,
 
230
                value         => $vals,
 
231
                tooltip       => $tooltip,
 
232
                attributes    => $attributes,
 
233
                definingTopic => $definingTopic,
 
234
                web           => $this->{web},
 
235
                topic         => $this->{topic}
 
236
            );
 
237
            push( @fields, $fieldDef );
 
238
 
 
239
            $this->{mandatoryFieldsPresent} ||= $fieldDef->isMandatory();
 
240
        }
 
241
        else {
 
242
            $inBlock = 0;
 
243
        }
 
244
    }
 
245
 
 
246
    return \@fields;
 
247
}
 
248
 
 
249
# PROTECTED
 
250
# Create a field object. Done like this so that this method can be
 
251
# overridden by subclasses to extend the range of field types.
 
252
sub createField {
 
253
    my $this = shift;
 
254
    my $type = shift;
 
255
 
 
256
    # The untaint is required for the validation *and* the ucfirst, which
 
257
    # retaints when use locale is in force
 
258
    my $class = Foswiki::Sandbox::untaint(
 
259
        $type,
 
260
        sub {
 
261
            my $class = shift;
 
262
            $class =~ /^(\w*)/;    # cut off +buttons etc
 
263
            return 'Foswiki::Form::' . ucfirst($1);
 
264
        }
 
265
    );
 
266
 
 
267
    eval 'require ' . $class;
 
268
    if ($@) {
 
269
 
 
270
        # Type not available; use base type
 
271
        require Foswiki::Form::FieldDefinition;
 
272
        $class = 'Foswiki::Form::FieldDefinition';
 
273
    }
 
274
    return $class->new( session => $this->{session}, type => $type, @_ );
 
275
}
 
276
 
 
277
# Generate a link to the given topic, so we can bring up details in a
 
278
# separate window.
 
279
sub _link {
 
280
    my ( $this, $meta, $string, $tooltip, $topic ) = @_;
 
281
 
 
282
    $string =~ s/[\[\]]//go;
 
283
 
 
284
    $topic ||= $string;
 
285
    my $defaultToolTip =
 
286
      $this->{session}->i18n->maketext('Details in separate window');
 
287
    $tooltip ||= $defaultToolTip;
 
288
 
 
289
    my $web;
 
290
    ( $web, $topic ) =
 
291
      $this->{session}->normalizeWebTopicName( $this->{web}, $topic );
 
292
 
 
293
    my $link;
 
294
 
 
295
    my $store = $this->{session}->{store};
 
296
    if ( $store->topicExists( $web, $topic ) ) {
 
297
        $link = CGI::a(
 
298
            {
 
299
                target => $topic,
 
300
                title  => $tooltip,
 
301
                href =>
 
302
                  $this->{session}->getScriptUrl( 0, 'view', $web, $topic ),
 
303
                rel => 'nofollow'
 
304
            },
 
305
            $string
 
306
        );
 
307
    }
 
308
    else {
 
309
        my $expanded =
 
310
          $this->{session}->handleCommonTags( $string, $web, $topic, $meta );
 
311
        if ( $tooltip ne $defaultToolTip ) {
 
312
            $link = CGI::span( { title => $tooltip }, $expanded );
 
313
        }
 
314
        else {
 
315
            $link = $expanded;
 
316
        }
 
317
    }
 
318
 
 
319
    return $link;
 
320
}
 
321
 
 
322
=begin TML
 
323
 
 
324
---++ ObjectMethod renderForEdit( $web, $topic, $meta ) -> $html
 
325
 
 
326
   * =$web= the web of the topic being rendered
 
327
   * =$topic= the topic being rendered
 
328
   * =$meta= the meta data for the form
 
329
 
 
330
Render the form fields for entry during an edit session, using data values
 
331
from $meta
 
332
 
 
333
=cut
 
334
 
 
335
sub renderForEdit {
 
336
    my ( $this, $web, $topic, $meta ) = @_;
 
337
    ASSERT( $meta->isa('Foswiki::Meta') ) if DEBUG;
 
338
    require CGI;
 
339
    my $session = $this->{session};
 
340
 
 
341
    if ( $this->{mandatoryFieldsPresent} ) {
 
342
        $session->enterContext('mandatoryfields');
 
343
    }
 
344
    my $tmpl = $session->templates->readTemplate("form");
 
345
    $tmpl = $session->handleCommonTags( $tmpl, $web, $topic, $meta );
 
346
 
 
347
    # Note: if WEBFORMS preference is not set, can only delete form.
 
348
    $tmpl =~ s/%FORMTITLE%/_link(
 
349
        $this, $meta, $this->{web}.'.'.$this->{topic})/ge;
 
350
    my ( $text, $repeatTitledText, $repeatUntitledText, $afterText ) =
 
351
      split( /%REPEAT%/, $tmpl );
 
352
 
 
353
    foreach my $fieldDef ( @{ $this->{fields} } ) {
 
354
 
 
355
        my $value;
 
356
        my $tooltip       = $fieldDef->{tooltip};
 
357
        my $definingTopic = $fieldDef->{definingTopic};
 
358
        my $title         = $fieldDef->{title};
 
359
        my $tmp           = '';
 
360
        if ( !$title && !$fieldDef->isEditable() ) {
 
361
 
 
362
            # Special handling for untitled labels.
 
363
            # SMELL: Assumes that uneditable fields are not multi-valued
 
364
            $tmp   = $repeatUntitledText;
 
365
            $value = $session->{renderer}->getRenderedVersion(
 
366
                $session->handleCommonTags(
 
367
                    $fieldDef->{value},
 
368
                    $web, $topic, $meta
 
369
                )
 
370
            );
 
371
        }
 
372
        else {
 
373
            $tmp = $repeatTitledText;
 
374
 
 
375
            if ( defined( $fieldDef->{name} ) ) {
 
376
                my $field = $meta->get( 'FIELD', $fieldDef->{name} );
 
377
                $value = $field->{value};
 
378
            }
 
379
            my $extra = '';    # extras on col 0
 
380
 
 
381
            unless ( defined($value) ) {
 
382
                my $dv = $fieldDef->getDefaultValue($value);
 
383
                if ( defined($dv) ) {
 
384
                    $dv =
 
385
                      $this->{session}
 
386
                      ->handleCommonTags( $dv, $web, $topic, $meta );
 
387
                    $value = Foswiki::expandStandardEscapes($dv);    # Item2837
 
388
                }
 
389
            }
 
390
 
 
391
            # Give plugin field types a chance first (but no chance to add to
 
392
            # col 0 :-(
 
393
            # SMELL: assumes that the field value is a string
 
394
            my $output = $session->{plugins}->dispatch(
 
395
                'renderFormFieldForEditHandler', $fieldDef->{name},
 
396
                $fieldDef->{type},               $fieldDef->{size},
 
397
                $value,                          $fieldDef->{attributes},
 
398
                $fieldDef->{value}
 
399
            );
 
400
 
 
401
            if ($output) {
 
402
                $value = $output;
 
403
            }
 
404
            else {
 
405
                ( $extra, $value ) =
 
406
                  $fieldDef->renderForEdit( $web, $topic, $value );
 
407
            }
 
408
 
 
409
            if ( $fieldDef->isMandatory() ) {
 
410
                $extra .= CGI::span( { class => 'foswikiAlert' }, ' *' );
 
411
            }
 
412
 
 
413
            $tmp =~ s/%ROWTITLE%/_link(
 
414
                $this, $meta, $title, $tooltip, $definingTopic )/ge;
 
415
            $tmp =~ s/%ROWEXTRA%/$extra/g;
 
416
        }
 
417
        $tmp =~ s/%ROWVALUE%/$value/g;
 
418
        $text .= $tmp;
 
419
    }
 
420
 
 
421
    $text .= $afterText;
 
422
    return $text;
 
423
}
 
424
 
 
425
=begin TML
 
426
 
 
427
---++ ObjectMethod renderHidden( $meta ) -> $html
 
428
 
 
429
Render form fields found in the meta as hidden inputs, so they pass
 
430
through edits untouched.
 
431
 
 
432
=cut
 
433
 
 
434
sub renderHidden {
 
435
    my ( $this, $meta ) = @_;
 
436
    ASSERT( $meta->isa('Foswiki::Meta') ) if DEBUG;
 
437
 
 
438
    my $text = '';
 
439
 
 
440
    foreach my $field ( @{ $this->{fields} } ) {
 
441
        $text .= $field->renderHidden($meta);
 
442
    }
 
443
 
 
444
    return $text;
 
445
}
 
446
 
 
447
=begin TML
 
448
 
 
449
---++ ObjectMethod getFieldValuesFromQuery($query, $metaObject) -> ( $seen, \@missing )
 
450
 
 
451
Extract new values for form fields from a query.
 
452
 
 
453
   * =$query= - the query
 
454
   * =$metaObject= - the meta object that is storing the form values
 
455
 
 
456
For each field, if there is a value in the query, use it.
 
457
Otherwise if there is already entry for the field in the meta, keep it.
 
458
 
 
459
Returns the number of fields which had values provided by the query,
 
460
and a references to an array of the names of mandatory fields that were
 
461
missing from the query.
 
462
 
 
463
=cut
 
464
 
 
465
sub getFieldValuesFromQuery {
 
466
    my ( $this, $query, $meta ) = @_;
 
467
    ASSERT( $meta->isa('Foswiki::Meta') ) if DEBUG;
 
468
    my @missing;
 
469
    my $seen = 0;
 
470
 
 
471
    # Remove the old defs so we apply the
 
472
    # order in the form definition, and not the
 
473
    # order in the previous meta object. See Item1982.
 
474
    my @old = $meta->find('FIELD');
 
475
    $meta->remove('FIELD');
 
476
    foreach my $fieldDef ( @{ $this->{fields} } ) {
 
477
        my ( $set, $present ) =
 
478
          $fieldDef->populateMetaFromQueryData( $query, $meta, \@old );
 
479
        if ($present) {
 
480
            $seen++;
 
481
        }
 
482
        if ( !$set && $fieldDef->isMandatory() ) {
 
483
 
 
484
            # Remember missing mandatory fields
 
485
            push( @missing, $fieldDef->{title} || "unnamed field" );
 
486
        }
 
487
    }
 
488
    return ( $seen, \@missing );
 
489
}
 
490
 
 
491
=begin TML
 
492
 
 
493
---++ ObjectMethod isTextMergeable( $name ) -> $boolean
 
494
 
 
495
   * =$name= - name of a form field (value of the =name= attribute)
 
496
 
 
497
Returns true if the type of the named field allows it to be text-merged.
 
498
 
 
499
If the form does not define the field, it is assumed to be mergeable.
 
500
 
 
501
=cut
 
502
 
 
503
sub isTextMergeable {
 
504
    my ( $this, $name ) = @_;
 
505
 
 
506
    my $fieldDef = $this->getField($name);
 
507
    if ($fieldDef) {
 
508
        return $fieldDef->isTextMergeable();
 
509
    }
 
510
 
 
511
    # Field not found - assume it is mergeable
 
512
    return 1;
 
513
}
 
514
 
 
515
=begin TML
 
516
 
 
517
---++ ObjectMethod getField( $name ) -> $fieldDefinition
 
518
 
 
519
   * =$name= - name of a form field (value of the =name= attribute)
 
520
 
 
521
Returns a =Foswiki::Form::FieldDefinition=, or undef if the form does not
 
522
define the field.
 
523
 
 
524
=cut
 
525
 
 
526
sub getField {
 
527
    my ( $this, $name ) = @_;
 
528
    foreach my $fieldDef ( @{ $this->{fields} } ) {
 
529
        return $fieldDef if ( $fieldDef->{name} && $fieldDef->{name} eq $name );
 
530
    }
 
531
    return undef;
 
532
}
 
533
 
 
534
=begin TML
 
535
 
 
536
---++ ObjectMethod getFields() -> \@fields
 
537
 
 
538
Return a list containing references to field name/value pairs.
 
539
Each entry in the list has a {name} field and a {value} field. It may
 
540
have other fields as well, which caller should ignore. The
 
541
returned list should be treated as *read only* (must not be written to).
 
542
 
 
543
=cut
 
544
 
 
545
sub getFields {
 
546
    my $this = shift;
 
547
    return $this->{fields};
 
548
}
 
549
 
 
550
sub renderForDisplay {
 
551
    my ( $this, $meta ) = @_;
 
552
 
 
553
    my $templates = $this->{session}->templates;
 
554
    $templates->readTemplate('formtables');
 
555
 
 
556
    my $text        = '';
 
557
    my $rowTemplate = $templates->expandTemplate('FORM:display:row');
 
558
    foreach my $fieldDef ( @{ $this->{fields} } ) {
 
559
        my $fm = $meta->get( 'FIELD', $fieldDef->{name} );
 
560
        next unless $fm;
 
561
        my $fa = $fm->{attributes} || '';
 
562
        unless ( $fa =~ /H/ ) {
 
563
            my $row = $rowTemplate;
 
564
 
 
565
            # Legacy; was %A_TITLE% before it was $title
 
566
            $row =~ s/%A_TITLE%/\$title/g;
 
567
            $row =~ s/%A_VALUE%/\$value/g;    # Legacy
 
568
            $text .= $fieldDef->renderForDisplay( $row, $fm->{value} );
 
569
        }
 
570
    }
 
571
    $text = $templates->expandTemplate('FORM:display:header') . $text;
 
572
    $text .= $templates->expandTemplate('FORM:display:footer');
 
573
 
 
574
    # substitute remaining placeholders in footer and header
 
575
    $text =~ s/%A_TITLE%/$this->{web}.$this->{topic}/g;
 
576
 
 
577
    return $text;
 
578
}
 
579
 
 
580
# extractPseudoFieldDefs( $meta ) -> $fieldDefs
 
581
# Examine the FIELDs in $meta and reverse-engineer a set of field
 
582
# definitions that can be used to construct a new "pseudo-form". This
 
583
# fake form can be used to support editing of topics that have an attached
 
584
# form that has no definition topic.
 
585
sub _extractPseudoFieldDefs {
 
586
    my ( $this, $meta ) = @_;
 
587
    my @fields = $meta->find('FIELD');
 
588
    my @fieldDefs;
 
589
    require Foswiki::Form::FieldDefinition;
 
590
    foreach my $field (@fields) {
 
591
 
 
592
        # Fields are name, value, title, but there is no other type
 
593
        # information so we have to treat them all as "text" :-(
 
594
        my $fieldDef = new Foswiki::Form::FieldDefinition(
 
595
            session    => $this->{session},
 
596
            name       => $field->{name},
 
597
            title      => $field->{title} || $field->{name},
 
598
            attributes => $field->{attributes} || ''
 
599
        );
 
600
        push( @fieldDefs, $fieldDef );
 
601
    }
 
602
    return \@fieldDefs;
 
603
}
 
604
 
 
605
1;
 
606
 
 
607
__DATA__
 
608
 
 
609
Module of Foswiki - The Free and Open Source Wiki, http://foswiki.org/, http://Foswiki.org/
 
610
 
 
611
Copyright (C) 2008-2009 Foswiki Contributors. Foswiki Contributors
 
612
are listed in the AUTHORS file in the root of this distribution.
 
613
NOTE: Please extend that file, not this notice.
 
614
 
 
615
Additional copyrights apply to some or all of the code in this
 
616
file as follows:
 
617
 
 
618
Copyright (C) 2001-2007 Peter Thoeny, peter@thoeny.org
 
619
and TWiki Contributors. All Rights Reserved. TWiki Contributors
 
620
are listed in the AUTHORS file in the root of this distribution.
 
621
NOTE: Please extend that file, not this notice.
 
622
 
 
623
This program is free software; you can redistribute it and/or
 
624
modify it under the terms of the GNU General Public License
 
625
as published by the Free Software Foundation; either version 2
 
626
of the License, or (at your option) any later version. For
 
627
more details read LICENSE in the root of this distribution.
 
628
 
 
629
This program is distributed in the hope that it will be useful,
 
630
but WITHOUT ANY WARRANTY; without even the implied warranty of
 
631
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
632
 
 
633
As per the GPL, removal of this notice is prohibited.
 
634