Source of file ProseController.php
Size: 17,413 Bytes - Last Modified: 2021-12-23T10:50:06+00:00
/var/www/docs.ssmods.com/process/src/src/ProseController.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527 | <?php namespace Symbiote\Prose; use SilverStripe\ORM\DataObject; use SilverStripe\Core\Convert; use SilverStripe\CMS\Model\SiteTree; use SilverStripe\Control\Controller; use SilverStripe\Assets\File; use SilverStripe\Assets\Folder; use SilverStripe\Core\Manifest\ModuleResourceLoader; use SilverStripe\Assets\Image; use SilverStripe\Control\HTTPRequest; use SilverStripe\Assets\Upload; use SilverStripe\Core\ClassInfo; use SilverStripe\Security\SecurityToken; use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Manifest\ResourceURLGenerator; use SilverStripe\Versioned\Versioned; use SilverStripe\View\Parsers\ShortcodeParser; use SilverStripe\View\Parsers\HTML4Value; /** * Controller that handles requests for data to manage the tree * * @author Marcus Nyeholt <marcus@silverstripe.com.au> */ class ProseController extends Controller { private static $allowed_actions = array( 'childnodes' => 'CMS_ACCESS_CMSMain', 'search' => 'CMS_ACCESS_CMSMain', 'pastefile' => 'CMS_ACCESS_CMSMain', 'uploadfile' => 'CMS_ACCESS_CMSMain', 'rendershortcode' ); /** * The raw types to search and filter by. If this is an array, * we should load objects of index 0, and filter for the classnames in index 1 */ private static $type_map = [ 'page' => SiteTree::class, 'file' => File::class, 'image' => [File::class, [Image::class, Folder::class]] ]; protected function searchBaseType($type) { $rootObjectType = $this->config()->type_map[$type]; if (is_array($rootObjectType)) { return $rootObjectType[0]; } return $rootObjectType; } protected function searchFilterTypes($type) { $rootObjectType = $this->config()->type_map[$type]; if (is_array($rootObjectType)) { return $rootObjectType[1]; } return null; } public function search($request) { $data = array(); $searchType = 'page'; if ($request->param('ID')) { $searchType = $request->param('ID'); } $rootObjectType = $this->searchBaseType($searchType); $term = $request->getVar('term'); $type = $rootObjectType; $data = array(); $parents = []; $list = null; if (!$type || strlen($term) < 1) { $list = DataObject::get($rootObjectType); } else { $list = DataObject::get($rootObjectType); if (is_numeric($term)) { $list = $list->filter([ 'ParentID' => $term, ]); } else { $list = $list->filterAny([ 'Title:PartialMatch' => $term, 'Name:PartialMatch' => $term, ]); } $hasParents = isset(singleton($rootObjectType)->hasOne()['Parent']); if ($hasParents) { $parentIds = $list->column('ParentID'); if (count($parentIds)) { $base = DataObject::getSchema()->baseDataClass($rootObjectType); $parentObs = $base::get()->filter('ID', $parentIds); $parents = $parentObs->map()->toArray(); } } } $filterTypes = $this->searchFilterTypes($searchType); if ($filterTypes) { $allClasses = []; foreach ($filterTypes as $filterType) { $allTypes = ClassInfo::subclassesFor($filterType); $allClasses = array_merge($allClasses, array_values($allTypes)); } $list = $list->filter(['ClassName' => array_values($allClasses)]); } // provides a way to browse _upwards_. Note that this doesn't // handle the root folder at all yet - that'd need further // thought about how tree browsing should work vs search $pid = 0; if (is_numeric($term)) { $item = $type::get()->byID($term); if ($item) { $pid = $item->ParentID; } } $data = $this->packageDataList($list, $parents, $pid); $this->getResponse()->addHeader('Content-Type', 'application/json'); return Convert::raw2json([ 'results' => $data ]); } protected function packageDataList($list, $parents = [], $parentId = false) { $data = array(); if ($parentId) { $data[] = [ 'text' => 'Parent', 'location' => '', 'id' => $parentId, 'icon' => $this->imgUrl('folder.png'), ]; } if ($list) { $list = $list->limit(50); foreach ($list as $child) { if ($child->ID < 0) { continue; } $nodeData = [ 'text' => isset($child->MenuTitle) ? $child->MenuTitle : $child->Title, 'location' => isset($parents[$child->ParentID]) ? $parents[$child->ParentID] : '', 'id' => $child->ID, ]; $thumbs = null; $nodeData['data'] = [ 'link' => $child instanceof File ? $child->getURL() : $child->RelativeLink() ]; // if (!strlen($nodeData['data']['link'])) { // continue; // } if ($child->ClassName == Image::class) { $thumbs = $this->generateThumbnails($child); $nodeData['icon'] = $thumbs['x128']; if (!$nodeData['icon']) { $nodeData['icon'] = $this->imgUrl('page.png'); } } else if ($child instanceof SiteTree) { // $nodeData['icon'] = ModuleResourceLoader::singleton()->resolvePath('symbiote/silverstripe-frontend-authoring: client/images/page.png'); $nodeData['icon'] = $this->imgUrl('page.png'); } else { $nodeData['icon'] = $this->imgUrl('folder.png'); } $data[] = $nodeData; } } return $data; } /** * Request nodes from the server * * TODO - refactor to use the 'packageDataList' method above * * @param SS_HTTPRequest $request * @return JSONString */ public function childnodes($request) { $data = array(); $searchType = 'page'; if ($request->param('ID')) { $searchType = $request->param('ID'); } $rootObjectType = $this->searchBaseType($searchType); if ($request->getVar('search')) { return $this->performSearch($request->getVar('search'), $rootObjectType); } $parentId = (int) $request->getVar('id'); if (!$parentId || $parentId == '#') { $parentId = 0; } $selectable = null; if ($request->param('OtherID')) { $selectable = explode(',', $request->param('OtherID')); } $type = $rootObjectType; $id = $parentId; if (!$type || $id < 0) { $data = array(0 => array('data' => 'An error has occurred')); } else { $children = null; if ($id === 0) { $children = DataObject::get($rootObjectType)->filter('ParentID', 0); } else { $object = DataObject::get_by_id($type, $id); $children = $this->childrenOfNode($object); } $data = array(); if ($children && count($children)) { foreach ($children as $child) { if ($child->ID < 0) { continue; } $haskids = $child->numChildren() > 0; $nodeData = [ 'text' => isset($child->MenuTitle) ? $child->MenuTitle : $child->Title, 'id' => $child->ID, ]; if ($selectable && !in_array($child->ClassName, $selectable)) { $nodeData['clickable'] = false; } $thumbs = null; if ($child->ClassName == Image::class) { $thumbs = $this->generateThumbnails($child); $nodeData['icon'] = $thumbs['x32']; } else if ($child instanceof SiteTree) { // $nodeData['icon'] = ModuleResourceLoader::singleton()->resolvePath('symbiote/silverstripe-frontend-authoring: client/images/page.png'); $nodeData['icon'] = $this->imgUrl('page.png'); } else { $nodeData['icon'] = $this->imgUrl('folder.png'); } $nodeData['children'] = $haskids; $nodeData['data'] = [ 'link' => $child instanceof File ? $child->getURL() : $child->RelativeLink() ]; $data[] = $nodeData; } } } $this->getResponse()->addHeader('Content-Type', 'application/json'); return Convert::raw2json($data); } /** * Called to generate thumbnails before sending the image data back * * @param Image $image */ protected function generateThumbnails(Image $image) { $thumbs = array(); /** @var Image */ $by16 = $image->Fit(16, 16); /** @var Image */ $by32 = $image->Fit(32, 32); /** @var Image */ $by64 = $image->Fit(64, 64); /** @var Image */ $by128 = $image->Fit(128, 128); $thumbs['x16'] = $by16 ? $by16->Link() : ''; $thumbs['x32'] = $by32 ? $by32->Link() : ''; $thumbs['x64'] = $by64 ? $by64->Link() : ''; $thumbs['x128'] = $by128 ? $by128->Link() : ''; return $thumbs; } protected function imgUrl($img) { static $generator; static $loader; if (!$generator) { $generator = Injector::inst()->get(ResourceURLGenerator::class); } if (!$loader) { $loader = ModuleResourceLoader::singleton(); } return $generator->urlForResource($loader->resolvePath('symbiote/silverstripe-prose-editor: client/images/' . $img)); } /** * Method to work around bug where Hierarchy.php directly refers to * ShowInMenus, which is only available on SiteTree * * @param DataObject $node * @return DataList */ protected function childrenOfNode($node) { $result = $node->stageChildren(true); if (isset($result)) { foreach ($result as $child) { if (!$child->canView()) { $result->remove($child); } } } return $result; } public function uploadfile(HTTPRequest $request) { if (!SecurityToken::inst()->checkRequest($request)) { return $this->owner->httpError(403); } Versioned::set_stage(Versioned::DRAFT); $response = ['success' => false]; if (isset($_FILES['imageUpload']) && file_exists($_FILES['imageUpload']['tmp_name'])) { $upload = Upload::create(); $upload->setValidator(Injector::inst()->create(ContentUploadValidator::class)); $image = Image::create(); $path = $this->sanitiseFilePath($request->requestVar('path')); if (!$path) { $path = 'Uploads'; } $upload->loadIntoFile($_FILES['imageUpload'], $image, $path); $file = $upload->getFile(); if ($file && $file->ID) { $response['success'] = true; $response['url'] = $file->getURL(); $response['name'] = $file->Title; $response['folder_id'] = $file->ParentID; } if (file_exists($_FILES['imageUpload']['tmp_name'])) { @unlink($_FILES['imageUpload']['tmp_name']); } } $this->owner->getResponse()->addHeader('Content-Type', 'application/json'); return json_encode($response, JSON_PRETTY_PRINT); } public function pastefile(HTTPRequest $request) { if (!SecurityToken::inst()->checkRequest($request)) { return $this->owner->httpError(403); } $raw = $request->postVar('rawData'); $filename = $request->postVar('filename') ? $request->postVar('filename') . '.png' : 'upload.png'; $response = ['success' => true]; if (substr($raw, 0, strlen('data:image/png;base64,')) === 'data:image/png;base64,') { $path = $this->sanitiseFilePath($request->postVar('path')); $parts = explode('/', $path); if (count($parts) > 5) { $path = 'Uploads'; } $base64 = substr($raw, strlen('data:image/png;base64,')); $tempFilePath = tempnam(TEMP_FOLDER, 'png'); file_put_contents($tempFilePath, base64_decode($base64)); $image = Image::create(); $tempFile = [ 'error' => '', 'size' => strlen($raw), 'name' => $filename, 'tmp_name' => $tempFilePath ]; $upload = Upload::create(); $upload->setValidator(Injector::inst()->create(ContentUploadValidator::class)); $upload->loadIntoFile($tempFile, $image, $path); $file = $upload->getFile(); if ($file && $file->ID) { $response['url'] = $file->getURL(); $response['name'] = $file->Title; $response['id'] = $file->ID; } if (file_exists($tempFilePath)) { @unlink($tempFile); } } $this->owner->getResponse()->addHeader('Content-Type', 'application/json'); return json_encode($response, JSON_PRETTY_PRINT); } protected function sanitiseFilePath($path) { $bits = explode('/', $path); $valid = array_map(function ($segment) { if ($segment == '.' || $segment == '..') { return ''; } return substr($segment, 0, 32); }, $bits); $valid = array_filter($valid, function ($part) { return strlen($part) > 0; }); return implode("/", $valid); } /** * Search for a node based on the passed in criteria. The output is a list * of nodes that should be opened from the top down * */ protected function performSearch($query, $rootObjectType = 'SiteTree') { $item = null; if (preg_match('/\[sitetree_link id=([0-9]+)\]/i', $query, $matches)) { $item = DataObject::get_by_id($rootObjectType, $matches[1]); } else if (preg_match('/^assets\//', $query)) { // search for the file based on its filepath $item = DataObject::get_one($rootObjectType, singleton('FEUtils')->dbQuote(array('Filename =' => $query))); } if ($item && $item->ID) { $items = array(); while ($item->ParentID != 0) { array_unshift($items, $rootObjectType . '-' . $item->ID); $item = $item->Parent(); } array_unshift($items, $rootObjectType . '-' . $item->ID); return implode(',', $items); } return ''; } public function rendershortcode() { $item = $this->owner->getRequest()->getVar('shortcode'); if ($item) { $shortcodeParams = $this->owner->getRequest()->getVar('attrs') ? json_decode($this->owner->getRequest()->getVar('attrs'), true) : []; $shortcodeStr = $this->shortcodeStr($item, $shortcodeParams); // shortcode parser doesn't handle missing width/height attributes well. $str = @ShortcodeParser::get_active()->parse($shortcodeStr); if ($str && strlen($str)) { $str = HTML4Value::create($str)->getContent(); $str = preg_replace('~>\\s+<~m', '><', $str); // replace style="width: 0px;" as caused by embedShortcodeProvider $str = str_replace('style="width: 0px;"', '', $str); } return trim($str); } } protected function shortcodeStr($shortcode, $params) { $paramStr = $this->attrListToAttrString($params); $shortcode = '[' . $shortcode . ']'; return strlen($paramStr) ? str_replace(']', ',' . $paramStr . ']', $shortcode) : $shortcode; } /** * Convert an array of key => values to shortcode parameters. * * @param aray $shortcodeParams * @return string */ protected function attrListToAttrString($shortcodeParams) { $params = []; if (is_array($shortcodeParams)) { foreach ($shortcodeParams as $name => $values) { if (strlen($values)) { $params[] = $name . '="' . $values . '"'; } } } return implode(',', $params); } } |