1 |
<? |
2 |
/** |
3 |
* $Id: InfoBox.php,v 1.1 2003/05/13 16:22:22 joko Exp $ |
4 |
* |
5 |
* $Log: InfoBox.php,v $ |
6 |
* |
7 |
*/ |
8 |
|
9 |
|
10 |
class TopicMapper extends StandardFormContent { |
11 |
|
12 |
/** |
13 |
* holds reference to source object. |
14 |
*/ |
15 |
var $_datasource = NULL; |
16 |
|
17 |
/** |
18 |
* container for arguments needed for e.g. setting the source object. |
19 |
*/ |
20 |
var $_options = array(); |
21 |
|
22 |
var $_confirm_msg = "Die Daten wurden erfolgreich gespeichert!"; |
23 |
|
24 |
function TopicMapper($title, $model = array(), $blocks = array()) { |
25 |
$this->_model = $model; |
26 |
$this->_blocks = $blocks; |
27 |
|
28 |
// new as of 2003-05-29 |
29 |
$this->RULESET = new RuleSet("TopicMapper"); |
30 |
|
31 |
//print "YAI!<br/>"; |
32 |
|
33 |
// prefetch data |
34 |
//$this->data_prefetch(); |
35 |
|
36 |
// doesn't work if EditDataItem gets inherited |
37 |
//$parent = get_parent_class($this); |
38 |
//$this->$parent($title, $PHP_SELF, 600); |
39 |
|
40 |
// Create FEListBox objects from elements inside $this->_model. |
41 |
// new FEListBox( $key, TRUE, "250px", $cnt*15 . "pt", $list ) |
42 |
$this->_parts = array(); |
43 |
//print "count: " . count($this->_model) . "<br/>"; |
44 |
for ($i = 0; $i < count($this->_model); $i++) { |
45 |
$this->_model[$i][data] = array_flip($this->_model[$i][data]); |
46 |
$entry = $this->_model[$i]; |
47 |
$key = $entry[key]; |
48 |
array_push($this->_parts, $key); |
49 |
$this->_area[$key] = new FEMultiListBox($key, FALSE, "200px", "250px"); |
50 |
//$this->_listbox = new FEMultiListBox($this->_options[key], FALSE, NULL, NULL, array( abc => "def" ) ); |
51 |
} |
52 |
|
53 |
$this->StandardFormContent($title, $PHP_SELF, 600); |
54 |
|
55 |
//$this->form_init_elements(); |
56 |
|
57 |
} |
58 |
|
59 |
function normalize() { |
60 |
// calculate changes to the right block - diff the "BUCKET" |
61 |
// this uses two resources: |
62 |
// a) the RuleSet |
63 |
// b) the Bucket |
64 |
|
65 |
// extract values from ruleset |
66 |
$keys = array(); |
67 |
//$ruleset = new RuleSet("TopicMapper"); |
68 |
//$resolved = $ruleset->resolve('rightone'); |
69 |
$resolved = $this->RULESET->resolve_normalized('rightone'); |
70 |
print "unique: " . Dumper($resolved); |
71 |
// diff against BUCKET | _model |
72 |
$diff_plus = $this->BUCKET->diff($resolved[APPEND]); |
73 |
$diff_minus = $this->BUCKET->intersect($resolved[REMOVE]); |
74 |
} |
75 |
|
76 |
function inject_data($data) { |
77 |
$this->_data2rules($data); |
78 |
} |
79 |
|
80 |
// TODO: Init two boxes ( left & right ) here! |
81 |
function form_init_elements() { |
82 |
|
83 |
//print Dumper($_REQUEST); |
84 |
//print Dumper($_POST); |
85 |
|
86 |
$this->_action2rules(); |
87 |
|
88 |
/* |
89 |
$this->_post2session(); |
90 |
$this->_init_session(); |
91 |
$this->_normalize_parts(); |
92 |
$this->apply_operation(); |
93 |
$this->data2widget(); |
94 |
*/ |
95 |
|
96 |
$this->_rules2widget(); |
97 |
|
98 |
//$this->data2widget(); |
99 |
|
100 |
/* |
101 |
$elem = $this->_listbox; |
102 |
$this->add_element($elem); |
103 |
|
104 |
$clipboard = new FEMultiListBox("clip", FALSE, NULL, "250px"); |
105 |
$this->add_element($clipboard); |
106 |
*/ |
107 |
foreach ($this->_parts as $key) { |
108 |
//print Dumper($this->_area[$key]); |
109 |
$this->add_element($this->_area[$key]); |
110 |
} |
111 |
} |
112 |
|
113 |
function _data2rules($payload) { |
114 |
foreach ($payload as $entry) { |
115 |
$slot = $entry[key]; |
116 |
$data = $entry[data]; |
117 |
foreach ($data as $key => $value) { |
118 |
//print "key: $key<br/>"; |
119 |
if ($value) { |
120 |
$this->RULESET->add('APPEND', $slot, $key, $value, 'static'); |
121 |
} |
122 |
} |
123 |
} |
124 |
} |
125 |
|
126 |
function _action2rules() { |
127 |
|
128 |
// check action "add" |
129 |
if ($_REQUEST[ga] == 'add') { |
130 |
//print Dumper($_REQUEST); |
131 |
$values = $_REQUEST[payload]; |
132 |
//array_push($_SESSION[DataManager][op][$slot], $value); |
133 |
foreach ($values as $slot => $value) { |
134 |
if ($value) { |
135 |
$this->RULESET->add('APPEND', $slot, $value, $value); |
136 |
} |
137 |
} |
138 |
} |
139 |
|
140 |
// check action "moveback" |
141 |
// V1 - tied to add-logic |
142 |
//$slots = array_keys($_REQUEST[values]); |
143 |
// V2 - now independent |
144 |
$slots = split(',', $_REQUEST[keys]); |
145 |
if ($_REQUEST[move]) { |
146 |
$value = $_REQUEST[$slots[0]]; |
147 |
$this->RULESET->add('APPEND', $slots[1], $value); |
148 |
$this->RULESET->add('REMOVE', $slots[0], $value); |
149 |
} elseif ($_REQUEST[moveback]) { |
150 |
$value = $_REQUEST[$slots[1]]; |
151 |
$this->RULESET->add('APPEND', $slots[0], $value); |
152 |
$this->RULESET->add('REMOVE', $slots[1], $value); |
153 |
} |
154 |
|
155 |
/* |
156 |
foreach ($values as $slot => $value) { |
157 |
if ($value) { |
158 |
$this->RULESET->add('APPEND', $slot, $value); |
159 |
} |
160 |
} |
161 |
*/ |
162 |
|
163 |
} |
164 |
|
165 |
function _rules2widget() { |
166 |
while ($rule = $this->RULESET->read()) { |
167 |
//print Dumper($rule); |
168 |
$slot = $rule[slot]; |
169 |
|
170 |
$value = $rule[key]; |
171 |
$label = $rule[value]; |
172 |
|
173 |
$flag_reverse_lookup = 0; |
174 |
if (!$label) { |
175 |
$flag_reverse_lookup = 1; |
176 |
} |
177 |
|
178 |
if (!is_array($value)) { |
179 |
$value = array( $value ); |
180 |
} |
181 |
foreach ($value as $elem) { |
182 |
if ($rule[op] == 'APPEND') { |
183 |
//$this->_area[$slot]->_data_list[$elem] = $elem; |
184 |
// HACK!!! |
185 |
//$this->_area[$slot]->_data_list[$elem] = $label; |
186 |
if ($flag_reverse_lookup) { |
187 |
$label = $this->_reverse_lookup_label($elem); |
188 |
} |
189 |
$this->_area[$slot]->_data_list[$label] = $elem; |
190 |
} elseif ($rule[op] == 'REMOVE') { |
191 |
unset($this->_area[$slot]->_data_list[$elem]); |
192 |
// HACK!!! |
193 |
if ($flag_reverse_lookup) { |
194 |
$label = $this->_reverse_lookup_label($elem); |
195 |
unset($this->_area[$slot]->_data_list[$label]); |
196 |
} |
197 |
} |
198 |
} |
199 |
} |
200 |
} |
201 |
|
202 |
function result() { |
203 |
$result = array(); |
204 |
foreach ($this->_parts as $slot) { |
205 |
$result[$slot] = array_values( $this->_area[$slot]->_data_list ); |
206 |
} |
207 |
return $result; |
208 |
} |
209 |
|
210 |
function _reverse_lookup_label($value) { |
211 |
// If label is empty, try to look it up reverse from already |
212 |
// established data inside _model - "use other part/area". |
213 |
//print "NO LABEL<br/>"; |
214 |
foreach ($this->_parts as $key) { |
215 |
//print "key: $key<br/>"; |
216 |
//print "value: $value<br/>"; |
217 |
//print Dumper($this->_area[$key]); |
218 |
// new as of 2003-06-03: lookup label by key |
219 |
$idx = array_search($value, $this->_area[$key]->_data_list); |
220 |
//print "idx: $idx<br/>"; |
221 |
if ($idx) { return $idx; } |
222 |
} |
223 |
//print Dumper($this->_model); |
224 |
//exit; |
225 |
} |
226 |
|
227 |
// This is not exactly like the others: Don't generate a table, just a (simple) container. |
228 |
function form_content() { |
229 |
|
230 |
$table = html_table(350, 0, 3, 2); |
231 |
//$table->set_style("align: top;"); |
232 |
//print Dumper($table); |
233 |
//$table->set_align("top"); |
234 |
//$table->_default_col_attributes = array( valign => "top" ); |
235 |
//print Dumper($table); |
236 |
|
237 |
$row = array(); |
238 |
$row2 = array(); |
239 |
|
240 |
// string of concatenated keys, propagated as single hidden field |
241 |
$keys = array(); |
242 |
|
243 |
// touch all parts |
244 |
foreach ($this->_parts as $key) { |
245 |
|
246 |
array_push($keys, $key); |
247 |
|
248 |
// If anything went wrong, build a dummy FEBoxElement |
249 |
// with the error-message as value to show at least something. |
250 |
$elem = $this->get_element($key); |
251 |
if (!is_object($elem)) { |
252 |
$elem = new FEBoxElement($key); |
253 |
$elem->set_value("Sorry, ListBox could not be rendered."); |
254 |
$this->add_element($elem); |
255 |
} |
256 |
|
257 |
//$comp = container( $this->element_label($key), $this->element_form($key)); |
258 |
//$td->add(); |
259 |
array_push($row, |
260 |
//container( |
261 |
new TDtag(array( valign => "top" ), |
262 |
//$this->element_label($key), html_br(), |
263 |
$this->element_form($key) //, _HTML_SPACE |
264 |
//link::action('add', array( subtopic => $_REQUEST[subtopic] )) |
265 |
//, container("", html_input('', "payload[$key]"), form_submit("ga", "add")) |
266 |
) |
267 |
); |
268 |
array_push($row, |
269 |
//container( |
270 |
new TDtag(array( valign => "middle" ), |
271 |
form_submit('moveback', "<-----"), |
272 |
form_submit('move', "----->") |
273 |
//html_br(), |
274 |
//, _HTML_SPACE |
275 |
) |
276 |
); |
277 |
|
278 |
|
279 |
$appendix = html_div('', |
280 |
html_input('', "payload[$key]"), form_submit("ga", "add") |
281 |
); |
282 |
$appendix->set_style("width:200px;"); |
283 |
array_push($row2, $appendix); |
284 |
array_push($row2, NULL); |
285 |
|
286 |
} |
287 |
|
288 |
// handle even- & oddness |
289 |
array_pop($row); |
290 |
array_pop($row2); |
291 |
|
292 |
|
293 |
// new as of 2003-06-04: side-pane (result-, init- ext) |
294 |
foreach ($this->_blocks as $block) { |
295 |
$elem = $this->get_element($block); |
296 |
if (is_object($elem)) { |
297 |
$td = new TDtag(array( valign => "top" ), $this->element_form($block)); |
298 |
$td->set_style("padding: 5px; border-left: 1px solid red;"); |
299 |
array_push($row, $td); |
300 |
} |
301 |
} |
302 |
|
303 |
// propagate slots of $row to html table columns |
304 |
$tr = new TRtag(); |
305 |
foreach ($row as $column) { |
306 |
//$td = new TDtag(array( valign => "top" ), $column); |
307 |
$tr->add($column); |
308 |
} |
309 |
$table->add_row( $tr ); |
310 |
|
311 |
$tr2 = new TRtag(); |
312 |
foreach ($row2 as $column) { |
313 |
$tr2->add($column); |
314 |
} |
315 |
$table->add_row( $tr2 ); |
316 |
|
317 |
//$table->add( form_hidden("t", $_REQUEST[t]) ); |
318 |
$table->add( form_hidden("keys", join(',', $keys)) ); |
319 |
|
320 |
//$comp = $table; |
321 |
|
322 |
$subtitle = $this->_options[title]; |
323 |
$this->add_form_block($subtitle, $table); |
324 |
} |
325 |
|
326 |
function form_content_buttons() { |
327 |
//return; |
328 |
$div = new DIVtag( array( |
329 |
"style" => "background-color: #eeeeee;" |
330 |
. "padding-top:5px;padding-bottom:5px;" |
331 |
, |
332 |
"align"=>"center", "halign" => "middle", |
333 |
"nowrap"), |
334 |
//$this->add_action("Save"), |
335 |
form_submit("ga", "Go"), |
336 |
_HTML_SPACE, |
337 |
$this->add_cancel() |
338 |
); |
339 |
return $div; |
340 |
} |
341 |
|
342 |
function add_block($name) { |
343 |
|
344 |
$num_args = func_num_args(); |
345 |
$result_block = html_div(); |
346 |
$result_block->set_style("border: 1px solid groove; padding: 3px; font-family:Tahoma; font-size: 11px;"); |
347 |
|
348 |
for ($i = 1; $i < $num_args; $i++) { |
349 |
$result_block->add( func_get_arg($i) ); |
350 |
} |
351 |
|
352 |
$elem = new FEBoxElement($name); |
353 |
$elem->set_value($result_block); |
354 |
$this->add_element($elem); |
355 |
} |
356 |
|
357 |
|
358 |
|
359 |
|
360 |
|
361 |
// ============================================= |
362 |
// deprecated |
363 |
// ============================================= |
364 |
|
365 |
function _init_session() { |
366 |
|
367 |
// HACK: Check if slot 1 isn't empty. If so, subtract from slot 0. |
368 |
// (add it to slot 0's associated clipboard) |
369 |
// patched [2003-05-29]: just do this once! |
370 |
|
371 |
/* |
372 |
$part1 = $this->_parts[0]; |
373 |
$part2 = $this->_parts[1]; |
374 |
$clipboard_doubles = array_intersect($_SESSION[DataManager][op][$part1], $_SESSION[DataManager][op][$part2]); |
375 |
*/ |
376 |
|
377 |
//return; |
378 |
//if (!isset($_SESSION[DataManager][used])) { $_SESSION[DataManager][used] = 0; } |
379 |
|
380 |
if ($_SESSION[DataManager][used] == 0) { |
381 |
|
382 |
print "INIT<br/>"; |
383 |
|
384 |
if (sizeof($this->_model[1][data])) { |
385 |
$part1 = $this->_parts[0]; |
386 |
$part2 = $this->_parts[1]; |
387 |
$_SESSION[DataManager][op][$part1] = php::array_join_merge($_SESSION[DataManager][op][$part1], $this->_model[1][data]); |
388 |
} |
389 |
|
390 |
} |
391 |
|
392 |
|
393 |
} |
394 |
|
395 |
|
396 |
|
397 |
function _post2session() { |
398 |
|
399 |
//print Dumper($_SESSION); |
400 |
|
401 |
// TODO: clear complete clipboard |
402 |
//unset($_SESSION[DataManager]); |
403 |
|
404 |
foreach ($this->_parts as $part) { |
405 |
if (!is_array($_SESSION[DataManager][op][$part])) { |
406 |
$_SESSION[DataManager][op][$part] = array(); |
407 |
} |
408 |
} |
409 |
|
410 |
//print Dumper($_POST); |
411 |
|
412 |
// detect & apply "Move selected elements from left to right." |
413 |
foreach ($this->_model as $idx => $entry) { |
414 |
$key = $entry[key]; |
415 |
$token = $entry[token]; |
416 |
$list = $entry[data]; |
417 |
|
418 |
// Just do it if list-var is defined? |
419 |
//if (isset($_POST[$token]) && is_array($list)) { |
420 |
if (isset($_POST[$token])) { |
421 |
$_SESSION[DataManager][token] = $token; |
422 |
|
423 |
// V1 - append POST to clipboard |
424 |
//array_push($_SESSION[DataManager][op][$token], $_POST[$entry[key]]); |
425 |
|
426 |
// V2 - *merge* POST to clipboard |
427 |
$_SESSION[DataManager][op][$key] = |
428 |
php::array_join_merge($_SESSION[DataManager][op][$key], $_POST[$entry[key]]); |
429 |
|
430 |
// new as of 2003-05-29: flag session: "used" (actually this is a use-*count* per page-view) |
431 |
print "used!<br/>"; |
432 |
$_SESSION[DataManager][used] = 1; |
433 |
|
434 |
} |
435 |
} |
436 |
|
437 |
//print Dumper($_SESSION[DataManager]); |
438 |
} |
439 |
|
440 |
function _normalize_parts() { |
441 |
// normalize parts (delete equals from *both* clipboards) |
442 |
// by now: intersect *two* parts ... |
443 |
$part1 = $this->_parts[0]; |
444 |
$part2 = $this->_parts[1]; |
445 |
$clipboard_doubles = array_intersect($_SESSION[DataManager][op][$part1], $_SESSION[DataManager][op][$part2]); |
446 |
|
447 |
// ... and remove elements from both clipboards |
448 |
foreach ($clipboard_doubles as $key) { |
449 |
$this->_rmslot($key, $_SESSION[DataManager][op][$part1]); |
450 |
$this->_rmslot($key, $_SESSION[DataManager][op][$part2]); |
451 |
} |
452 |
} |
453 |
|
454 |
function apply_operation() { |
455 |
|
456 |
// tracepoint |
457 |
//print Dumper($_SESSION[DataManager][op]); |
458 |
//print Dumper($this->_model); |
459 |
|
460 |
// apply clipboard associated with each box to the others box content |
461 |
// (merge $_SESSION of other $part to $this->_model) |
462 |
|
463 |
$idx = 0; |
464 |
$idx_other = 1; |
465 |
// TODO: Better indexing! Enhance to actually make multiple columns possible! |
466 |
$map = array( |
467 |
); |
468 |
|
469 |
foreach ($this->_parts as $part) { |
470 |
|
471 |
// logic: Take diff from orig against clipboard, to calculate which are still left. |
472 |
$orig = array_values($this->_model[$idx][data]); |
473 |
$clipboard = $_SESSION[DataManager][op][$part]; |
474 |
$left = array_diff($orig, $clipboard); |
475 |
|
476 |
// overwrite data in model with calculated "left" ones: |
477 |
//$this->_model[$idx][data] = $left; |
478 |
|
479 |
// unset all in model which are already in clipboard - |
480 |
// doing kind of reverse mapping here utilizing array_search - |
481 |
// and move underlying items to *other* $part of $this->_model. |
482 |
foreach ($clipboard as $key) { |
483 |
$this->_swapslot($key, $this->_model[$idx][data], $this->_model[$idx_other][data]); |
484 |
} |
485 |
|
486 |
/* |
487 |
// tracepoint |
488 |
print "$part-ORIG: " . Dumper($this->_model[$idx][data]); |
489 |
print "$part-SESSION: " . Dumper($_SESSION[DataManager][op][$part]); |
490 |
print "$part-left: " . Dumper($left); |
491 |
*/ |
492 |
|
493 |
$idx++; |
494 |
$idx_other--; |
495 |
} |
496 |
|
497 |
} |
498 |
|
499 |
function _rmslot($slot, &$haystack) { |
500 |
$idx = array_search($slot, $haystack); |
501 |
unset($haystack[$idx]); |
502 |
} |
503 |
|
504 |
function _swapslot($slot, &$one, &$other) { |
505 |
// find itemkey from appkey |
506 |
$idx = array_search($slot, $one); |
507 |
|
508 |
// move key/value pair to other part of the model |
509 |
$other[$idx] = $one[$idx]; |
510 |
|
511 |
// delete by key in this part of the model |
512 |
unset($one[$idx]); |
513 |
} |
514 |
|
515 |
function data2widget() { |
516 |
foreach ($this->_model as $entry) { |
517 |
$key = $entry[key]; |
518 |
// V1 |
519 |
//$this->_area[$key]->_data_list =& $entry[data]; |
520 |
// V2 |
521 |
$this->_area[$key]->_data_list = php::array_join_merge($this->_area[$key]->_data_list, $entry[data]); |
522 |
} |
523 |
} |
524 |
|
525 |
|
526 |
} |
527 |
|
528 |
|
529 |
|
530 |
class RuleSet { |
531 |
|
532 |
function RuleSet($token) { |
533 |
$this->token = $token; |
534 |
if (!isset($_SESSION[RuleSet][$this->token])) { |
535 |
$_SESSION[RuleSet][$this->token] = array(); |
536 |
} |
537 |
//print Dumper($_SESSION[RuleSet]); |
538 |
reset($_SESSION[RuleSet][$this->token]); |
539 |
} |
540 |
|
541 |
function add($op, $slot, $key, $value = NULL, $tag = NULL) { |
542 |
$rule = array(op => $op, slot => $slot, key => $key, value => $value, tag => $tag); |
543 |
//print "rule: " . Dumper($rule); |
544 |
array_push($_SESSION[RuleSet][$this->token], $rule); |
545 |
} |
546 |
|
547 |
function read() { |
548 |
$current = current($_SESSION[RuleSet][$this->token]); |
549 |
next($_SESSION[RuleSet][$this->token]); |
550 |
return $current; |
551 |
} |
552 |
|
553 |
function readmulti() { |
554 |
return $_SESSION[RuleSet][$this->token]; |
555 |
} |
556 |
|
557 |
function resolve($slot) { |
558 |
$keys = array(); |
559 |
$result = array(); |
560 |
$rules = $this->readmulti(); |
561 |
foreach ($rules as $rule) { |
562 |
if ($rule[slot] == $slot && $rule[tag] != 'static') { |
563 |
//array_push($keys, $rule[key]); |
564 |
$keys[$rule[op]] = array_merge($keys[$rule[op]], $rule[key]); |
565 |
} |
566 |
//$keys[$rule[op]] = array_unique($keys[$rule[op]]); |
567 |
} |
568 |
return $keys; |
569 |
} |
570 |
|
571 |
function resolve_normalized($slot) { |
572 |
|
573 |
$stats = array(); |
574 |
$normalized = array(); |
575 |
$resolved = $this->resolve($slot); |
576 |
|
577 |
//print Dumper($resolved); |
578 |
//exit; |
579 |
|
580 |
// declare tokens used inside rules |
581 |
$tokens = array( 'APPEND', 'REMOVE' ); |
582 |
|
583 |
// count operations |
584 |
foreach ($tokens as $token) { |
585 |
foreach ($resolved[$token] as $item) { |
586 |
$stats[$token][$item]++; |
587 |
} |
588 |
} |
589 |
//return $stats; |
590 |
|
591 |
// apply normalization (delete all slots from $stats which have even counts) |
592 |
// compare APPEND-count against REMOVE-count |
593 |
$token1 = $tokens[0]; |
594 |
$token2 = $tokens[1]; |
595 |
//foreach ($tokens as $token) { |
596 |
//foreach ($stats[$token1] as $item) { |
597 |
//$stats[$token][$item]++; |
598 |
if ($stats[$token1] == $stats[$token2]) { |
599 |
unset($stats[$token1]); |
600 |
unset($stats[$token2]); |
601 |
} else { |
602 |
//print "HMM???<br/>"; |
603 |
foreach ($stats[$token2] as $idx => $item) { |
604 |
if ($stats[$token1][$idx] >= $stats[$token2][$idx]) { |
605 |
//print "YAI!!!<br/>"; |
606 |
unset($stats[$token2][$idx]); |
607 |
} else { |
608 |
unset($stats[$token1][$idx]); |
609 |
} |
610 |
} |
611 |
} |
612 |
//} |
613 |
//} |
614 |
|
615 |
// build normalized result |
616 |
foreach ($tokens as $token) { |
617 |
$normalized[$token] = array_keys($stats[$token]); |
618 |
} |
619 |
return $normalized; |
620 |
|
621 |
$intersection = array_intersect($resolved[APPEND], $resolved[REMOVE]); |
622 |
foreach ($intersection as $item) { |
623 |
//print "item: " . Dumper($item); |
624 |
$normalized[APPEND] = array_diff( $intersection, $resolved[APPEND] ); |
625 |
$normalized[REMOVE] = array_diff( $intersection, $resolved[REMOVE] ); |
626 |
} |
627 |
return $normalized; |
628 |
} |
629 |
|
630 |
} |
631 |
|
632 |
?> |