diff --git a/.htaccess b/.htaccess index ec4a30a28..b1b409a41 100644 --- a/.htaccess +++ b/.htaccess @@ -15,15 +15,14 @@ ## Black list protected files ## RewriteRule ^themes/.*/(layouts|pages|partials)/.*.htm index.php [L,NC] - RewriteRule ^uploads/protected/.* index.php [L,NC] RewriteRule ^bootstrap/.* index.php [L,NC] RewriteRule ^config/.* index.php [L,NC] RewriteRule ^vendor/.* index.php [L,NC] RewriteRule ^storage/cms/.* index.php [L,NC] RewriteRule ^storage/logs/.* index.php [L,NC] - RewriteCond %{REQUEST_URI} !storage/temp/public - RewriteRule ^storage/temp/.* index.php [L,NC] RewriteRule ^storage/framework/.* index.php [L,NC] + RewriteRule ^storage/temp/protected/.* index.php [L,NC] + RewriteRule ^storage/app/uploads/protected/.* index.php [L,NC] ## ## White listed folders and files diff --git a/CHANGELOG.md b/CHANGELOG.md index a8510c878..35ccca504 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +* **Build 24x** (2015-04-xx) + - Protected files can now be downloaded by administrators using the `fileupload` form widget. + - The `{% content %}` tag now supports passing parameters, parsed by a basic template engine (see Cms > Content block docs). + * **Build 247** (2015-04-23) - Added Media Manager feature. diff --git a/modules/backend/controllers/Files.php b/modules/backend/controllers/Files.php new file mode 100644 index 000000000..dea2d5493 --- /dev/null +++ b/modules/backend/controllers/Files.php @@ -0,0 +1,67 @@ +output(); + exit; + } + catch (Exception $ex) {} + + /* + * Fall back on Cms controller + */ + return App::make('Cms\Classes\Controller')->setStatusCode(404)->run('/404'); + } + + public static function getDownloadUrl($file) + { + return Backend::url('backend/files/get/' . self::getUniqueCode($file)); + } + + public static function getUniqueCode($file) + { + if (!$file) { + return null; + } + + $hash = md5($file->file_name . '!' . $file->disk_name); + return base64_encode($file->id . '!' . $hash); + } +} \ No newline at end of file diff --git a/modules/backend/formwidgets/FileUpload.php b/modules/backend/formwidgets/FileUpload.php index 1b52d5f06..2cf35280b 100644 --- a/modules/backend/formwidgets/FileUpload.php +++ b/modules/backend/formwidgets/FileUpload.php @@ -94,7 +94,7 @@ class FileUpload extends FormWidgetBase /** * Prepares the view data */ - public function prepareVars() + protected function prepareVars() { $this->vars['fileList'] = $this->getFileList(); $this->vars['singleFile'] = array_get($this->vars['fileList'], 0, null); @@ -107,14 +107,19 @@ class FileUpload extends FormWidgetBase protected function getFileList() { - $list = $this->getRelationObject()->withDeferred($this->sessionKey)->orderBy('sort_order')->get(); + $list = $this + ->getRelationObject() + ->withDeferred($this->sessionKey) + ->orderBy('sort_order') + ->get() + ; /* - * Set the thumb for each file + * Decorate each file with thumb and custom download path */ - foreach ($list as $file) { - $file->thumb = $file->getThumb($this->imageWidth, $this->imageHeight, $this->thumbOptions); - } + $list->each(function($file){ + $this->decorateFileAttributes($file); + }); return $list; } @@ -325,8 +330,7 @@ class FileUpload extends FormWidgetBase $fileRelation->add($file, $this->sessionKey); - $file->thumb = $file->getThumb($this->imageWidth, $this->imageHeight, $this->thumbOptions); - $result = $file; + $result = $this->decorateFileAttributes($file); } catch (Exception $ex) { @@ -336,4 +340,20 @@ class FileUpload extends FormWidgetBase header('Content-Type: application/json'); die($result); } + + /** + * Adds the bespoke thumb and path property used by this widget. + * @return System\Models\File + */ + protected function decorateFileAttributes($file) + { + $file->thumb = $file->getThumb($this->imageWidth, $this->imageHeight, $this->thumbOptions); + + // Internal download link + if (!$file->isImage() || !$file->isPublic()) { + $file->path = \Backend\Controllers\Files::getDownloadUrl($file); + } + + return $file; + } } diff --git a/modules/backend/formwidgets/fileupload/partials/_file_single.htm b/modules/backend/formwidgets/fileupload/partials/_file_single.htm index 9548af1b7..4dc7c7c64 100644 --- a/modules/backend/formwidgets/fileupload/partials/_file_single.htm +++ b/modules/backend/formwidgets/fileupload/partials/_file_single.htm @@ -61,7 +61,7 @@ diff --git a/modules/cms/classes/Controller.php b/modules/cms/classes/Controller.php index 4b0a65d65..9caa21e6b 100644 --- a/modules/cms/classes/Controller.php +++ b/modules/cms/classes/Controller.php @@ -28,6 +28,7 @@ use October\Rain\Exception\AjaxException; use October\Rain\Exception\SystemException; use October\Rain\Exception\ValidationException; use October\Rain\Exception\ApplicationException; +use October\Rain\Parse\Template as TextParser; use Illuminate\Http\RedirectResponse; /** @@ -894,8 +895,11 @@ class Controller /** * Renders a requested content file. * The framework uses this method internally. + * @param string $name The content view to load. + * @param array $parameters Parameter variables to pass to the view. + * @return string */ - public function renderContent($name) + public function renderContent($name, $parameters = []) { /* * Extensibility @@ -915,6 +919,13 @@ class Controller $fileContent = $content->parsedMarkup; + /* + * Parse basic template variables + */ + if (!empty($parameters)) { + $fileContent = TextParser::parse($fileContent, $parameters); + } + /* * Extensibility */ diff --git a/modules/cms/twig/ContentNode.php b/modules/cms/twig/ContentNode.php index b64a75a7b..61d02b677 100644 --- a/modules/cms/twig/ContentNode.php +++ b/modules/cms/twig/ContentNode.php @@ -1,8 +1,8 @@ $name], [], $lineno, $tag); + parent::__construct(['nodes' => $nodes], ['names' => $paramNames], $lineno, $tag); } /** @@ -24,11 +24,25 @@ class ContentNode extends Twig_Node */ public function compile(Twig_Compiler $compiler) { + $compiler->addDebugInfo($this); + + $compiler->write("\$context['__cms_content_params'] = [];\n"); + + for ($i = 1; $i < count($this->getNode('nodes')); $i++) { + $compiler->write("\$context['__cms_content_params']['".$this->getAttribute('names')[$i-1]."'] = "); + $compiler->write('twig_escape_filter($this->env, '); + $compiler->subcompile($this->getNode('nodes')->getNode($i)); + $compiler->write(")"); + $compiler->write(";\n"); + } + $compiler - ->addDebugInfo($this) ->write("echo \$this->env->getExtension('CMS')->contentFunction(") - ->subcompile($this->getNode('name')) + ->subcompile($this->getNode('nodes')->getNode(0)) + ->write(", \$context['__cms_content_params']") ->write(");\n") ; + + $compiler->write("unset(\$context['__cms_content_params']);\n"); } } diff --git a/modules/cms/twig/ContentTokenParser.php b/modules/cms/twig/ContentTokenParser.php index ee9973f65..a71128db0 100644 --- a/modules/cms/twig/ContentTokenParser.php +++ b/modules/cms/twig/ContentTokenParser.php @@ -1,13 +1,19 @@ * {% content "intro.htm" %} + * + * {% content "intro.md" name='John' %} + * + * {% content "intro/txt" name='John', year=2013 %} * * * @package october\cms @@ -24,10 +30,39 @@ class ContentTokenParser extends Twig_TokenParser */ public function parse(Twig_Token $token) { + $lineno = $token->getLine(); $stream = $this->parser->getStream(); + $name = $this->parser->getExpressionParser()->parseExpression(); - $stream->expect(Twig_Token::BLOCK_END_TYPE); - return new ContentNode($name, $token->getLine(), $this->getTag()); + $paramNames = []; + $nodes = [$name]; + + $end = false; + while (!$end) { + $current = $stream->next(); + + switch ($current->getType()) { + case Twig_Token::NAME_TYPE: + $paramNames[] = $current->getValue(); + $stream->expect(Twig_Token::OPERATOR_TYPE, '='); + $nodes[] = $this->parser->getExpressionParser()->parseExpression(); + break; + + case Twig_Token::BLOCK_END_TYPE: + $end = true; + break; + + default: + throw new Twig_Error_Syntax( + sprintf('Invalid syntax in the content tag. Line %s', $lineno), + $stream->getCurrent()->getLine(), + $stream->getFilename() + ); + break; + } + } + + return new ContentNode(new Twig_Node($nodes), $paramNames, $token->getLine(), $this->getTag()); } /** diff --git a/modules/cms/twig/Extension.php b/modules/cms/twig/Extension.php index f04395630..1c20a66e3 100644 --- a/modules/cms/twig/Extension.php +++ b/modules/cms/twig/Extension.php @@ -118,11 +118,13 @@ class Extension extends Twig_Extension /** * Renders a content file. + * @param string $name Specifies the content block name. + * @param array $parameters A optional list of parameters to pass to the content. * @return string Returns the file contents. */ - public function contentFunction($name) + public function contentFunction($name, $parameters = []) { - return $this->controller->renderContent($name); + return $this->controller->renderContent($name, $parameters); } /** diff --git a/modules/system/controllers/Updates.php b/modules/system/controllers/Updates.php index d6aec0024..1636ce75c 100644 --- a/modules/system/controllers/Updates.php +++ b/modules/system/controllers/Updates.php @@ -296,14 +296,14 @@ class Updates extends Controller $core = [null, null]; } - if (!is_array($plugins)) { - $plugins = []; - } - if (!is_array($themes)) { $themes = []; } + if (!is_array($plugins)) { + $plugins = []; + } + $updateSteps = []; list($coreHash, $coreBuild) = $core; @@ -318,19 +318,19 @@ class Updates extends Controller ]; } - foreach ($plugins as $name => $hash) { + foreach ($themes as $name => $hash) { $updateSteps[] = [ - 'code' => 'downloadPlugin', - 'label' => Lang::get('system::lang.updates.plugin_downloading', compact('name')), + 'code' => 'downloadTheme', + 'label' => Lang::get('system::lang.updates.theme_downloading', compact('name')), 'name' => $name, 'hash' => $hash ]; } - foreach ($themes as $name => $hash) { + foreach ($plugins as $name => $hash) { $updateSteps[] = [ - 'code' => 'downloadTheme', - 'label' => Lang::get('system::lang.updates.theme_downloading', compact('name')), + 'code' => 'downloadPlugin', + 'label' => Lang::get('system::lang.updates.plugin_downloading', compact('name')), 'name' => $name, 'hash' => $hash ]; @@ -348,19 +348,19 @@ class Updates extends Controller ]; } - foreach ($plugins as $name => $hash) { + foreach ($themes as $name => $hash) { $updateSteps[] = [ - 'code' => 'extractPlugin', - 'label' => Lang::get('system::lang.updates.plugin_extracting', compact('name')), + 'code' => 'extractTheme', + 'label' => Lang::get('system::lang.updates.theme_extracting', compact('name')), 'name' => $name, 'hash' => $hash ]; } - foreach ($themes as $name => $hash) { + foreach ($plugins as $name => $hash) { $updateSteps[] = [ - 'code' => 'extractTheme', - 'label' => Lang::get('system::lang.updates.theme_extracting', compact('name')), + 'code' => 'extractPlugin', + 'label' => Lang::get('system::lang.updates.plugin_extracting', compact('name')), 'name' => $name, 'hash' => $hash ]; @@ -600,6 +600,15 @@ class Updates extends Controller $themes = [$name => $hash]; $plugins = []; + foreach ((array) array_get($result, 'require') as $plugin) { + if ( + ($name = array_get($plugin, 'code')) && + ($hash = array_get($plugin, 'hash')) + ) { + $plugins[$name] = $hash; + } + } + /* * Update steps */