annotate core/lib/Drupal/Core/Field/FieldConfigBase.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\Core\Field;
Chris@0 4
Chris@0 5 use Drupal\Core\Config\Entity\ConfigEntityBase;
Chris@0 6 use Drupal\Core\Entity\EntityStorageInterface;
Chris@0 7 use Drupal\Core\Entity\FieldableEntityInterface;
Chris@0 8 use Drupal\Core\Field\TypedData\FieldItemDataDefinition;
Chris@0 9
Chris@0 10 /**
Chris@0 11 * Base class for configurable field definitions.
Chris@0 12 */
Chris@0 13 abstract class FieldConfigBase extends ConfigEntityBase implements FieldConfigInterface {
Chris@0 14
Chris@17 15 use FieldInputValueNormalizerTrait;
Chris@17 16
Chris@0 17 /**
Chris@0 18 * The field ID.
Chris@0 19 *
Chris@0 20 * The ID consists of 3 parts: the entity type, bundle and the field name.
Chris@0 21 *
Chris@0 22 * Example: node.article.body, user.user.field_main_image.
Chris@0 23 *
Chris@0 24 * @var string
Chris@0 25 */
Chris@0 26 protected $id;
Chris@0 27
Chris@0 28 /**
Chris@0 29 * The field name.
Chris@0 30 *
Chris@0 31 * @var string
Chris@0 32 */
Chris@0 33 protected $field_name;
Chris@0 34
Chris@0 35 /**
Chris@0 36 * The field type.
Chris@0 37 *
Chris@0 38 * This property is denormalized from the field storage for optimization of
Chris@0 39 * the "entity and render cache hits" critical paths. If not present in the
Chris@0 40 * $values passed to create(), it is populated from the field storage in
Chris@0 41 * postCreate(), and saved in config records so that it is present on
Chris@0 42 * subsequent loads.
Chris@0 43 *
Chris@0 44 * @var string
Chris@0 45 */
Chris@0 46 protected $field_type;
Chris@0 47
Chris@0 48 /**
Chris@0 49 * The name of the entity type the field is attached to.
Chris@0 50 *
Chris@0 51 * @var string
Chris@0 52 */
Chris@0 53 protected $entity_type;
Chris@0 54
Chris@0 55 /**
Chris@0 56 * The name of the bundle the field is attached to.
Chris@0 57 *
Chris@0 58 * @var string
Chris@0 59 */
Chris@0 60 protected $bundle;
Chris@0 61
Chris@0 62 /**
Chris@0 63 * The human-readable label for the field.
Chris@0 64 *
Chris@0 65 * This will be used as the title of Form API elements for the field in entity
Chris@0 66 * edit forms, or as the label for the field values in displayed entities.
Chris@0 67 *
Chris@0 68 * If not specified, this defaults to the field_name (mostly useful for fields
Chris@0 69 * created in tests).
Chris@0 70 *
Chris@0 71 * @var string
Chris@0 72 */
Chris@0 73 protected $label;
Chris@0 74
Chris@0 75 /**
Chris@0 76 * The field description.
Chris@0 77 *
Chris@0 78 * A human-readable description for the field when used with this bundle.
Chris@0 79 * For example, the description will be the help text of Form API elements for
Chris@0 80 * this field in entity edit forms.
Chris@0 81 *
Chris@0 82 * @var string
Chris@0 83 */
Chris@0 84 protected $description = '';
Chris@0 85
Chris@0 86 /**
Chris@0 87 * Field-type specific settings.
Chris@0 88 *
Chris@0 89 * An array of key/value pairs. The keys and default values are defined by the
Chris@0 90 * field type.
Chris@0 91 *
Chris@0 92 * @var array
Chris@0 93 */
Chris@0 94 protected $settings = [];
Chris@0 95
Chris@0 96 /**
Chris@0 97 * Flag indicating whether the field is required.
Chris@0 98 *
Chris@0 99 * TRUE if a value for this field is required when used with this bundle,
Chris@0 100 * FALSE otherwise. Currently, required-ness is only enforced at the Form API
Chris@0 101 * level in entity edit forms, not during direct API saves.
Chris@0 102 *
Chris@0 103 * @var bool
Chris@0 104 */
Chris@0 105 protected $required = FALSE;
Chris@0 106
Chris@0 107 /**
Chris@0 108 * Flag indicating whether the field is translatable.
Chris@0 109 *
Chris@0 110 * Defaults to TRUE.
Chris@0 111 *
Chris@0 112 * @var bool
Chris@0 113 */
Chris@0 114 protected $translatable = TRUE;
Chris@0 115
Chris@0 116 /**
Chris@0 117 * Default field value.
Chris@0 118 *
Chris@0 119 * The default value is used when an entity is created, either:
Chris@0 120 * - through an entity creation form; the form elements for the field are
Chris@0 121 * prepopulated with the default value.
Chris@0 122 * - through direct API calls (i.e. $entity->save()); the default value is
Chris@0 123 * added if the $entity object provides no explicit entry (actual values or
Chris@0 124 * "the field is empty") for the field.
Chris@0 125 *
Chris@0 126 * The default value is expressed as a numerically indexed array of items,
Chris@0 127 * each item being an array of key/value pairs matching the set of 'columns'
Chris@0 128 * defined by the "field schema" for the field type, as exposed in
Chris@0 129 * hook_field_schema(). If the number of items exceeds the cardinality of the
Chris@0 130 * field, extraneous items will be ignored.
Chris@0 131 *
Chris@0 132 * This property is overlooked if the $default_value_callback is non-empty.
Chris@0 133 *
Chris@0 134 * Example for a integer field:
Chris@0 135 * @code
Chris@0 136 * array(
Chris@0 137 * array('value' => 1),
Chris@0 138 * array('value' => 2),
Chris@0 139 * )
Chris@0 140 * @endcode
Chris@0 141 *
Chris@0 142 * @var array
Chris@0 143 */
Chris@0 144 protected $default_value = [];
Chris@0 145
Chris@0 146 /**
Chris@0 147 * The name of a callback function that returns default values.
Chris@0 148 *
Chris@0 149 * The function will be called with the following arguments:
Chris@0 150 * - \Drupal\Core\Entity\FieldableEntityInterface $entity
Chris@0 151 * The entity being created.
Chris@0 152 * - \Drupal\Core\Field\FieldDefinitionInterface $definition
Chris@0 153 * The field definition.
Chris@0 154 * It should return an array of default values, in the same format as the
Chris@0 155 * $default_value property.
Chris@0 156 *
Chris@0 157 * This property takes precedence on the list of fixed values specified in the
Chris@0 158 * $default_value property.
Chris@0 159 *
Chris@0 160 * @var string
Chris@0 161 */
Chris@0 162 protected $default_value_callback = '';
Chris@0 163
Chris@0 164 /**
Chris@0 165 * The field storage object.
Chris@0 166 *
Chris@0 167 * @var \Drupal\Core\Field\FieldStorageDefinitionInterface
Chris@0 168 */
Chris@0 169 protected $fieldStorage;
Chris@0 170
Chris@0 171 /**
Chris@0 172 * The data definition of a field item.
Chris@0 173 *
Chris@0 174 * @var \Drupal\Core\Field\TypedData\FieldItemDataDefinition
Chris@0 175 */
Chris@0 176 protected $itemDefinition;
Chris@0 177
Chris@0 178 /**
Chris@0 179 * Array of constraint options keyed by constraint plugin ID.
Chris@0 180 *
Chris@0 181 * @var array
Chris@0 182 */
Chris@0 183 protected $constraints = [];
Chris@0 184
Chris@0 185 /**
Chris@0 186 * Array of property constraint options keyed by property ID. The values are
Chris@0 187 * associative array of constraint options keyed by constraint plugin ID.
Chris@0 188 *
Chris@0 189 * @var array[]
Chris@0 190 */
Chris@0 191 protected $propertyConstraints = [];
Chris@0 192
Chris@0 193 /**
Chris@0 194 * {@inheritdoc}
Chris@0 195 */
Chris@0 196 public function id() {
Chris@0 197 return $this->entity_type . '.' . $this->bundle . '.' . $this->field_name;
Chris@0 198 }
Chris@0 199
Chris@0 200 /**
Chris@0 201 * {@inheritdoc}
Chris@0 202 */
Chris@0 203 public function getName() {
Chris@0 204 return $this->field_name;
Chris@0 205 }
Chris@0 206
Chris@0 207 /**
Chris@0 208 * {@inheritdoc}
Chris@0 209 */
Chris@0 210 public function getType() {
Chris@0 211 return $this->field_type;
Chris@0 212 }
Chris@0 213
Chris@0 214 /**
Chris@0 215 * {@inheritdoc}
Chris@0 216 */
Chris@0 217 public function getTargetEntityTypeId() {
Chris@0 218 return $this->entity_type;
Chris@0 219 }
Chris@0 220
Chris@0 221 /**
Chris@0 222 * {@inheritdoc}
Chris@0 223 */
Chris@0 224 public function getTargetBundle() {
Chris@0 225 return $this->bundle;
Chris@0 226 }
Chris@0 227
Chris@0 228 /**
Chris@0 229 * {@inheritdoc}
Chris@0 230 */
Chris@0 231 public function calculateDependencies() {
Chris@0 232 parent::calculateDependencies();
Chris@0 233 // Add dependencies from the field type plugin. We can not use
Chris@0 234 // self::calculatePluginDependencies() because instantiation of a field item
Chris@0 235 // plugin requires a parent entity.
Chris@0 236 /** @var $field_type_manager \Drupal\Core\Field\FieldTypePluginManagerInterface */
Chris@0 237 $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
Chris@0 238 $definition = $field_type_manager->getDefinition($this->getType());
Chris@0 239 $this->addDependency('module', $definition['provider']);
Chris@0 240 // Plugins can declare additional dependencies in their definition.
Chris@0 241 if (isset($definition['config_dependencies'])) {
Chris@0 242 $this->addDependencies($definition['config_dependencies']);
Chris@0 243 }
Chris@0 244 // Let the field type plugin specify its own dependencies.
Chris@0 245 // @see \Drupal\Core\Field\FieldItemInterface::calculateDependencies()
Chris@0 246 $this->addDependencies($definition['class']::calculateDependencies($this));
Chris@0 247
Chris@0 248 // Create dependency on the bundle.
Chris@18 249 $bundle_config_dependency = $this->entityTypeManager()->getDefinition($this->entity_type)->getBundleConfigDependency($this->bundle);
Chris@0 250 $this->addDependency($bundle_config_dependency['type'], $bundle_config_dependency['name']);
Chris@0 251
Chris@0 252 return $this;
Chris@0 253 }
Chris@0 254
Chris@0 255 /**
Chris@0 256 * {@inheritdoc}
Chris@0 257 */
Chris@0 258 public function onDependencyRemoval(array $dependencies) {
Chris@0 259 $changed = parent::onDependencyRemoval($dependencies);
Chris@0 260 $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
Chris@0 261 $definition = $field_type_manager->getDefinition($this->getType());
Chris@0 262 if ($definition['class']::onDependencyRemoval($this, $dependencies)) {
Chris@0 263 $changed = TRUE;
Chris@0 264 }
Chris@0 265 return $changed;
Chris@0 266 }
Chris@0 267
Chris@0 268 /**
Chris@0 269 * {@inheritdoc}
Chris@0 270 */
Chris@0 271 public function postCreate(EntityStorageInterface $storage) {
Chris@0 272 parent::postCreate($storage);
Chris@0 273 // If it was not present in the $values passed to create(), (e.g. for
Chris@0 274 // programmatic creation), populate the denormalized field_type property
Chris@0 275 // from the field storage, so that it gets saved in the config record.
Chris@0 276 if (empty($this->field_type)) {
Chris@0 277 $this->field_type = $this->getFieldStorageDefinition()->getType();
Chris@0 278 }
Chris@0 279 }
Chris@0 280
Chris@0 281 /**
Chris@0 282 * {@inheritdoc}
Chris@0 283 */
Chris@0 284 public function postSave(EntityStorageInterface $storage, $update = TRUE) {
Chris@0 285 // Clear the cache.
Chris@18 286 \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
Chris@0 287
Chris@0 288 // Invalidate the render cache for all affected entities.
Chris@0 289 $entity_type = $this->getFieldStorageDefinition()->getTargetEntityTypeId();
Chris@18 290 if ($this->entityTypeManager()->hasHandler($entity_type, 'view_builder')) {
Chris@18 291 $this->entityTypeManager()->getViewBuilder($entity_type)->resetCache();
Chris@0 292 }
Chris@0 293 }
Chris@0 294
Chris@0 295 /**
Chris@0 296 * {@inheritdoc}
Chris@0 297 */
Chris@0 298 public function getLabel() {
Chris@0 299 return $this->label();
Chris@0 300 }
Chris@0 301
Chris@0 302 /**
Chris@0 303 * {@inheritdoc}
Chris@0 304 */
Chris@0 305 public function setLabel($label) {
Chris@0 306 $this->label = $label;
Chris@0 307 return $this;
Chris@0 308 }
Chris@0 309
Chris@0 310 /**
Chris@0 311 * {@inheritdoc}
Chris@0 312 */
Chris@0 313 public function getDescription() {
Chris@0 314 return $this->description;
Chris@0 315 }
Chris@0 316
Chris@0 317 /**
Chris@0 318 * {@inheritdoc}
Chris@0 319 */
Chris@0 320 public function setDescription($description) {
Chris@0 321 $this->description = $description;
Chris@0 322 return $this;
Chris@0 323 }
Chris@0 324
Chris@0 325 /**
Chris@0 326 * {@inheritdoc}
Chris@0 327 */
Chris@0 328 public function isTranslatable() {
Chris@0 329 // A field can be enabled for translation only if translation is supported.
Chris@0 330 return $this->translatable && $this->getFieldStorageDefinition()->isTranslatable();
Chris@0 331 }
Chris@0 332
Chris@0 333 /**
Chris@0 334 * {@inheritdoc}
Chris@0 335 */
Chris@0 336 public function setTranslatable($translatable) {
Chris@0 337 $this->translatable = $translatable;
Chris@0 338 return $this;
Chris@0 339 }
Chris@0 340
Chris@0 341 /**
Chris@0 342 * {@inheritdoc}
Chris@0 343 */
Chris@0 344 public function getSettings() {
Chris@0 345 return $this->settings + $this->getFieldStorageDefinition()->getSettings();
Chris@0 346 }
Chris@0 347
Chris@0 348 /**
Chris@0 349 * {@inheritdoc}
Chris@0 350 */
Chris@0 351 public function setSettings(array $settings) {
Chris@0 352 $this->settings = $settings + $this->settings;
Chris@0 353 return $this;
Chris@0 354 }
Chris@0 355
Chris@0 356 /**
Chris@0 357 * {@inheritdoc}
Chris@0 358 */
Chris@0 359 public function getSetting($setting_name) {
Chris@0 360 if (array_key_exists($setting_name, $this->settings)) {
Chris@0 361 return $this->settings[$setting_name];
Chris@0 362 }
Chris@0 363 else {
Chris@0 364 return $this->getFieldStorageDefinition()->getSetting($setting_name);
Chris@0 365 }
Chris@0 366 }
Chris@0 367
Chris@0 368 /**
Chris@0 369 * {@inheritdoc}
Chris@0 370 */
Chris@0 371 public function setSetting($setting_name, $value) {
Chris@0 372 $this->settings[$setting_name] = $value;
Chris@0 373 return $this;
Chris@0 374 }
Chris@0 375
Chris@0 376 /**
Chris@0 377 * {@inheritdoc}
Chris@0 378 */
Chris@0 379 public function isRequired() {
Chris@0 380 return $this->required;
Chris@0 381 }
Chris@0 382
Chris@0 383 /**
Chris@0 384 * [@inheritdoc}
Chris@0 385 */
Chris@0 386 public function setRequired($required) {
Chris@0 387 $this->required = $required;
Chris@0 388 return $this;
Chris@0 389 }
Chris@0 390
Chris@0 391 /**
Chris@0 392 * {@inheritdoc}
Chris@0 393 */
Chris@0 394 public function getDefaultValue(FieldableEntityInterface $entity) {
Chris@0 395 // Allow custom default values function.
Chris@0 396 if ($callback = $this->getDefaultValueCallback()) {
Chris@0 397 $value = call_user_func($callback, $entity, $this);
Chris@17 398 $value = $this->normalizeValue($value, $this->getFieldStorageDefinition()->getMainPropertyName());
Chris@0 399 }
Chris@0 400 else {
Chris@0 401 $value = $this->getDefaultValueLiteral();
Chris@0 402 }
Chris@0 403 // Allow the field type to process default values.
Chris@0 404 $field_item_list_class = $this->getClass();
Chris@0 405 return $field_item_list_class::processDefaultValue($value, $entity, $this);
Chris@0 406 }
Chris@0 407
Chris@0 408 /**
Chris@0 409 * {@inheritdoc}
Chris@0 410 */
Chris@0 411 public function getDefaultValueLiteral() {
Chris@0 412 return $this->default_value;
Chris@0 413 }
Chris@0 414
Chris@0 415 /**
Chris@0 416 * {@inheritdoc}
Chris@0 417 */
Chris@0 418 public function setDefaultValue($value) {
Chris@17 419 $this->default_value = $this->normalizeValue($value, $this->getFieldStorageDefinition()->getMainPropertyName());
Chris@0 420 return $this;
Chris@0 421 }
Chris@0 422
Chris@0 423 /**
Chris@0 424 * {@inheritdoc}
Chris@0 425 */
Chris@0 426 public function getDefaultValueCallback() {
Chris@0 427 return $this->default_value_callback;
Chris@0 428 }
Chris@0 429
Chris@0 430 /**
Chris@0 431 * {@inheritdoc}
Chris@0 432 */
Chris@0 433 public function setDefaultValueCallback($callback) {
Chris@0 434 $this->default_value_callback = $callback;
Chris@0 435 return $this;
Chris@0 436 }
Chris@0 437
Chris@0 438 /**
Chris@0 439 * Implements the magic __sleep() method.
Chris@0 440 *
Chris@0 441 * Using the Serialize interface and serialize() / unserialize() methods
Chris@0 442 * breaks entity forms in PHP 5.4.
Chris@0 443 * @todo Investigate in https://www.drupal.org/node/2074253.
Chris@0 444 */
Chris@0 445 public function __sleep() {
Chris@0 446 // Only serialize necessary properties, excluding those that can be
Chris@0 447 // recalculated.
Chris@0 448 $properties = get_object_vars($this);
Chris@0 449 unset($properties['fieldStorage'], $properties['itemDefinition'], $properties['original']);
Chris@0 450 return array_keys($properties);
Chris@0 451 }
Chris@0 452
Chris@0 453 /**
Chris@0 454 * {@inheritdoc}
Chris@0 455 */
Chris@0 456 public static function createFromItemType($item_type) {
Chris@0 457 // Forward to the field definition class for creating new data definitions
Chris@0 458 // via the typed manager.
Chris@0 459 return BaseFieldDefinition::createFromItemType($item_type);
Chris@0 460 }
Chris@0 461
Chris@0 462 /**
Chris@0 463 * {@inheritdoc}
Chris@0 464 */
Chris@0 465 public static function createFromDataType($type) {
Chris@0 466 // Forward to the field definition class for creating new data definitions
Chris@0 467 // via the typed manager.
Chris@0 468 return BaseFieldDefinition::createFromDataType($type);
Chris@0 469 }
Chris@0 470
Chris@0 471 /**
Chris@0 472 * {@inheritdoc}
Chris@0 473 */
Chris@0 474 public function getDataType() {
Chris@0 475 return 'list';
Chris@0 476 }
Chris@0 477
Chris@0 478 /**
Chris@0 479 * {@inheritdoc}
Chris@0 480 */
Chris@0 481 public function isList() {
Chris@0 482 return TRUE;
Chris@0 483 }
Chris@0 484
Chris@0 485 /**
Chris@0 486 * {@inheritdoc}
Chris@0 487 */
Chris@0 488 public function getClass() {
Chris@0 489 // Derive list class from the field type.
Chris@0 490 $type_definition = \Drupal::service('plugin.manager.field.field_type')
Chris@0 491 ->getDefinition($this->getType());
Chris@0 492 return $type_definition['list_class'];
Chris@0 493 }
Chris@0 494
Chris@0 495 /**
Chris@0 496 * {@inheritdoc}
Chris@0 497 */
Chris@0 498 public function getConstraints() {
Chris@0 499 return \Drupal::typedDataManager()->getDefaultConstraints($this) + $this->constraints;
Chris@0 500 }
Chris@0 501
Chris@0 502 /**
Chris@0 503 * {@inheritdoc}
Chris@0 504 */
Chris@0 505 public function getConstraint($constraint_name) {
Chris@0 506 $constraints = $this->getConstraints();
Chris@0 507 return isset($constraints[$constraint_name]) ? $constraints[$constraint_name] : NULL;
Chris@0 508 }
Chris@0 509
Chris@0 510 /**
Chris@0 511 * {@inheritdoc}
Chris@0 512 */
Chris@0 513 public function getItemDefinition() {
Chris@0 514 if (!isset($this->itemDefinition)) {
Chris@0 515 $this->itemDefinition = FieldItemDataDefinition::create($this)
Chris@0 516 ->setSettings($this->getSettings());
Chris@0 517
Chris@0 518 // Add any custom property constraints, overwriting as required.
Chris@0 519 $item_constraints = $this->itemDefinition->getConstraint('ComplexData') ?: [];
Chris@0 520 foreach ($this->propertyConstraints as $name => $constraints) {
Chris@0 521 if (isset($item_constraints[$name])) {
Chris@0 522 $item_constraints[$name] = $constraints + $item_constraints[$name];
Chris@0 523 }
Chris@0 524 else {
Chris@0 525 $item_constraints[$name] = $constraints;
Chris@0 526 }
Chris@0 527 $this->itemDefinition->addConstraint('ComplexData', $item_constraints);
Chris@0 528 }
Chris@0 529 }
Chris@0 530
Chris@0 531 return $this->itemDefinition;
Chris@0 532 }
Chris@0 533
Chris@0 534 /**
Chris@0 535 * {@inheritdoc}
Chris@0 536 */
Chris@0 537 public function getConfig($bundle) {
Chris@0 538 return $this;
Chris@0 539 }
Chris@0 540
Chris@0 541 /**
Chris@0 542 * {@inheritdoc}
Chris@0 543 */
Chris@0 544 public function setConstraints(array $constraints) {
Chris@0 545 $this->constraints = $constraints;
Chris@0 546 return $this;
Chris@0 547 }
Chris@0 548
Chris@0 549 /**
Chris@0 550 * {@inheritdoc}
Chris@0 551 */
Chris@0 552 public function addConstraint($constraint_name, $options = NULL) {
Chris@0 553 $this->constraints[$constraint_name] = $options;
Chris@0 554 return $this;
Chris@0 555 }
Chris@0 556
Chris@0 557 /**
Chris@0 558 * {@inheritdoc}
Chris@0 559 */
Chris@0 560 public function setPropertyConstraints($name, array $constraints) {
Chris@0 561 $this->propertyConstraints[$name] = $constraints;
Chris@0 562
Chris@0 563 // Reset the field item definition so the next time it is instantiated it
Chris@0 564 // will receive the new constraints.
Chris@0 565 $this->itemDefinition = NULL;
Chris@0 566
Chris@0 567 return $this;
Chris@0 568 }
Chris@0 569
Chris@0 570 /**
Chris@0 571 * {@inheritdoc}
Chris@0 572 */
Chris@0 573 public function addPropertyConstraints($name, array $constraints) {
Chris@0 574 foreach ($constraints as $constraint_name => $options) {
Chris@0 575 $this->propertyConstraints[$name][$constraint_name] = $options;
Chris@0 576 }
Chris@0 577
Chris@0 578 // Reset the field item definition so the next time it is instantiated it
Chris@0 579 // will receive the new constraints.
Chris@0 580 $this->itemDefinition = NULL;
Chris@0 581
Chris@0 582 return $this;
Chris@0 583 }
Chris@0 584
Chris@14 585 /**
Chris@14 586 * {@inheritdoc}
Chris@14 587 */
Chris@14 588 public function isInternal() {
Chris@14 589 // Respect the definition, otherwise default to TRUE for computed fields.
Chris@14 590 if (isset($this->definition['internal'])) {
Chris@14 591 return $this->definition['internal'];
Chris@14 592 }
Chris@14 593 return $this->isComputed();
Chris@14 594 }
Chris@14 595
Chris@0 596 }