[ Index ] |
|
Code source de PHPonTrax 2.6.6-svn |
1 <?php 2 /** 3 * File containing ActiveRecordHelper class and support functions 4 * 5 * (PHP 5) 6 * 7 * @package PHPonTrax 8 * @version $Id: active_record_helper.php 250 2006-08-25 15:56:48Z john $ 9 * @copyright (c) 2005 John Peterson 10 * 11 * Permission is hereby granted, free of charge, to any person obtaining 12 * a copy of this software and associated documentation files (the 13 * "Software"), to deal in the Software without restriction, including 14 * without limitation the rights to use, copy, modify, merge, publish, 15 * distribute, sublicense, and/or sell copies of the Software, and to 16 * permit persons to whom the Software is furnished to do so, subject to 17 * the following conditions: 18 * 19 * The above copyright notice and this permission notice shall be 20 * included in all copies or substantial portions of the Software. 21 * 22 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 24 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 25 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 26 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 27 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 28 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 29 * 30 * @package PHPonTrax 31 */ 32 33 /** 34 * @todo Document this class 35 */ 36 class ActiveRecordHelper extends Helpers { 37 38 /** 39 * Whether to generate scaffolding HTML 40 * 41 * Set to true in {@link form_scaffolding.phtml}. If true 42 * generate HTML scaffold otherwise generate final HTML 43 * @var boolean 44 */ 45 public $scaffolding = false; 46 47 /** 48 * Returns a default input tag for the type of object returned by the method. Example 49 * (title is a VARCHAR column and holds "Hello World"): 50 * input("post", "title") => 51 * <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" /> 52 * @uses to_tag() 53 */ 54 function input($object_name, $attribute_name, $options = array()) { 55 return $this->to_tag($object_name, $attribute_name, $options); 56 } 57 58 /** 59 * @todo Document this method 60 * @uses to_scaffold_tag() 61 */ 62 function input_scaffolding($object_name, $attribute_name, $options = array()) { 63 return $this->to_scaffold_tag($object_name, $attribute_name, $options); 64 } 65 66 /** 67 * Returns an entire form with input tags and everything for a specified Active Record object. Example 68 * (post is a new record that has a title using VARCHAR and a body using TEXT): 69 * form("post") => 70 * <form action='/post/create' method='post'> 71 * <p> 72 * <label for="post_title">Title</label><br /> 73 * <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" /> 74 * </p> 75 * <p> 76 * <label for="post_body">Body</label><br /> 77 * <textarea cols="40" id="post_body" name="post[body]" rows="20"> 78 * Back to the hill and over it again! 79 * </textarea> 80 * </p> 81 * <input type='submit' value='Create' /> 82 * </form> 83 * 84 * It's possible to specialize the form builder by using a different action name and by supplying another 85 * block renderer. Example (entry is a new record that has a message attribute using VARCHAR): 86 * 87 * form("entry", array('action' => "sign", 'input_block' => 88 * 'foreach($record->content_columns() as $column_name => $column) $contents .= Inflector::humanize($column_name) . ": " . input($record, $column) . "<br />"')) => 89 * 90 * <form action='/post/sign' method='post'> 91 * Message: 92 * <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" /><br /> 93 * <input type='submit' value='Sign' /> 94 * </form> 95 * 96 * It's also possible to add additional content to the form by giving it a block, such as: 97 * 98 * form("entry", array('action' => "sign", 'block' => 99 * content_tag("b", "Department") . 100 * collection_select("department", "id", $departments, "id", "name")) 101 * ) 102 * @uses all_input_tags() 103 * @uses content_tag() 104 * @uses Helpers::object() 105 */ 106 function form($record_name, $options = array()) { 107 $record = $this->object($record_name); 108 $options["action"] = $options[":action"] ? $options[":action"] : $record->is_new_record() ? "add" : "save"; 109 $action = url_for(array(':action' => $options[':action'], ':id' => $record->id)); 110 $submit_value = (isset($options['submit_value']) ? $options['submit_value'] : ucfirst(preg_replace('/[^\w]/', '', $options[':action']))); 111 112 $contents = ''; 113 if(!$record->is_new_record()) $contents .= hidden_field($record_name, 'id'); 114 $contents .= $this->all_input_tags($record, $record_name, $options); 115 if(isset($options['block'])) $contents .= eval($options['block']); 116 $contents .= "<br>".submit_tag($submit_value)."<br><br>"; 117 118 return $this->content_tag('form', $contents, array('action' => $action, 'method' => 'post')); 119 } 120 121 /** 122 * Returns a string containing the error message attached to the +method+ on the +object+, if one exists. 123 * This error message is wrapped in a DIV tag, which can be specialized to include both a +prepend_text+ and +append_text+ 124 * to properly introduce the error and a +css_class+ to style it accordingly. Examples (post has an error message 125 * "can't be empty" on the title attribute): 126 * 127 * <?= error_message_on("post", "title") ?> => 128 * <div class="formError">can't be empty</div> 129 * 130 * <?= error_message_on "post", "title", "Title simply ", " (or it won't work)", "inputError" ?> => 131 * <div class="inputError">Title simply can't be empty (or it won't work)</div> 132 * @uses attribute_name 133 * @uses controller_object 134 * @uses content_tag() 135 * @uses object_name 136 */ 137 function error_message_on($object_name, $attribute_name, $prepend_text = "", $append_text = "", $css_class = "formError") { 138 $this->object_name = $object_name; 139 $this->attribute_name = $attribute_name; 140 $object = $this->controller_object->$object_name; 141 if($errors = $object->errors[$attribute_name]) { 142 return $this->content_tag("div", $prepend_text . (is_array($errors) ? current($errors) : $errors) . $append_text, array('class' => $css_class)); 143 } 144 } 145 146 /** 147 * Returns a string with a div containing all the error messages for the object located as an instance variable by the name 148 * of <tt>object_name</tt>. This div can be tailored by the following options: 149 * 150 * <tt>header_tag</tt> - Used for the header of the error div (default: h2) 151 * <tt>id</tt> - The id of the error div (default: errorExplanation) 152 * <tt>class</tt> - The class of the error div (default: errorExplanation) 153 * @param mixed object_name The name of a PHP class, or 154 * an object instance of that class 155 * @param string[] options Set of options: 'header_tag', 'id', 'class' 156 * @uses content_tag() 157 * @uses object_name 158 * @uses Inflector::humanize() 159 */ 160 function error_messages_for($object_name, $options = array()) { 161 if(is_object($object_name)) { 162 $object_name = get_class($object_name); 163 //echo "object name:".$object_name; 164 } 165 $this->object_name = $object_name; 166 $object = $this->controller_object->$object_name; 167 if(!empty($object->errors)) { 168 $errors = ($num_errors = count($object->errors)) > 1 ? "$num_errors errors" : "$num_errors error"; 169 return $this->content_tag("div", 170 $this->content_tag( 171 (isset($options['header_tag']) ? $options['header_tag'] : "h2"), 172 $errors . 173 " prohibited this " . Inflector::humanize($object_name) . " from being saved" 174 ) . 175 $this->content_tag("p", "There were problems with the following fields:") . 176 $this->content_tag("ul", array_reduce($object->errors, create_function('$v,$w', 'return ($v ? $v : "") . content_tag("li", $w);'), '')), 177 array("id" => (isset($options['id']) ? $options['id'] : "ErrorExplanation"), "class" => (isset($options['class']) ? $options['class'] : "ErrorExplanation")) 178 ); 179 } 180 } 181 182 /** 183 * @todo Document this method 184 * @uses default_input_block() 185 */ 186 function all_input_tags($record, $record_name, $options) { 187 //if($record_name) $this->object_name = $record_name; 188 $input_block = (isset($options['input_block']) ? $options['input_block'] : $this->default_input_block()); 189 $contents = ''; 190 if(is_array($record->content_columns)) { 191 foreach($record->content_columns as $column) { 192 //$contents .= "<p><label for=\"".$record_name."_".$column['name']."\">"; 193 //$contents .= Inflector::humanize($column['name']) . ":</label><br />"; 194 //$contents .= input($record_name, $column['name']) . "</p>\n"; 195 if(!in_array($column['name'], $record->primary_keys)) { 196 eval($input_block) . "\n"; 197 } 198 } 199 } 200 return $contents; 201 } 202 203 /** 204 * @todo Document this method 205 * @uses scaffolding 206 * @uses input_scaffolding() 207 */ 208 function default_input_block() { 209 if($this->scaffolding) { 210 return '$contents .= "<p><label for=\"{$record_name}_{$column[\'name\']}\">" . Inflector::humanize($column[\'name\']) . ":</label><br/>\n<?= " . input_scaffolding($record_name, $column[\'name\']) . " ?></p>\n";'; 211 } else { 212 return '$contents .= "<p><label for=\"{$record_name}_{$column[\'name\']}\">" . Inflector::humanize($column[\'name\']) . ":</label><br/>\n" . input($record_name, $column[\'name\']) . "</p>\n";'; 213 } 214 } 215 216 /** 217 * @todo Document this method 218 * 219 * @param string object_name Name of an ActiveRecord subclass 220 * @param string attribute_name Name of an attribute of $object_name 221 * @param string[] options 222 * @uses attribute_name 223 * @uses column_type() 224 * @uses error_wrapping() 225 * @uses object_name 226 * @uses DateHelper::to_date_select_tag() 227 * @uses FormHelper::to_boolean_select_tag() 228 * @uses FormHelper::to_input_field_tag() 229 * @uses FormHelper::to_text_area_tag() 230 * @uses to_datetime_select_tag() 231 * @uses object() 232 */ 233 function to_tag($object_name, $attribute_name, $options = array()) { 234 $this->object_name = $object_name; 235 $this->attribute_name = $attribute_name; 236 $form = new FormHelper($object_name, $attribute_name); 237 switch($this->column_type()) { 238 case 'string': 239 case 'varchar': 240 case 'varchar2': 241 $field_type = (eregi("password", $this->attribute_name) ? "password" : "text"); 242 $results = $form->to_input_field_tag($field_type, $options); 243 break; 244 245 case 'text': 246 case 'blob': 247 $results = $form->to_text_area_tag($options); 248 break; 249 250 case 'integer': 251 case 'int': 252 case 'number': 253 case 'float': 254 case 'real': 255 $results = $form->to_input_field_tag("text", $options); 256 break; 257 258 case 'date': 259 $form = new DateHelper($object_name, $attribute_name); 260 $results = $form->to_date_select_tag($options); 261 break; 262 263 case 'datetime': 264 case 'timestamp': 265 $results = $this->to_datetime_select_tag($options); 266 break; 267 268 case 'boolean': 269 case 'bool': 270 $results = $form->to_boolean_select_tag($options); 271 break; 272 273 } 274 if(count($this->object()->errors)) { 275 $results = $this->error_wrapping($results, $this->object()->errors[$this->attribute_name]); 276 } 277 return $results; 278 } 279 280 /** 281 * @todo Document this method 282 * 283 * @uses attribute_name 284 * @uses column_type() 285 * @uses error_wrapping 286 * @uses object() 287 * @uses object_name 288 */ 289 function to_scaffold_tag($object_name, $attribute_name, $options = array()) { 290 $this->object_name = $object_name; 291 $this->attribute_name = $attribute_name; 292 switch($this->column_type()) { 293 case 'string': 294 case 'varchar': 295 case 'varchar2': 296 $field_type = (eregi("password", $this->attribute_name) ? "password" : "text"); 297 $results = $field_type."_field(\"$object_name\", \"$attribute_name\")"; 298 break; 299 300 case 'text': 301 case 'blob': 302 $results = "text_area(\"$object_name\", \"$attribute_name\")"; 303 break; 304 305 case 'integer': 306 case 'int': 307 case 'number': 308 case 'float': 309 case 'real': 310 $results = "text_field(\"$object_name\", \"$attribute_name\")"; 311 break; 312 313 case 'date': 314 $results = "date_select(\"$object_name\", \"$attribute_name\")"; 315 break; 316 317 case 'year': 318 $results = "year_select(\"$object_name\", \"$attribute_name\")"; 319 break; 320 321 case 'datetime': 322 case 'timestamp': 323 $results = "datetime_select(\"$object_name\", \"$attribute_name\")"; 324 break; 325 326 case 'time': 327 $results = "time_select(\"$object_name\", \"$attribute_name\")"; 328 break; 329 330 case 'boolean': 331 case 'bool': 332 $results = "boolean_select(\"$object_name\", \"$attribute_name\")"; 333 break; 334 335 default: 336 echo "No case statement for ".$this->column_type()."\n"; 337 } 338 if(count($this->object()->errors)) { 339 $results = $this->error_wrapping($results, 340 $this->object()->errors[$this->attribute_name]); 341 } 342 return $results; 343 } 344 345 /** 346 * @todo Document this method 347 * 348 * @uses tag() 349 */ 350 function tag_without_error_wrapping() { 351 $args = func_get_args(); 352 return call_user_func_array(array(parent, 'tag'), $args); 353 } 354 355 /** 356 * @todo Document this method 357 * 358 * @uses error_wrapping() 359 * @uses object() 360 * @uses tag_without_error_wrapping() 361 */ 362 function tag($name, $options = array()) { 363 if(count($this->object()->errors)) { 364 return $this->error_wrapping($this->tag_without_error_wrapping($name, $options), $this->object()->errors[$this->attribute_name]); 365 } else { 366 return $this->tag_without_error_wrapping($name, $options); 367 } 368 } 369 370 /** 371 * @todo Document this method 372 * 373 * @uses content_tag() 374 */ 375 function content_tag_without_error_wrapping() { 376 $args = func_get_args(); 377 return call_user_func_array('content_tag', $args); 378 } 379 380 /** 381 * @todo Document this method 382 * 383 * @uses object() 384 * @uses error_wrapping() 385 * @uses content_tag_without_error_wrapping() 386 */ 387 function content_tag($name, $value, $options = array()) { 388 if (count($this->object()->errors)) { 389 return $this->error_wrapping( 390 $this->content_tag_without_error_wrapping($name, $value, $options), 391 array_key_exists($this->attribute_name,$this->object()->errors) 392 ? true : false); 393 } else { 394 return $this->content_tag_without_error_wrapping($name, $value, 395 $options); 396 } 397 } 398 399 /** 400 * @todo Document this method 401 * 402 * @uses object_name 403 * @uses attribute_name 404 * @uses DateHelper::to_date_select_tag() 405 */ 406 function to_date_select_tag_without_error_wrapping() { 407 $form = new DateHelper($this->object_name, $this->attribute_name); 408 $args = func_get_args(); 409 return call_user_func_array(array($form, 'to_date_select_tag'), $args); 410 } 411 412 /** 413 * @todo Document this method 414 */ 415 function to_date_select_tag($options = array()) { 416 if (count($this->object()->errors)) { 417 return $this->error_wrapping($this->to_date_select_tag_without_error_wrapping($options), $this->object()->errors[$this->attribute_name]); 418 } else { 419 return $this->to_date_select_tag_without_error_wrapping($options); 420 } 421 } 422 423 /** 424 * @todo Document this method 425 * 426 * @uses attribute_name 427 * @uses object_name 428 * @uses DateHelper::to_datetime_select_tag() 429 */ 430 function to_datetime_select_tag_without_error_wrapping() { 431 $form = new DateHelper($this->object_name, $this->attribute_name); 432 $args = func_get_args(); 433 return call_user_func_array(array($form, 'to_datetime_select_tag'), 434 $args); 435 } 436 437 /** 438 * @todo Document this method 439 * 440 * @uses attribute_name 441 * @uses error_wrapping() 442 * @uses object() 443 * @uses to_datetime_select_tag_without_error_wrapping 444 */ 445 function to_datetime_select_tag($options = array()) { 446 if (count($this->object()->errors)) { 447 return $this->error_wrapping($this->to_datetime_select_tag_without_error_wrapping($options), $this->object()->errors[$this->attribute_name]); 448 } else { 449 return $this->to_datetime_select_tag_without_error_wrapping($options); 450 } 451 } 452 453 /** 454 * @todo Document this method 455 * 456 * @uses attribute_name 457 * @uses object() 458 */ 459 function error_message() { 460 return $this->object()->errors[$this->attribute_name]; 461 } 462 463 /** 464 * @todo Document this method 465 * 466 * @uses attribute_name 467 * @uses object() 468 * @uses ActiveRecord::column_type() 469 */ 470 function column_type() { 471 return $this->object()->column_type($this->attribute_name); 472 } 473 474 /** 475 * Paging html functions 476 * @todo Document this API 477 */ 478 function pagination_limit_select($object_name_or_object, $default_text = "per page:") { 479 480 if(is_object($object_name_or_object)) { 481 $object = $object_name_or_object; 482 } else { 483 $object = $this->object($object_name_or_object); 484 } 485 486 if(!is_object($object)) { 487 return null; 488 } 489 490 if($object->pages > 0) { 491 $html .= " 492 <select name=\"per_page\" onChange=\"document.location = '?".escape_javascript($object->paging_extra_params)."&per_page=' + this.options[this.selectedIndex].value;\"> 493 <option value=\"$object->rows_per_page\" selected>$default_text</option> 494 <option value=10>10</option> 495 <option value=20>20</option> 496 <option value=50>50</option> 497 <option value=100>100</option> 498 <option value=999999999>ALL</option> 499 </select> 500 "; 501 } 502 return $html; 503 } 504 505 /** 506 * @todo Document this API 507 * 508 * @return string HTML to link to previous and next pages 509 * @uses $display 510 * @uses $page 511 * @uses $pages 512 * @uses $paging_extra_params 513 * @uses rows_per_page 514 */ 515 function pagination_links($object_name_or_object) { 516 517 if(is_object($object_name_or_object)) { 518 $object = $object_name_or_object; 519 } else { 520 $object = $this->object($object_name_or_object); 521 } 522 523 if(!is_object($object)) { 524 return null; 525 } 526 527 //$html = "<pre>".print_r($object,1); 528 /* Print the first and previous page links if necessary */ 529 if(($object->page != 1) && ($object->page)) { 530 $html .= link_to("<<", 531 "?$object->paging_extra_params&page=1&per_page=$object->rows_per_page", 532 array( 533 "class" => "pageList", 534 "title" => "First page" 535 ))." "; 536 } 537 if(($object->page-1) > 0) { 538 $html .= link_to("<", 539 "?$object->paging_extra_params&page=".($object->page-1)."&per_page=$object->rows_per_page", 540 array( 541 "class" => "pageList", 542 "title" => "Previous Page" 543 )); 544 } 545 546 if($object->pages < $object->display) { 547 $object->display = $object->pages; 548 } 549 550 if($object->page == $object->pages) { 551 if(($object->pages - $object->display) == 0) { 552 $start = 1; 553 } else { 554 $start = $object->pages - $object->display; 555 } 556 $end = $object->pages; 557 } else { 558 if($object->page >= $object->display) { 559 $start = $object->page - ($object->display / 2); 560 $end = $object->page + (($object->display / 2) - 1); 561 } else { 562 $start = 1; 563 $end = $object->display; 564 } 565 } 566 567 if($end >= $object->pages) { 568 $end = $object->pages; 569 } 570 571 /* Print the numeric page list; make the current page unlinked and bold */ 572 if($end != 1) { 573 for($i=$start; $i<=$end; $i++) { 574 if($i == $object->page) { 575 $html .= "<span class=\"pageList\"><b>".$i."</b></span>"; 576 } else { 577 $html .= link_to($i, 578 "?$object->paging_extra_params&page=$i&per_page=$object->rows_per_page", 579 array( 580 "class" => "pageList", 581 "title" => "Page $i" 582 )); 583 } 584 $html .= " "; 585 } 586 } 587 588 /* Print the Next and Last page links if necessary */ 589 if(($object->page+1) <= $object->pages) { 590 $html .= link_to(">", 591 "?$object->paging_extra_params&page=".($object->page+1)."&per_page=$object->rows_per_page", 592 array( 593 "class" => "pageList", 594 "title" => "Next Page" 595 )); 596 } 597 if(($object->page != $object->pages) && ($object->pages != 0)) { 598 $html .= link_to(">>", 599 "?$object->paging_extra_params&page=".$object->pages."&per_page=$object->rows_per_page", 600 array( 601 "class" => "pageList", 602 "title" => "Last Page" 603 )); 604 } 605 $html .= "\n"; 606 607 return $html; 608 } 609 610 function pagination_range_text($object_name_or_object, $format = "Showing %d - %d of %d items.") { 611 if(is_object($object_name_or_object)) { 612 $object = $object_name_or_object; 613 } else { 614 $object = $this->object($object_name_or_object); 615 } 616 617 if(!is_object($object)) { 618 return null; 619 } 620 $end = $object->rows_per_page * $object->page; 621 $start = $end - ($object->rows_per_page - 1); 622 if($end >= $object->pagination_count) { 623 $end = $object->pagination_count; 624 } 625 626 return $object->pagination_count ? sprintf($format, $start, $end, $object->pagination_count) : null; 627 } 628 629 } 630 631 /** 632 * Avialble functions for use in views 633 * error_message_on($object, $attribute_name, $prepend_text = "", $append_text = "", $css_class = "formError") 634 * @uses ActiveRecordHelper::error_message_on() 635 */ 636 function error_message_on() { 637 $ar_helper = new ActiveRecordHelper(); 638 $args = func_get_args(); 639 return call_user_func_array(array($ar_helper, 'error_message_on'), $args); 640 } 641 642 /** 643 * error_messages_for($object_name, $options = array()) 644 * @uses ActiveRecordHelper::error_messages_for() 645 */ 646 function error_messages_for() { 647 $ar_helper = new ActiveRecordHelper(); 648 $args = func_get_args(); 649 return call_user_func_array(array($ar_helper, 'error_messages_for'), $args); 650 } 651 652 /** 653 * form($record_name, $options = array()) 654 * @uses ActiveRecordHelper::form() 655 */ 656 function form() { 657 $ar_helper = new ActiveRecordHelper(); 658 $args = func_get_args(); 659 return call_user_func_array(array($ar_helper, 'form'), $args); 660 } 661 662 /** 663 * Returns a default input tag for the type of object returned by the method. Example 664 * (title is a VARCHAR column and holds "Hello World"): 665 * input("post", "title") => 666 * <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" /> 667 * @uses ActiveRecordHelper::input() 668 */ 669 function input() { 670 $ar_helper = new ActiveRecordHelper(); 671 $args = func_get_args(); 672 return call_user_func_array(array($ar_helper, 'input'), $args); 673 } 674 675 /** 676 * 677 * @uses ActiveRecordHelper::input_scaffolding() 678 */ 679 function input_scaffolding() { 680 $ar_helper = new ActiveRecordHelper(); 681 $args = func_get_args(); 682 return call_user_func_array(array($ar_helper, 'input_scaffolding'), $args); 683 } 684 685 /** 686 * 687 * @uses ActiveRecordHelper::pagination_limit_select() 688 */ 689 function pagination_limit_select() { 690 $ar_helper = new ActiveRecordHelper(); 691 $args = func_get_args(); 692 return call_user_func_array(array($ar_helper, 'pagination_limit_select'), $args); 693 } 694 695 /** 696 * 697 * @uses ActiveRecordHelper::pagination_links() 698 */ 699 function pagination_links() { 700 $ar_helper = new ActiveRecordHelper(); 701 $args = func_get_args(); 702 return call_user_func_array(array($ar_helper, 'pagination_links'), $args); 703 } 704 705 /** 706 * 707 * @uses ActiveRecordHelper::pagination_range_text() 708 */ 709 function pagination_range_text() { 710 $ar_helper = new ActiveRecordHelper(); 711 $args = func_get_args(); 712 return call_user_func_array(array($ar_helper, 'pagination_range_text'), $args); 713 } 714 715 // -- set Emacs parameters -- 716 // Local variables: 717 // tab-width: 4 718 // c-basic-offset: 4 719 // c-hanging-comment-ender-p: nil 720 // indent-tabs-mode: nil 721 // End: 722 ?>
titre
Description
Corps
titre
Description
Corps
titre
Description
Corps
titre
Corps
Généré le : Sun Feb 25 20:04:38 2007 | par Balluche grâce à PHPXref 0.7 |