Source of file VirtualPage.php
Size: 15,968 Bytes - Last Modified: 2021-12-23T10:28:18+00:00
/var/www/docs.ssmods.com/process/src/code/Model/VirtualPage.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549 | <?php namespace SilverStripe\CMS\Model; use Page; use SilverStripe\Core\Convert; use SilverStripe\Dev\Deprecation; use SilverStripe\Forms\FieldList; use SilverStripe\Forms\LiteralField; use SilverStripe\Forms\ReadonlyTransformation; use SilverStripe\Forms\TreeDropdownField; use SilverStripe\ORM\DataObject; use SilverStripe\ORM\ValidationResult; use SilverStripe\Security\Member; use SilverStripe\Versioned\Versioned; use SilverStripe\View\HTML; /** * Virtual Page creates an instance of a page, with the same fields that the original page had, but readonly. * This allows you can have a page in mulitple places in the site structure, with different children without * duplicating the content. * * Note: This Only duplicates $db fields and not the $has_one etc.. * * @method SiteTree CopyContentFrom() * @property int $CopyContentFromID */ class VirtualPage extends Page { private static $description = 'Displays the content of another page'; private static $icon_class = 'font-icon-p-virtual'; public static $virtualFields; /** * @var array Define fields that are not virtual - the virtual page must define these fields themselves. * Note that anything in {@link self::config()->initially_copied_fields} is implicitly included in this list. */ private static $non_virtual_fields = [ "ID", "ClassName", "ObsoleteClassName", "SecurityTypeID", "OwnerID", "ParentID", "URLSegment", "Sort", "Status", 'ShowInMenus', // 'Locale' 'ShowInSearch', 'Version', "Embargo", "Expiry", "CanViewType", "CanEditType", "CopyContentFromID", "HasBrokenLink", ]; /** * @var array Define fields that are initially copied to virtual pages but left modifiable after that. */ private static $initially_copied_fields = [ 'ShowInMenus', 'ShowInSearch', 'URLSegment', ]; private static $has_one = [ "CopyContentFrom" => SiteTree::class, ]; private static $owns = [ "CopyContentFrom", ]; private static $db = [ "VersionID" => "Int", ]; private static $table_name = 'VirtualPage'; /** * Generates the array of fields required for the page type. * * @return array */ public function getVirtualFields() { // Check if copied page exists $record = $this->CopyContentFrom(); if (!$record || !$record->exists()) { return []; } // Diff db with non-virtual fields $fields = array_keys(static::getSchema()->fieldSpecs($record)); $nonVirtualFields = $this->getNonVirtualisedFields(); return array_diff($fields, $nonVirtualFields); } /** * List of fields or properties to never virtualise * * @return array */ public function getNonVirtualisedFields() { $config = self::config(); return array_merge( $config->get('non_virtual_fields'), $config->get('initially_copied_fields') ); } public function setCopyContentFromID($val) { // Sanity check to prevent pages virtualising other virtual pages if ($val && DataObject::get_by_id(SiteTree::class, $val) instanceof VirtualPage) { $val = 0; } return $this->setField("CopyContentFromID", $val); } public function ContentSource() { $copied = $this->CopyContentFrom(); if ($copied && $copied->exists()) { return $copied; } return $this; } /** * @return array */ public function MetaComponents() { $tags = parent::MetaComponents(); $copied = $this->CopyContentFrom(); if ($copied && $copied->exists()) { $tags['canonical'] = [ 'tag' => 'link', 'attributes' => [ 'rel' => 'canonical', 'href' => $copied->AbsoluteLink(), ], ]; } return $tags; } public function allowedChildren() { $copy = $this->CopyContentFrom(); if ($copy && $copy->exists()) { return $copy->allowedChildren(); } return []; } public function syncLinkTracking() { if ($this->CopyContentFromID) { $this->HasBrokenLink = Versioned::get_by_stage(SiteTree::class, Versioned::DRAFT) ->filter('ID', $this->CopyContentFromID) ->count() === 0; } else { $this->HasBrokenLink = true; } } /** * We can only publish the page if there is a published source page * * @param Member $member Member to check * @return bool */ public function canPublish($member = null) { return $this->isPublishable() && parent::canPublish($member); } /** * Returns true if is page is publishable by anyone at all * Return false if the source page isn't published yet. * * Note that isPublishable doesn't affect ete from live, only publish. */ public function isPublishable() { // No source if (!$this->CopyContentFrom() || !$this->CopyContentFrom()->ID) { return false; } // Unpublished source if (!Versioned::get_versionnumber_by_stage( SiteTree::class, 'Live', $this->CopyContentFromID )) { return false; } // Default - publishable return true; } /** * Generate the CMS fields from the fields from the original page. */ public function getCMSFields() { $this->beforeUpdateCMSFields(function (FieldList $fields) { // Setup the linking to the original page. $copyContentFromField = TreeDropdownField::create( 'CopyContentFromID', _t(self::class . '.CHOOSE', "Linked Page"), SiteTree::class ); // Setup virtual fields if ($virtualFields = $this->getVirtualFields()) { $roTransformation = new ReadonlyTransformation(); foreach ($virtualFields as $virtualField) { if ($fields->dataFieldByName($virtualField)) { $fields->replaceField( $virtualField, $fields->dataFieldByName($virtualField)->transform($roTransformation) ); } } } $msgs = []; $fields->addFieldToTab('Root.Main', $copyContentFromField, 'Title'); // Create links back to the original object in the CMS if ($this->CopyContentFrom()->exists()) { $link = HTML::createTag( 'a', [ 'class' => 'cmsEditlink', 'href' => 'admin/pages/edit/show/' . $this->CopyContentFromID, ], _t(self::class . '.EditLink', 'edit') ); $msgs[] = _t( self::class . '.HEADERWITHLINK', "This is a virtual page copying content from \"{title}\" ({link})", [ 'title' => $this->CopyContentFrom()->obj('Title'), 'link' => $link, ] ); } else { $msgs[] = _t(self::class . '.HEADER', "This is a virtual page"); $msgs[] = _t( 'SilverStripe\\CMS\\Model\\SiteTree.VIRTUALPAGEWARNING', 'Please choose a linked page and save first in order to publish this page' ); } if ($this->CopyContentFromID && !Versioned::get_versionnumber_by_stage( SiteTree::class, Versioned::LIVE, $this->CopyContentFromID )) { $msgs[] = _t( 'SilverStripe\\CMS\\Model\\SiteTree.VIRTUALPAGEDRAFTWARNING', 'Please publish the linked page in order to publish the virtual page' ); } $fields->addFieldToTab("Root.Main", new LiteralField( 'VirtualPageMessage', '<div class="alert alert-info">' . implode('. ', $msgs) . '.</div>' ), 'CopyContentFromID'); }); return parent::getCMSFields(); } public function onBeforeWrite() { $this->refreshFromCopied(); parent::onBeforeWrite(); } /** * Copy any fields from the copied record to bootstrap /backup */ protected function refreshFromCopied() { // Skip if copied record isn't available $source = $this->CopyContentFrom(); if (!$source || !$source->exists()) { return; } // We also want to copy certain, but only if we're copying the source page for the first // time. After this point, the user is free to customise these for the virtual page themselves. if ($this->isChanged('CopyContentFromID', 2) && $this->CopyContentFromID) { foreach (self::config()->get('initially_copied_fields') as $fieldName) { $this->$fieldName = $source->$fieldName; } } // Copy fields to the original record in case the class type changes foreach ($this->getVirtualFields() as $virtualField) { $this->$virtualField = $source->$virtualField; } } public function getSettingsFields() { $fields = parent::getSettingsFields(); if (!$this->CopyContentFrom()->exists()) { $fields->addFieldToTab( "Root.Settings", new LiteralField( 'VirtualPageWarning', '<div class="message notice">' . _t( 'SilverStripe\\CMS\\Model\\SiteTree.VIRTUALPAGEWARNINGSETTINGS', 'Please choose a linked page in the main content fields in order to publish' ) . '</div>' ), 'ClassName' ); } return $fields; } public function validate() { $result = parent::validate(); // "Can be root" validation $orig = $this->CopyContentFrom(); if ($orig && $orig->exists() && !$orig->config()->get('can_be_root') && !$this->ParentID) { $result->addError( _t( self::class . '.PageTypNotAllowedOnRoot', 'Original page type "{type}" is not allowed on the root level for this virtual page', ['type' => $orig->i18n_singular_name()] ), ValidationResult::TYPE_ERROR, 'CAN_BE_ROOT_VIRTUAL' ); } return $result; } /** * @deprecated 4.2..5.0 */ public function updateImageTracking() { Deprecation::notice('5.0', 'This will be removed in 5.0'); // Doesn't work on unsaved records if (!$this->isInDB()) { return; } // Remove CopyContentFrom() from the cache unset($this->components['CopyContentFrom']); // Update ImageTracking $copyContentFrom = $this->CopyContentFrom(); if (!$copyContentFrom || !$copyContentFrom->isInDB()) { return; } $this->FileTracking()->setByIDList($copyContentFrom->FileTracking()->column('ID')); } public function CMSTreeClasses() { $parentClass = sprintf( ' VirtualPage-%s', Convert::raw2htmlid($this->CopyContentFrom()->ClassName) ); return parent::CMSTreeClasses() . $parentClass; } /** * Use the target page's class name for fetching templates - as we need to take on its appearance * * @param string $suffix * @return array */ public function getViewerTemplates($suffix = '') { $copy = $this->CopyContentFrom(); if ($copy && $copy->exists()) { return $copy->getViewerTemplates($suffix); } return parent::getViewerTemplates($suffix); } /** * Allow attributes on the master page to pass * through to the virtual page * * @param string $field * @return mixed */ public function __get($field) { if (parent::hasMethod($funcName = "get$field")) { return $this->$funcName(); } if (parent::hasField($field) || ($field === 'ID' && !$this->exists())) { return $this->getField($field); } if (($copy = $this->CopyContentFrom()) && $copy->exists()) { return $copy->$field; } return null; } public function getField($field) { if ($this->isFieldVirtualised($field)) { return $this->CopyContentFrom()->getField($field); } return parent::getField($field); } /** * Check if given field is virtualised * * @param string $field * @return bool */ public function isFieldVirtualised($field) { // Don't defer if field is non-virtualised $ignore = $this->getNonVirtualisedFields(); if (in_array($field, $ignore)) { return false; } // Don't defer if no virtual page $copied = $this->CopyContentFrom(); if (!$copied || !$copied->exists()) { return false; } // Check if copied object has this field return $copied->hasField($field); } /** * Pass unrecognized method calls on to the original data object * * @param string $method * @param array $args * @return mixed */ public function __call($method, $args) { if (parent::hasMethod($method)) { return parent::__call($method, $args); } else { return call_user_func_array([$this->CopyContentFrom(), $method], $args); } } /** * @param string $field * @return bool */ public function hasField($field) { if (parent::hasField($field)) { return true; } $copy = $this->CopyContentFrom(); return $copy && $copy->exists() && $copy->hasField($field); } /** * @param string $method * @return bool */ public function hasMethod($method) { if (parent::hasMethod($method)) { return true; } $copy = $this->CopyContentFrom(); return $copy && $copy->exists() && $copy->hasMethod($method); } /** * Return the "casting helper" (a piece of PHP code that when evaluated creates a casted value object) for a field * on this object. * * @param string $field * @return string */ public function castingHelper($field) { $copy = $this->CopyContentFrom(); if ($copy && $copy->exists() && ($helper = $copy->castingHelper($field))) { return $helper; } return parent::castingHelper($field); } /** * {@inheritdoc} */ public function allMethodNames($custom = false) { $methods = parent::allMethodNames($custom); if ($copy = $this->CopyContentFrom()) { $methods = array_merge($methods, $copy->allMethodNames($custom)); } return $methods; } /** * {@inheritdoc} */ public function getControllerName() { if ($copy = $this->CopyContentFrom()) { return $copy->getControllerName(); } return parent::getControllerName(); } } |