* @link https://getkirby.com * @copyright Bastian Allgeier * @license https://getkirby.com/license */ class Content { /** * The raw data array */ protected array $data = []; /** * Cached field objects * Once a field is being fetched * it is added to this array for * later reuse */ protected array $fields = []; /** * A potential parent object. * Not necessarily needed. Especially * for testing, but field methods might * need it. */ protected ModelWithContent|null $parent; /** * Magic getter for content fields */ public function __call(string $name, array $arguments = []): Field { return $this->get($name); } /** * Creates a new Content object * * @param bool $normalize Set to `false` if the input field keys are already lowercase */ public function __construct( array $data = [], ModelWithContent $parent = null, bool $normalize = true ) { if ($normalize === true) { $data = array_change_key_case($data, CASE_LOWER); } $this->data = $data; $this->parent = $parent; } /** * Same as `self::data()` to improve * `var_dump` output * @codeCoverageIgnore * * @see self::data() */ public function __debugInfo(): array { return $this->toArray(); } /** * Converts the content to a new blueprint */ public function convertTo(string $to): array { // prepare data $data = []; $content = $this; // blueprints $old = $this->parent->blueprint(); $subfolder = dirname($old->name()); $new = Blueprint::factory( $subfolder . '/' . $to, $subfolder . '/default', $this->parent ); // forms $oldForm = new Form([ 'fields' => $old->fields(), 'model' => $this->parent ]); $newForm = new Form([ 'fields' => $new->fields(), 'model' => $this->parent ]); // fields $oldFields = $oldForm->fields(); $newFields = $newForm->fields(); // go through all fields of new template foreach ($newFields as $newField) { $name = $newField->name(); $oldField = $oldFields->get($name); // field name and type matches with old template if ($oldField?->type() === $newField->type()) { $data[$name] = $content->get($name)->value(); } else { $data[$name] = $newField->default(); } } // preserve existing fields return array_merge($this->data, $data); } /** * Returns the raw data array */ public function data(): array { return $this->data; } /** * Returns all registered field objects */ public function fields(): array { foreach ($this->data as $key => $value) { $this->get($key); } return $this->fields; } /** * Returns either a single field object * or all registered fields */ public function get(string $key = null): Field|array { if ($key === null) { return $this->fields(); } $key = strtolower($key); return $this->fields[$key] ??= new Field( $this->parent, $key, $this->data()[$key] ?? null ); } /** * Checks if a content field is set */ public function has(string $key): bool { return isset($this->data[strtolower($key)]) === true; } /** * Returns all field keys */ public function keys(): array { return array_keys($this->data()); } /** * Returns a clone of the content object * without the fields, specified by the * passed key(s) */ public function not(string ...$keys): static { $copy = clone $this; $copy->fields = []; foreach ($keys as $key) { unset($copy->data[strtolower($key)]); } return $copy; } /** * Returns the parent * Site, Page, File or User object */ public function parent(): ModelWithContent|null { return $this->parent; } /** * Set the parent model * * @return $this */ public function setParent(ModelWithContent $parent): static { $this->parent = $parent; return $this; } /** * Returns the raw data array * * @see self::data() */ public function toArray(): array { return $this->data(); } /** * Updates the content and returns * a cloned object * * @return $this */ public function update( array $content = null, bool $overwrite = false ): static { $content = array_change_key_case((array)$content, CASE_LOWER); $this->data = $overwrite === true ? $content : array_merge($this->data, $content); // clear cache of Field objects $this->fields = []; return $this; } }