Skip to content

Commit

Permalink
Merge branch '5.x' of https://github.com/statamic/cms into starter-ki…
Browse files Browse the repository at this point in the history
…t-init-command
  • Loading branch information
jesseleite committed Dec 5, 2024
2 parents 0411923 + 9962a63 commit f92ba52
Show file tree
Hide file tree
Showing 15 changed files with 288 additions and 26 deletions.
32 changes: 32 additions & 0 deletions src/Fieldtypes/AddsEntryValidationReplacements.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Statamic\Fieldtypes;

use Statamic\Entries\Entry;
use Statamic\Fields\Field;
use Statamic\Fields\Validator;

/**
* TODO
* This allows Grid/Replicator/Bard fields to add validation replacements.
* It adds the same replacements that get added in EntriesController@update.
* Ideally those would get passed down into the field automatically somehow,
* so this can be considered a workaround until that happens.
*/
trait AddsEntryValidationReplacements
{
protected function addEntryValidationReplacements(Field $field, Validator $rules): Validator
{
$fieldParent = $field->parent();

if (! $fieldParent instanceof Entry) {
return $rules;
}

return $rules->withReplacements([
'id' => $fieldParent->id(),
'collection' => $fieldParent->collection()->handle(),
'site' => $fieldParent->locale(),
]);
}
}
10 changes: 9 additions & 1 deletion src/Fieldtypes/Bard/LinkMark.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,18 @@ protected function convertHref($href)
return '';
}

if ($item instanceof Entry) {
if (! $this->isApi() && $item instanceof Entry) {
return ($item->in(Site::current()->handle()) ?? $item)->url();
}

return $item->url();
}

private function isApi()
{
$isRestApi = config('statamic.api.enabled', false) && Str::startsWith(request()->path(), config('statamic.api.route', 'api'));
$isGraphqlApi = config('statamic.graphql.enabled', false) && Str::startsWith(request()->path(), 'graphql');

return $isRestApi || $isGraphqlApi;
}
}
7 changes: 6 additions & 1 deletion src/Fieldtypes/Grid.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

class Grid extends Fieldtype
{
use AddsEntryValidationReplacements;

protected $categories = ['structured'];
protected $defaultable = false;

Expand Down Expand Up @@ -158,7 +160,10 @@ protected function rowRules($data, $index)
->validator()
->withContext([
'prefix' => $this->field->validationContext('prefix').$this->rowRuleFieldPrefix($index).'.',
])
]);

$rules = $this
->addEntryValidationReplacements($this->field, $rules)
->rules();

return collect($rules)->mapWithKeys(function ($rules, $handle) use ($index) {
Expand Down
7 changes: 6 additions & 1 deletion src/Fieldtypes/Replicator.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

class Replicator extends Fieldtype
{
use AddsEntryValidationReplacements;

protected $categories = ['structured'];
protected $keywords = ['builder', 'page builder', 'content'];
protected $rules = ['array'];
Expand Down Expand Up @@ -150,7 +152,10 @@ protected function setRules($handle, $data, $index)
->validator()
->withContext([
'prefix' => $this->field->validationContext('prefix').$this->setRuleFieldPrefix($index).'.',
])
]);

$rules = $this
->addEntryValidationReplacements($this->field, $rules)
->rules();

return collect($rules)->mapWithKeys(function ($rules, $handle) use ($index) {
Expand Down
9 changes: 8 additions & 1 deletion src/Http/Requests/FrontendFormRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,15 @@ public function validateResolved()
// If this was submitted from a front-end form, we want to use the appropriate language
// for the translation messages. If there's no previous url, it was likely submitted
// directly in a headless format. In that case, we'll just use the default lang.
$site = ($previousUrl = session()->previousUrl()) ? Site::findByUrl($previousUrl) : null;
$site = ($previousUrl = $this->previousUrl()) ? Site::findByUrl($previousUrl) : null;

return $this->withLocale($site?->lang(), fn () => parent::validateResolved());
}

private function previousUrl()
{
return ($referrer = request()->header('referer'))
? url()->to($referrer)
: session()->previousUrl();
}
}
7 changes: 5 additions & 2 deletions src/Providers/ExtensionServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -335,9 +335,12 @@ protected function registerAppExtensions($folder, $requiredClass)
return;
}

foreach ($this->app['files']->files($path) as $file) {
foreach ($this->app['files']->allFiles($path) as $file) {
$relativePathOfFolder = str_replace(app_path('/'), '', $file->getPath());
$namespace = str_replace('/', '\\', $relativePathOfFolder);
$class = $file->getBasename('.php');
$fqcn = $this->app->getNamespace()."{$folder}\\{$class}";

$fqcn = $this->app->getNamespace()."{$namespace}\\{$class}";
if (is_subclass_of($fqcn, $requiredClass)) {
$fqcn::register();
}
Expand Down
24 changes: 12 additions & 12 deletions src/Providers/RouteServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,14 @@ private function needsEntryBinding(?Route $route): bool
return false;
}

if ($this->isCpRoute($route)) {
return true;
}

if ($this->isApiRoute($route)) {
return false;
}

if ($this->isCpRoute($route)) {
return true;
}

return $this->isFrontendBindingEnabled();
}

Expand Down Expand Up @@ -191,14 +191,14 @@ private function needsTermBinding(?Route $route): bool
return false;
}

if ($this->isCpRoute($route)) {
return true;
}

if ($this->isApiRoute($route)) {
return false;
}

if ($this->isCpRoute($route)) {
return true;
}

return $this->isFrontendBindingEnabled();
}

Expand Down Expand Up @@ -382,14 +382,14 @@ private function needsRevisionBinding(?Route $route): bool
return false;
}

if ($this->isCpRoute($route)) {
return true;
}

if ($this->isApiRoute($route)) {
return false;
}

if ($this->isCpRoute($route)) {
return true;
}

return $this->isFrontendBindingEnabled();
}

Expand Down
9 changes: 6 additions & 3 deletions src/Stache/Indexes/Index.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,18 @@ public function load()
return $this;
}

static::$currentlyLoading = $this->store->key().'/'.$this->name;
$loadingKey = $this->store->key().'/'.$this->name;
$currentlyLoadingThis = static::$currentlyLoading === $loadingKey;

static::$currentlyLoading = $loadingKey;

$this->loaded = true;

if (Statamic::isWorker()) {
if (Statamic::isWorker() && ! $currentlyLoadingThis) {
$this->loaded = false;
}

debugbar()->addMessage("Loading index: {$this->store->key()}/{$this->name}", 'stache');
debugbar()->addMessage("Loading index: {$loadingKey}", 'stache');

$this->items = Stache::cacheStore()->get($this->cacheKey());

Expand Down
51 changes: 48 additions & 3 deletions src/View/Antlers/Language/Runtime/PathDataManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

use Exception;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
Expand Down Expand Up @@ -641,10 +643,12 @@ public function getData(VariableReference $path, $data, $isForArrayIndex = false
}
}

if (count($path->pathParts) > 1 && $this->isPair == false) {
if (count($path->pathParts) > 1 && $this->isPair == false && ! $this->reducedVar instanceof Model) {
// If we have more steps in the path to take, but we are
// not a tag pair, we need to reduce anyway so we
// can descend further into the nested values.
// We skip this step for Models to prevent
// some of the reflection stuff below.
$this->lockData();
$this->reducedVar = self::reduce($this->reducedVar, true, $this->shouldDoValueIntercept);
$this->unlockData();
Expand Down Expand Up @@ -694,7 +698,11 @@ public function getData(VariableReference $path, $data, $isForArrayIndex = false
$wasBuilderGoingIntoLast = true;
}

$this->reduceVar($pathItem, $data);
if ($this->reducedVar instanceof Model) {
$this->reducedVar = $this->reducedVar->{$pathItem->name};
} else {
$this->reduceVar($pathItem, $data);
}

$this->collapseValues($pathItem->isFinal);

Expand Down Expand Up @@ -774,6 +782,10 @@ public function getData(VariableReference $path, $data, $isForArrayIndex = false
$this->compact(true);
}

if ($this->reducedVar instanceof Model && $this->isPair) {
$this->reducedVar = self::reduce($this->reducedVar, true, true, false);
}

$this->namedSlotsInScope = false;
$this->resetInternalState();

Expand Down Expand Up @@ -909,6 +921,10 @@ private function reduceVar($path, $processorData = [])
*/
private function compact($isFinal)
{
if (! $isFinal && $this->reducedVar instanceof Model) {
return;
}

if ($this->isForArrayIndex && $isFinal && is_object($this->reducedVar) && method_exists($this->reducedVar, '__toString')) {
$this->reducedVar = (string) $this->reducedVar;

Expand Down Expand Up @@ -951,13 +967,18 @@ protected static function guardRuntimeReturnValue($value)
* @param mixed $value The value to reduce.
* @param bool $isPair Indicates if the path belongs to a node pair.
* @param bool $reduceBuildersAndAugmentables Indicates if Builder and Augmentable instances should be resolved.
* @param bool $leaveModelsAlone
* @return array|string
*/
public static function reduce($value, $isPair = true, $reduceBuildersAndAugmentables = true)
public static function reduce($value, $isPair = true, $reduceBuildersAndAugmentables = true, $leaveModelsAlone = true)
{
$reductionStack = [$value];
$returnValue = $value;

if ($value instanceof Model && $leaveModelsAlone) {
return $value;
}

while (! empty($reductionStack)) {
$reductionValue = array_pop($reductionStack);

Expand Down Expand Up @@ -1014,6 +1035,26 @@ public static function reduce($value, $isPair = true, $reduceBuildersAndAugmenta
$reductionStack[] = $reductionValue->all();
GlobalRuntimeState::$isEvaluatingData = false;

continue;
} elseif ($reductionValue instanceof Model) {
GlobalRuntimeState::$isEvaluatingData = true;
$data = $reductionValue->toArray();

foreach (get_class_methods($reductionValue) as $method) {
if ((new \ReflectionMethod($reductionValue, $method))->getReturnType()?->getName() === Attribute::class) {
$method = Str::snake($method);
$data[$method] = $reductionValue->$method;
}

if (Str::startsWith($method, 'get') && Str::endsWith($method, 'Attribute')) {
$method = Str::of($method)->after('get')->before('Attribute')->snake()->__toString();
$data[$method] = $reductionValue->getAttribute($method);
}
}

$reductionStack[] = $data;
GlobalRuntimeState::$isEvaluatingData = false;

continue;
} elseif ($reductionValue instanceof Arrayable) {
GlobalRuntimeState::$isEvaluatingData = true;
Expand Down Expand Up @@ -1049,6 +1090,10 @@ public static function reduceForAntlers($value, Parser $parser, $data, $isPair =
GlobalRuntimeState::$isEvaluatingUserData = true;
GlobalRuntimeState::$isEvaluatingData = true;

if ($value instanceof Model) {
return $value;
}

if ($value instanceof Collection) {
$value = $value->all();
}
Expand Down
3 changes: 1 addition & 2 deletions src/View/Antlers/Language/Runtime/Sandbox/Environment.php
Original file line number Diff line number Diff line change
Expand Up @@ -1167,7 +1167,6 @@ public function process($nodes)
private function evaluateNullCoalescence(NullCoalescenceGroup $group)
{
$leftVal = $this->getValue($group->left);
$rightVal = $this->getValue($group->right);

if ($leftVal instanceof ArrayableString) {
$leftVal = $leftVal->value();
Expand All @@ -1177,7 +1176,7 @@ private function evaluateNullCoalescence(NullCoalescenceGroup $group)
return $leftVal;
}

return $rightVal;
return $this->getValue($group->right);
}

/**
Expand Down
32 changes: 32 additions & 0 deletions tests/API/APITest.php
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,22 @@ public function relationships_are_shallow_augmented()
]);
}

#[Test]
public function can_view_entries_when_cp_route_is_empty()
{
Facades\Config::set('statamic.cp.route', '');
Facades\Config::set('statamic.api.resources.collections', true);

Facades\Collection::make('pages')->save();
Facades\Entry::make()->collection('pages')->id('home')->data(['title' => 'Home'])->save();

$this->get('/api/collections/pages/entries/home')->assertJson([
'data' => [
'title' => 'Home',
],
]);
}

#[Test]
#[DataProvider('userPasswordFilterProvider')]
public function it_never_allows_filtering_users_by_password($filter)
Expand Down Expand Up @@ -528,6 +544,22 @@ public static function termNotFoundProvider()
];
}

#[Test]
public function can_view_terms_when_cp_route_is_empty()
{
Facades\Config::set('statamic.cp.route', '');
Facades\Config::set('statamic.api.resources.taxonomies', true);

Facades\Taxonomy::make('topics')->save();
Facades\Term::make()->taxonomy('topics')->inDefaultLocale()->slug('dance')->data(['title' => 'Dance'])->save();

$this->get('/api/taxonomies/topics/terms/dance')->assertJson([
'data' => [
'title' => 'Dance',
],
]);
}

private function makeCollection($handle)
{
return Facades\Collection::make($handle);
Expand Down
Loading

0 comments on commit f92ba52

Please sign in to comment.