diff --git a/CHANGELOG-v3.5.md b/CHANGELOG-v3.5.md
new file mode 100644
index 00000000000..8ba2efcb09b
--- /dev/null
+++ b/CHANGELOG-v3.5.md
@@ -0,0 +1,196 @@
+# Running Release Notes for Craft CMS 3.5
+
+> {warning} If you have the `baseCpUrl` config setting set, Craft 3.5 will **only** allow the control panel to be accessed from that URL.
+
+### Added
+- Added the “Show rounded icons” user preference. ([#5518](https://github.com/craftcms/cms/issues/5518))
+- Added the “Use shapes to represent statuses” user preference. ([#3293](https://github.com/craftcms/cms/issues/3293))
+- Added the “Suspend by default” user registration setting. ([#5830](https://github.com/craftcms/cms/issues/5830))
+- Added the ability to disable sites on the front end. ([#3005](https://github.com/craftcms/cms/issues/3005))
+- Soft-deleted elements now have a “Delete permanently” element action. ([#4420](https://github.com/craftcms/cms/issues/4420))
+- Assets now have a “Copy URL” element action. ([#2944](https://github.com/craftcms/cms/issues/2944))
+- Entry indexes can now show “Revision Notes” and “Last Edited By” columns. ([#5907](https://github.com/craftcms/cms/issues/5907))
+- Sections now have a new Propagation Method option, which gives entries control over which sites they should be saved to. ([#5988](https://github.com/craftcms/cms/issues/5988))
+- It’s now possible to set a custom route that handles Set Password requests. ([#5722](https://github.com/craftcms/cms/issues/5722))
+- Field labels now reveal their handles when the Option/ALT key is pressed. ([#5833](https://github.com/craftcms/cms/issues/5833))
+- Added the `allowedGraphqlOrigins` config setting. ([#5933](https://github.com/craftcms/cms/issues/5933))
+- Added the `brokenImagePath` config setting. ([#5877](https://github.com/craftcms/cms/issues/5877))
+- Added the `cpHeadTags` config setting, making it possible to give the control panel a custom favicon. ([#4003](https://github.com/craftcms/cms/issues/4003))
+- Added the `siteToken` config setting.
+- Added the `install/check` command. ([#5810](https://github.com/craftcms/cms/issues/5810))
+- Added the `plugin/install`, `plugin/uninstall`, `plugin/enable`, and `plugin/disable` commands. ([#5817](https://github.com/craftcms/cms/issues/5817))
+- Added the `{% html %}` Twig tag, which makes it possible to register arbitrary HTML for inclusion in the `
`, beginning of ``, or end of ``. ([#5955](https://github.com/craftcms/cms/issues/5955))
+- Added the `|diff` Twig filter.
+- Added the `|explodeClass` Twig filter, which converts class names into an array.
+- Added the `|explodeStyle` Twig filter, which converts CSS styles into an array of property/value pairs.
+- Added the `|push` Twig filter, which returns a new array with one or more items appended to it.
+- Added the `|unshift` Twig filter, which returns a new array with one or more items prepended to it.
+- Added the `|where` Twig filter.
+- Added the `raw()` Twig function, which wraps the given string in a `Twig\Markup` object to prevent it from getting HTML-encoded.
+- Added support for eager-loading elements’ current revisions, via `currentRevision`.
+- Added support for eager-loading drafts’ and revisions’ creators, via `draftCreator` and `revisionCreator`.
+- Added support for the `CRAFT_CP` PHP constant. ([#5122](https://github.com/craftcms/cms/issues/5122))
+- Added support for [GraphQL mutations](https://docs.craftcms.com/v3/graphql.html#mutations). ([#4835](https://github.com/craftcms/cms/issues/4835))
+- Added the `drafts`, `draftOf`, `draftId`, `draftCreator`, `revisions`, `revisionOf`, `revisionId` and `revisionCreator` arguments to element queries using GraphQL API. ([#5580](https://github.com/craftcms/cms/issues/5580))
+- Added the `isDraft`, `isRevision`, `sourceId`, `sourceUid`, and `isUnsavedDraft` fields to elements when using GraphPQL API. ([#5580](https://github.com/craftcms/cms/issues/5580))
+- Added the `assetCount`, `categoryCount`, `entryCount`, `tagCount`, and `userCount` queries for fetching the element counts to the GraphPQL API. ([#4847](https://github.com/craftcms/cms/issues/4847))
+- Added the `locale` argument to the `formatDateTime` GraphQL directive. ([#5593](https://github.com/craftcms/cms/issues/5593))
+- Added support for specifying a transform on assets’ `width` and `height` fields via GraphQL.
+- Added `craft\base\Element::EVENT_SET_EAGER_LOADED_ELEMENTS`.
+- Added `craft\base\ElementInterface::getIconUrl()`.
+- Added `craft\base\ElementInterface::gqlMutationNameByContext()`.
+- Added `craft\base\ElementTrait::$elementSiteId`.
+- Added `craft\behaviors\BaseRevisionBehavior`.
+- Added `craft\base\FieldInterface::getContentGqlMutationArgumentType()`.
+- Added `craft\base\FieldInterface::getContentGqlQueryArgumentType()`.
+- Added `craft\config\GeneralConfig::getTestToEmailAddress()`.
+- Added `craft\console\controllers\MailerController::$to`.
+- Added `craft\controllers\AppController::actionBrokenImage()`.
+- Added `craft\controllers\BaseEntriesController::enforceSitePermissions()`.
+- Added `craft\elements\actions\CopyUrl`.
+- Added `craft\elements\actions\Delete::$hard`.
+- Added `craft\elements\Asset::getSrcset()`. ([#5774](https://github.com/craftcms/cms/issues/5774))
+- Added `craft\events\RegisterGqlMutationsEvent`.
+- Added `craft\events\RegisterGqlSchemaComponentsEvent`.
+- Added `craft\events\SetEagerLoadedElementsEvent`.
+- Added `craft\gql\arguments\mutations\Asset`.
+- Added `craft\gql\arguments\mutations\Draft`.
+- Added `craft\gql\arguments\mutations\Entry`.
+- Added `craft\gql\arguments\mutations\Structure`.
+- Added `craft\gql\base\ElementMutationArguments`.
+- Added `craft\gql\base\ElementMutationResolver`.
+- Added `craft\gql\base\MutationArguments`.
+- Added `craft\gql\base\MutationResolver`.
+- Added `craft\gql\base\SingleGeneratorInterface`.
+- Added `craft\gql\base\StructureMutationTrait`.
+- Added `craft\gql\ElementQueryConditionBuilder`.
+- Added `craft\gql\Mutation`.
+- Added `craft\gql\mutations\Category`.
+- Added `craft\gql\mutations\Entry`.
+- Added `craft\gql\mutations\GlobalSet`.
+- Added `craft\gql\mutations\Ping`.
+- Added `craft\gql\mutations\Tag`.
+- Added `craft\gql\resolvers\mutations\Asset`.
+- Added `craft\gql\resolvers\mutations\Category`.
+- Added `craft\gql\resolvers\mutations\Entry`.
+- Added `craft\gql\resolvers\mutations\GlobalSet`.
+- Added `craft\gql\resolvers\mutations\Tag`.
+- Added `craft\gql\types\input\File`.
+- Added `craft\gql\types\input\Matrix`.
+- Added `craft\gql\types\Mutation`.
+- Added `craft\gql\types\TableRow::prepareRowFieldDefinition()`.
+- Added `craft\helpers\Assets::parseSrcsetSize()`.
+- Added `craft\helpers\Assets::scaledDimensions()`.
+- Added `craft\helpers\Console::ensureProjectConfigFileExists()`.
+- Added `craft\helpers\Db::batchInsert()`.
+- Added `craft\helpers\Db::delete()`.
+- Added `craft\helpers\Db::insert()`.
+- Added `craft\helpers\Db::replace()`.
+- Added `craft\helpers\Db::update()`.
+- Added `craft\helpers\Db::upsert()`.
+- Added `craft\helpers\Gql::canMutateAssets()`.
+- Added `craft\helpers\Gql::canMutateCategories()`.
+- Added `craft\helpers\Gql::canMutateEntries()`.
+- Added `craft\helpers\Gql::canMutateGlobalSets()`.
+- Added `craft\helpers\Gql::canMutateTags()`.
+- Added `craft\helpers\Gql::extractEntityAllowedActions()`.
+- Added `craft\helpers\FileHelper::addFilesToZip()`.
+- Added `craft\helpers\FileHelper::zip()`.
+- Added `craft\helpers\Html::explodeClass()`.
+- Added `craft\helpers\Html::explodeStyle()`.
+- Added `craft\helpers\Html::id()`.
+- Added `craft\helpers\Html::namespaceAttributes()`.
+- Added `craft\helpers\Html::namespaceHtml()`.
+- Added `craft\helpers\Html::namespaceId()`.
+- Added `craft\helpers\Html::namespaceInputName()`.
+- Added `craft\helpers\Html::namespaceInputs()`.
+- Added `craft\helpers\MailerHelper::normalizeEmails()`.
+- Added `craft\helpers\MailerHelper::settingsReport()`.
+- Added `craft\helpers\Queue`.
+- Added `craft\models\Section::PROPAGATION_METHOD_CUSTOM`.
+- Added `craft\models\Site::$enabled`.
+- Added `craft\services\Composer::handleError()`.
+- Added `craft\services\Composer::run()`.
+- Added `craft\services\Elements::createElementQuery()`.
+- Added `craft\services\Gql::getAllSchemaComponents()`.
+- Added `craft\services\ProjectConfig::$filename`. ([#5982](https://github.com/craftcms/cms/issues/5982))
+- Added `craft\queue\jobs\PruneRevisions`.
+- Added `craft\test\mockclasses\elements\MockElementQuery`.
+- Added `craft\web\AssetBundle\ContentWindowAsset`.
+- Added `craft\web\AssetBundle\IframeResizerAsset`.
+- Added `craft\web\Request::getAcceptsImage()`.
+- Added `craft\web\Request::getFullUri()`.
+- Added the `_includes/forms/password.html` control panel template.
+- Added the `_includes/forms/copytext.html` control panel template.
+- Added the `copytext` and `copytextField` macros to the `_includes/forms.html` control panel template.
+- Added the `Craft.ui.createCopyTextInput()`, `createCopyTextField()`, and `createCopyTextPrompt()` JavaScript methods.
+- Added the [iFrame Resizer](http://davidjbradshaw.github.io/iframe-resizer/) library.
+
+### Changed
+- User registration forms in the control panel now give users the option to send an activation email, even if email verification isn’t required. ([#5836](https://github.com/craftcms/cms/issues/5836))
+- Activation emails are now sent automatically on public registration if the `deferPublicRegistrationPassword` config setting is enabled, even if email verification isn’t required. ([#5836](https://github.com/craftcms/cms/issues/5836))
+- Large asset thumbnails now use the same aspect ratio as the source image. ([#5515](https://github.com/craftcms/cms/issues/5515))
+- Preview frames now maintain their scroll position across refreshes, even for cross-origin preview targets.
+- Preview targets that aren’t directly rendered by Craft must now include `lib/iframe-resizer-cw/iframeResizer.contentWindow.js` in order to maintain scroll position across refreshes.
+- The preview frame header no longer hides the top 54px of the preview frame when it’s scrolled all the way to the top. ([#5547](https://github.com/craftcms/cms/issues/5547))
+- Modal backdrops no longer blur the page content. ([#5651](https://github.com/craftcms/cms/issues/5651))
+- Element editor HUDs now warn before switching to another site, if there are any unsaved content changes. ([#2512](https://github.com/craftcms/cms/issues/2512))
+- Improved the styling of password inputs in the control panel.
+- Improved the UI for copying user activation URLs, asset reference tags, and GraphQL tokens’ authentication headers.
+- Improved the wording of the meta info displayed in entry revision menus. ([#5889](https://github.com/craftcms/cms/issues/5889))
+- Plain Text fields are now sortable in the control panel. ([#5819](https://github.com/craftcms/cms/issues/5819))
+- Extra entry revisions (per the `maxRevisions` config setting) are now pruned via a background job. ([#5902](https://github.com/craftcms/cms/issues/5902))
+- Database backups created by the Database Backup utility are now saved as zip files. ([#5822](https://github.com/craftcms/cms/issues/5822))
+- It’s now possible to specify aliases when eager-loading elements via the `with` param. ([#5793](https://github.com/craftcms/cms/issues/5793))
+- It’s now possible to eager-load elements’ ancestors and parents. ([#1382](https://github.com/craftcms/cms/issues/1382))
+- The `cpTrigger` config setting can now be set to `null`. ([#5122](https://github.com/craftcms/cms/issues/5122))
+- The `pathParam` config setting can now be set to `null`. ([#5676](https://github.com/craftcms/cms/issues/5676))
+- If the `baseCpUrl` config setting is set, Craft will no longer treat any other base URLs as control panel requests, even if they contain the correct trigger segment. ([#5860](https://github.com/craftcms/cms/issues/5860))
+- The `mailer/test` command now only supports testing the current email settings.
+- Reference tags can now provide a fallback value to be used if the reference can’t be resolved. ([#5589](https://github.com/craftcms/cms/issues/5589))
+- It’s no longer necessary to append the `|raw` filter after the `|namespace` filter.
+- The `|namespace` Twig filter now namespaces ID selectors within `
+ *
...
+ * ```
+ *
+ * would become this, if it were namespaced with `foo`:
+ *
+ * ```html
+ *
+ *
...
+ * ```
+ *
+ * @param string $html The HTML code
+ * @param string $namespace The namespace
+ * @param bool $withClasses Whether class names should be namespaced as well (affects both `class` attributes and class name CSS selectors)
+ * @return string The HTML with namespaced attributes
+ * @since 3.5.0
+ * @see namespaceHtml()
+ * @see namespaceInputs()
+ */
+ public static function namespaceAttributes(string $html, string $namespace, bool $withClasses = false): string
+ {
+ $markers = self::_escapeTextareas($html);
+ self::_namespaceAttributes($html, $namespace, $withClasses);
+ return self::_restoreTextareas($html, $markers);
+ }
+
+ /**
+ * @param string $html
+ * @param string $namespace
+ * @param bool $withClasses
+ */
+ private static function _namespaceAttributes(string &$html, string $namespace, bool $withClasses)
+ {
+ // normalize the namespace
+ $namespace = static::id($namespace);
+
+ // normal HTML attributes
+ $html = preg_replace('/(?]*>)(.*?)(<\/style>)/is', function(array $matches) use ($namespace, $dlm) {
+ $html = preg_replace("/(?]*>)(.*?)(<\/textarea>)/is', function(array $matches) use (&$markers) {
+ $marker = '{marker:' . StringHelper::randomString() . '}';
+ $markers[$marker] = $matches[2];
+ return $matches[1] . $marker . $matches[3];
+ }, $html);
+ return $markers;
+ }
+
+ /**
+ * Replaces markers with textareas.
+ *
+ * @param string $html
+ * @param array $markers
+ * @return string
+ */
+ private static function _restoreTextareas(string $html, array &$markers): string
+ {
+ return str_replace(array_keys($markers), array_values($markers), $html);
+ }
}
diff --git a/src/helpers/MailerHelper.php b/src/helpers/MailerHelper.php
index 762c05bbe5a..22aaa374cc8 100644
--- a/src/helpers/MailerHelper.php
+++ b/src/helpers/MailerHelper.php
@@ -8,6 +8,7 @@
namespace craft\helpers;
use Craft;
+use craft\elements\User;
use craft\errors\MissingComponentException;
use craft\events\RegisterComponentTypesEvent;
use craft\mail\Mailer;
@@ -18,6 +19,7 @@
use craft\mail\transportadapters\TransportAdapterInterface;
use craft\models\MailSettings;
use yii\base\Event;
+use yii\helpers\Inflector;
/**
* Class MailerHelper
@@ -99,4 +101,115 @@ public static function createMailer(MailSettings $settings): Mailer
$config = App::mailerConfig($settings);
return Craft::createObject($config);
}
+
+ /**
+ * Normalizes To/From/CC/BCC values into an array of email addresses, or email/name pairs.
+ *
+ * @param string|array|User|User[]|null $emails
+ * @return array|null
+ * @since 3.5.0
+ */
+ public static function normalizeEmails($emails)
+ {
+ if (empty($emails)) {
+ return null;
+ }
+
+ if (!is_array($emails)) {
+ $emails = [$emails];
+ }
+
+ $normalized = [];
+
+ foreach ($emails as $key => $value) {
+ if ($value instanceof User) {
+ if (($name = $value->getFullName()) !== null) {
+ $normalized[$value->email] = $name;
+ } else {
+ $normalized[] = $value->email;
+ }
+ } else if (is_numeric($key)) {
+ $normalized[] = $value;
+ } else {
+ $normalized[$key] = $value;
+ }
+ }
+
+ return $normalized;
+ }
+
+ /**
+ * Returns a report of the settings used for the given Mailer instance.
+ *
+ * @param Mailer $mailer
+ * @param TransportAdapterInterface|null $transportAdapter
+ * @return string
+ * @since 3.5.0
+ */
+ public static function settingsReport(Mailer $mailer, TransportAdapterInterface $transportAdapter = null): string
+ {
+ $transport = $mailer->getTransport();
+ $settings = [
+ Craft::t('app', 'From') => self::_emailList($mailer->from),
+ Craft::t('app', 'Reply To') => self::_emailList($mailer->replyTo),
+ Craft::t('app', 'Template') => $mailer->template,
+ Craft::t('app', 'Transport Type') => get_class($transport),
+ ];
+
+ $transportSettings = [];
+ $security = Craft::$app->getSecurity();
+
+ // Use the transport adapter settings if it was sent
+ if ($transportAdapter !== null) {
+ /** @var BaseTransportAdapter $transportAdapter */
+ foreach ($transportAdapter->settingsAttributes() as $name) {
+ $transportSettings[$transportAdapter->getAttributeLabel($name)] = $transportAdapter->$name;
+ }
+ } else {
+ // Otherwise just output whatever public properties we have available on the transport
+ foreach ((array)$transportAdapter as $name => $value) {
+ $transportSettings[Inflector::camel2words($name, true)] = $value;
+ }
+ }
+
+ foreach ($transportSettings as $label => $value) {
+ if (is_scalar($value)) {
+ $settings[$label] = $security->redactIfSensitive($label, $value);
+ } else if (is_array($value)) {
+ $settings[$label] = 'Array';
+ } else if (is_object($value)) {
+ $settings[$label] = 'Object';
+ }
+ }
+
+ $report = '';
+ foreach ($settings as $label => $value) {
+ $report .= "- **$label:** $value\n";
+ }
+
+ return $report;
+ }
+
+ /**
+ * Normalizes a list of emails and returns them in a comma-separated list.
+ *
+ * @param $emails
+ * @return string
+ */
+ private static function _emailList($emails): string
+ {
+ $normalized = static::normalizeEmails($emails);
+ if ($normalized === null) {
+ return '';
+ }
+ $list = [];
+ foreach ($normalized as $key => $value) {
+ if (is_numeric($key)) {
+ $list[] = $value;
+ } else {
+ $list[] = "$value <$key>";
+ }
+ }
+ return implode(', ', $list);
+ }
}
diff --git a/src/helpers/Queue.php b/src/helpers/Queue.php
new file mode 100644
index 00000000000..d9defd819ba
--- /dev/null
+++ b/src/helpers/Queue.php
@@ -0,0 +1,44 @@
+
+ * @since 3.5.0
+ */
+class Queue
+{
+ /**
+ * Pushes a job to the main app queue.
+ *
+ * @param JobInterface $job
+ * @param int|null $priority
+ * @return string|null The new job ID
+ */
+ public static function push(JobInterface $job, int $priority = null)
+ {
+ $queue = Craft::$app->getQueue();
+
+ if ($priority !== null) {
+ return $queue->push($job);
+ }
+
+ try {
+ return $queue->priority($priority)->push($job);
+ } catch (NotSupportedException $e) {
+ // The queue probably doesn't support custom push priorities. Try again without one.
+ return $queue->push($job);
+ }
+ }
+}
diff --git a/src/helpers/Sequence.php b/src/helpers/Sequence.php
index 41346acab27..2656a35857c 100644
--- a/src/helpers/Sequence.php
+++ b/src/helpers/Sequence.php
@@ -56,13 +56,16 @@ public static function next(string $name, int $length = null)
$num = self::_next($name);
if ($num === 1) {
- Craft::$app->getDb()->createCommand()
- ->insert(Table::SEQUENCES, ['name' => $name, 'next' => $num + 1], false)
- ->execute();
+ Db::insert(Table::SEQUENCES, [
+ 'name' => $name,
+ 'next' => $num + 1,
+ ], false);
} else {
- Craft::$app->getDb()->createCommand()
- ->update(Table::SEQUENCES, ['next' => $num + 1], ['name' => $name], [], false)
- ->execute();
+ Db::update(Table::SEQUENCES, [
+ 'next' => $num + 1,
+ ], [
+ 'name' => $name,
+ ], [], false);
}
} catch (\Throwable $e) {
$mutex->release($lockName);
diff --git a/src/helpers/StringHelper.php b/src/helpers/StringHelper.php
index 6f6a4bc5312..bc1b9c0d74c 100644
--- a/src/helpers/StringHelper.php
+++ b/src/helpers/StringHelper.php
@@ -9,6 +9,7 @@
use Craft;
use Stringy\Stringy as BaseStringy;
+use voku\helper\ASCII;
use yii\base\Exception;
use yii\base\InvalidConfigException;
@@ -140,15 +141,13 @@ public static function asciiCharMap(bool $flat = false, string $language = null)
// Include language specific replacements (unless the ASCII chars have custom mappings)
if ($language !== null) {
- $langSpecific = Stringy::getLangSpecificCharsArray($language);
- if (!empty($langSpecific)) {
- $generalConfig = Craft::$app->getConfig()->getGeneral();
- $customChars = !empty($generalConfig->customAsciiCharMappings) ? call_user_func_array('array_merge', $generalConfig->customAsciiCharMappings) : [];
- $customChars = array_flip($customChars);
- foreach ($langSpecific[0] as $i => $char) {
- if (!isset($customChars[$char])) {
- $flatMap[$char] = $langSpecific[1][$i];
- }
+ $langSpecific = ASCII::charsArrayWithOneLanguage($language);
+ $generalConfig = Craft::$app->getConfig()->getGeneral();
+ $customChars = !empty($generalConfig->customAsciiCharMappings) ? call_user_func_array('array_merge', $generalConfig->customAsciiCharMappings) : [];
+ $customChars = array_flip($customChars);
+ foreach ($langSpecific['orig'] as $i => $char) {
+ if (!isset($customChars[$char])) {
+ $flatMap[$char] = $langSpecific['replace'][$i];
}
}
}
@@ -1017,14 +1016,13 @@ public static function prepend(string $str, string $string): string
* @param int $length The length of the random string. Defaults to 36.
* @param bool $extendedChars Whether to include symbols in the random string.
* @return string The randomly generated string.
- * @throws \Exception
*/
public static function randomString(int $length = 36, bool $extendedChars = false): string
{
if ($extendedChars) {
$validChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890`~!@#$%^&*()-_=+[]\{}|;:\'",./<>?"';
} else {
- $validChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
+ $validChars = 'abcdefghijklmnopqrstuvwxyz';
}
return static::randomStringWithChars($validChars, $length);
@@ -1038,7 +1036,6 @@ public static function randomString(int $length = 36, bool $extendedChars = fals
* @param string $validChars A string containing the valid characters
* @param int $length The length of the random string
* @return string The randomly generated string.
- * @throws \Exception
*/
public static function randomStringWithChars(string $validChars, int $length): string
{
@@ -1050,10 +1047,14 @@ public static function randomStringWithChars(string $validChars, int $length): s
// repeat the steps until we've created a string of the right length
for ($i = 0; $i < $length; $i++) {
// pick a random number from 1 up to the number of valid chars
- $randomPick = random_int(1, $numValidChars);
+ try {
+ $randomPick = random_int(0, $numValidChars - 1);
+ } catch (\Exception $e) {
+ $randomPick = rand(0, $numValidChars - 1);
+ }
// take the random character out of the string of valid chars
- $randomChar = $validChars[$randomPick - 1];
+ $randomChar = $validChars[$randomPick];
// add the randomly-chosen char onto the end of our string
$randomString .= $randomChar;
@@ -1087,7 +1088,7 @@ public static function regexReplace(string $str, string $pattern, string $replac
*/
public static function removeHtml(string $str, string $allowableTags = null): string
{
- return (string)BaseStringy::create($str)->removeHtml($allowableTags);
+ return (string)BaseStringy::create($str)->removeHtml($allowableTags ?? '');
}
/**
diff --git a/src/helpers/Stringy.php b/src/helpers/Stringy.php
index ce45d3d3d17..b241131e9a1 100644
--- a/src/helpers/Stringy.php
+++ b/src/helpers/Stringy.php
@@ -8,6 +8,7 @@
namespace craft\helpers;
use Craft;
+use voku\helper\ASCII;
/**
* The entire purpose of this class is so we can get at the charsArray in Stringy, which is a protected method
@@ -15,6 +16,7 @@
*
* @author Pixel & Tonic, Inc.
* @since 3.0.0
+ * @deprecated in 3.5.0
*/
class Stringy extends \Stringy\Stringy
{
@@ -27,7 +29,11 @@ class Stringy extends \Stringy\Stringy
*/
public static function getLangSpecificCharsArray(string $language = 'en'): array
{
- return static::langSpecificCharsArray($language);
+ $array = ASCII::charsArrayWithOneLanguage($language);
+ return [
+ $array['orig'],
+ $array['replace'],
+ ];
}
/**
@@ -50,7 +56,7 @@ protected function charsArray(): array
{
static $charsArray;
return $charsArray ?? $charsArray = array_merge(
- parent::charsArray(),
+ ASCII::charsArrayWithMultiLanguageValues(),
Craft::$app->getConfig()->getGeneral()->customAsciiCharMappings
);
}
diff --git a/src/helpers/Template.php b/src/helpers/Template.php
index ae635518c83..9f0e51e14b8 100644
--- a/src/helpers/Template.php
+++ b/src/helpers/Template.php
@@ -8,7 +8,6 @@
namespace craft\helpers;
use Craft;
-use craft\base\Element;
use craft\base\ElementInterface;
use craft\db\Paginator;
use craft\i18n\Locale;
@@ -218,7 +217,6 @@ private static function _profileToken(string $type, string $name, int $count): s
*/
private static function _includeElementInTemplateCaches(ElementInterface $element)
{
- /** @var Element $element */
$elementId = $element->id;
// Don't initialize the TemplateCaches service if we don't have to
diff --git a/src/helpers/UrlHelper.php b/src/helpers/UrlHelper.php
index 7bd8e8e1830..eeac1c5e5dd 100644
--- a/src/helpers/UrlHelper.php
+++ b/src/helpers/UrlHelper.php
@@ -240,7 +240,7 @@ public static function url(string $path = '', $params = null, string $scheme = n
$request = Craft::$app->getRequest();
if ($request->getIsCpRequest()) {
- $path = Craft::$app->getConfig()->getGeneral()->cpTrigger . ($path ? '/' . $path : '');
+ $path = static::prependCpTrigger($path);
$cpUrl = true;
} else {
$cpUrl = false;
@@ -270,7 +270,7 @@ public static function cpUrl(string $path = '', $params = null, string $scheme =
}
$path = trim($path, '/');
- $path = Craft::$app->getConfig()->getGeneral()->cpTrigger . ($path ? '/' . $path : '');
+ $path = static::prependCpTrigger($path);
return self::_createUrl($path, $params, $scheme, true);
}
@@ -337,12 +337,13 @@ public static function siteUrl(string $path = '', $params = null, string $scheme
*/
public static function actionUrl(string $path = '', $params = null, string $scheme = null): string
{
- $path = Craft::$app->getConfig()->getGeneral()->actionTrigger . '/' . trim($path, '/');
+ $generalConfig = Craft::$app->getConfig()->getGeneral();
+ $path = $generalConfig->actionTrigger . '/' . trim($path, '/');
$request = Craft::$app->getRequest();
if ($request->getIsCpRequest()) {
- $path = Craft::$app->getConfig()->getGeneral()->cpTrigger . ($path ? '/' . $path : '');
+ $path = static::prependCpTrigger($path);
$cpUrl = true;
} else {
$cpUrl = false;
@@ -353,7 +354,7 @@ public static function actionUrl(string $path = '', $params = null, string $sche
$scheme = 'https';
}
- return self::_createUrl($path, $params, $scheme, $cpUrl, true, false);
+ return self::_createUrl($path, $params, $scheme, $cpUrl, (bool)$generalConfig->pathParam, false);
}
/**
@@ -541,6 +542,18 @@ public static function hostInfo(string $url): string
return $host;
}
+ /**
+ * Prepends the CP trigger onto the given path.
+ *
+ * @param string $path
+ * @return string
+ * @since 3.5.0
+ */
+ public static function prependCpTrigger(string $path): string
+ {
+ return implode('/', array_filter([Craft::$app->getConfig()->getGeneral()->cpTrigger, $path]));
+ }
+
// Deprecated Methods
// -------------------------------------------------------------------------
@@ -554,7 +567,6 @@ public static function hostInfo(string $url): string
*/
public static function urlWithProtocol(string $url, string $scheme): string
{
- Craft::$app->getDeprecator()->log('UrlHelper::urlWithProtocol()', 'UrlHelper::urlWithProtocol() is deprecated. Use urlWithScheme() instead.');
return static::urlWithScheme($url, $scheme);
}
@@ -568,7 +580,6 @@ public static function urlWithProtocol(string $url, string $scheme): string
*/
public static function getProtocolForTokenizedUrl(): string
{
- Craft::$app->getDeprecator()->log('UrlHelper::getProtocolForTokenizedUrl()', 'UrlHelper::getProtocolForTokenizedUrl() is deprecated. Use getSchemeForTokenizedUrl() instead.');
return static::getSchemeForTokenizedUrl();
}
@@ -639,7 +650,7 @@ private static function _createUrl(string $path, $params, string $scheme = null,
}
// Put it all together
- if (!$showScriptName || $generalConfig->usePathInfo) {
+ if (!$showScriptName || $generalConfig->usePathInfo || !$generalConfig->pathParam) {
if ($path) {
$url = rtrim($baseUrl, '/') . '/' . trim($path, '/');
diff --git a/src/i18n/Formatter.php b/src/i18n/Formatter.php
index d43374eb236..7517535e2c8 100644
--- a/src/i18n/Formatter.php
+++ b/src/i18n/Formatter.php
@@ -188,12 +188,14 @@ public function asDatetime($value, $format = null): string
* It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
* Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
* PHP [date()](http://php.net/manual/en/function.date.php)-function.
+ * @param bool $withPreposition Whether a preposition should be included in the returned string
+ * (e.g. “**at** 12:00 PM” or “**on** Wednesday”).
* @return string the formatted result.
* @throws InvalidArgumentException if the input value can not be evaluated as a date value.
* @throws InvalidConfigException if the date format is invalid.
* @see datetimeFormat
*/
- public function asTimestamp($value, string $format = null): string
+ public function asTimestamp($value, string $format = null, bool $withPreposition = false): string
{
/** @var DateTime $timestamp */
/** @var bool $hasTimeInfo */
@@ -202,22 +204,28 @@ public function asTimestamp($value, string $format = null): string
// If it's today or missing date info, just return the local time.
if (!$hasDateInfo || DateTimeHelper::isToday($timestamp)) {
- return $hasTimeInfo ? $this->asTime($timestamp, $format) : Craft::t('app', 'Today');
+ if ($hasTimeInfo) {
+ $time = $this->asTime($timestamp, $format);
+ return $withPreposition ? Craft::t('app', 'at {time}', ['time' => $time]) : $time;
+ }
+ return $withPreposition ? Craft::t('app', 'today') : Craft::t('app', 'Today');
}
// If it was yesterday, display 'Yesterday'
if (DateTimeHelper::isYesterday($timestamp)) {
- return Craft::t('app', 'Yesterday');
+ return $withPreposition ? Craft::t('app', 'yesterday') : Craft::t('app', 'Yesterday');
}
// If it were up to 7 days ago, display the weekday name.
if (DateTimeHelper::isWithinLast($timestamp, '7 days')) {
$day = $timestamp->format('w');
- return Craft::$app->getI18n()->getLocaleById($this->locale)->getWeekDayName($day);
+ $dayName = Craft::$app->getI18n()->getLocaleById($this->locale)->getWeekDayName($day);
+ return $withPreposition ? Craft::t('app', 'on {day}', ['day' => $dayName]) : $dayName;
}
// Otherwise, just return the local date.
- return $this->asDate($timestamp, $format);
+ $date = $this->asDate($timestamp, $format);
+ return $withPreposition ? Craft::t('app', 'on {date}', ['date' => $date]) : $date;
}
/**
diff --git a/src/image/Raster.php b/src/image/Raster.php
index 6063d4a99e7..573bc874866 100644
--- a/src/image/Raster.php
+++ b/src/image/Raster.php
@@ -159,7 +159,7 @@ public function loadImage(string $path)
$mimeType = FileHelper::getMimeType($path, null, false);
if ($mimeType !== null && strpos($mimeType, 'image/') !== 0 && strpos($mimeType, 'application/pdf') !== 0) {
- throw new ImageException(Craft::t('app', 'The file “{name}” does not appear to be an image.', ['name' => pathinfo($path, PATHINFO_BASENAME)]));
+ throw new ImageException(Craft::t('app', 'The file “{name}” does not appear to be an image.', ['name' => basename($path)]));
}
try {
diff --git a/src/mail/Mailer.php b/src/mail/Mailer.php
index aaf59ed9360..a6a91cda337 100644
--- a/src/mail/Mailer.php
+++ b/src/mail/Mailer.php
@@ -151,17 +151,9 @@ public function send($message)
}
// Apply the testToEmailAddress config setting
- $testToEmailAddress = Craft::$app->getConfig()->getGeneral()->testToEmailAddress;
+ $testToEmailAddress = Craft::$app->getConfig()->getGeneral()->getTestToEmailAddress();
if (!empty($testToEmailAddress)) {
- $to = [];
- foreach ((array)$testToEmailAddress as $emailAddress => $name) {
- if (is_numeric($emailAddress)) {
- $to[$name] = Craft::t('app', 'Test Recipient');
- } else {
- $to[$emailAddress] = $name;
- }
- }
- $message->setTo($to);
+ $message->setTo($testToEmailAddress);
$message->setCc(null);
$message->setBcc(null);
}
diff --git a/src/mail/Message.php b/src/mail/Message.php
index 3ec10ff6112..899150145af 100644
--- a/src/mail/Message.php
+++ b/src/mail/Message.php
@@ -8,6 +8,7 @@
namespace craft\mail;
use craft\elements\User;
+use craft\helpers\MailerHelper;
/**
* Represents an email message.
@@ -43,9 +44,7 @@ class Message extends \yii\swiftmailer\Message
*/
public function setFrom($from)
{
- $from = $this->_normalizeEmails($from);
- parent::setFrom($from);
-
+ parent::setFrom(MailerHelper::normalizeEmails($from));
return $this;
}
@@ -61,9 +60,7 @@ public function setFrom($from)
*/
public function setReplyTo($replyTo)
{
- $replyTo = $this->_normalizeEmails($replyTo);
- parent::setReplyTo($replyTo);
-
+ parent::setReplyTo(MailerHelper::normalizeEmails($replyTo));
return $this;
}
@@ -86,9 +83,7 @@ public function setTo($to)
$this->variables['user'] = $to;
}
- $to = $this->_normalizeEmails($to);
- parent::setTo($to);
-
+ parent::setTo(MailerHelper::normalizeEmails($to));
return $this;
}
@@ -103,9 +98,7 @@ public function setTo($to)
*/
public function setCc($cc)
{
- $cc = $this->_normalizeEmails($cc);
- parent::setCc($cc);
-
+ parent::setCc(MailerHelper::normalizeEmails($cc));
return $this;
}
@@ -120,42 +113,7 @@ public function setCc($cc)
*/
public function setBcc($bcc)
{
- $bcc = $this->_normalizeEmails($bcc);
- parent::setBcc($bcc);
-
+ parent::setBcc(MailerHelper::normalizeEmails($bcc));
return $this;
}
-
- /**
- * @param string|array|User|User[]|null $emails
- * @return string|array
- */
- private function _normalizeEmails($emails)
- {
- if (empty($emails)) {
- return null;
- }
-
- if (!is_array($emails)) {
- $emails = [$emails];
- }
-
- $normalized = [];
-
- foreach ($emails as $key => $value) {
- if ($value instanceof User) {
- if (($name = $value->getFullName()) !== null) {
- $normalized[$value->email] = $name;
- } else {
- $normalized[] = $value->email;
- }
- } else if (is_numeric($key)) {
- $normalized[] = $value;
- } else {
- $normalized[$key] = $value;
- }
- }
-
- return $normalized;
- }
}
diff --git a/src/migrations/Install.php b/src/migrations/Install.php
index f7cab88078c..8fb2827ddc9 100644
--- a/src/migrations/Install.php
+++ b/src/migrations/Install.php
@@ -9,7 +9,6 @@
namespace craft\migrations;
use Craft;
-use craft\base\Plugin;
use craft\db\Migration;
use craft\db\Table;
use craft\elements\Asset;
@@ -121,8 +120,8 @@ public function createTables()
'format' => $this->string(),
'location' => $this->string()->notNull(),
'volumeId' => $this->integer(),
- 'fileExists' => $this->boolean()->defaultValue(false)->notNull(),
- 'inProgress' => $this->boolean()->defaultValue(false)->notNull(),
+ 'fileExists' => $this->boolean()->notNull()->defaultValue(false),
+ 'inProgress' => $this->boolean()->notNull()->defaultValue(false),
'dateIndexed' => $this->dateTime(),
'dateCreated' => $this->dateTime()->notNull(),
'dateUpdated' => $this->dateTime()->notNull(),
@@ -169,7 +168,7 @@ public function createTables()
'id' => $this->primaryKey(),
'groupId' => $this->integer()->notNull(),
'siteId' => $this->integer()->notNull(),
- 'hasUrls' => $this->boolean()->defaultValue(true)->notNull(),
+ 'hasUrls' => $this->boolean()->notNull()->defaultValue(true),
'uriFormat' => $this->text(),
'template' => $this->string(500),
'dateCreated' => $this->dateTime()->notNull(),
@@ -231,7 +230,7 @@ public function createTables()
'creatorId' => $this->integer(),
'name' => $this->string()->notNull(),
'notes' => $this->text(),
- 'trackChanges' => $this->boolean()->defaultValue(false)->notNull(),
+ 'trackChanges' => $this->boolean()->notNull()->defaultValue(false),
'dateLastMerged' => $this->dateTime(),
]);
$this->createTable(Table::ELEMENTINDEXSETTINGS, [
@@ -248,8 +247,8 @@ public function createTables()
'revisionId' => $this->integer(),
'fieldLayoutId' => $this->integer(),
'type' => $this->string()->notNull(),
- 'enabled' => $this->boolean()->defaultValue(true)->notNull(),
- 'archived' => $this->boolean()->defaultValue(false)->notNull(),
+ 'enabled' => $this->boolean()->notNull()->defaultValue(true),
+ 'archived' => $this->boolean()->notNull()->defaultValue(false),
'dateCreated' => $this->dateTime()->notNull(),
'dateUpdated' => $this->dateTime()->notNull(),
'dateDeleted' => $this->dateTime()->null(),
@@ -261,7 +260,7 @@ public function createTables()
'siteId' => $this->integer()->notNull(),
'slug' => $this->string(),
'uri' => $this->string(),
- 'enabled' => $this->boolean()->defaultValue(true)->notNull(),
+ 'enabled' => $this->boolean()->notNull()->defaultValue(true),
'dateCreated' => $this->dateTime()->notNull(),
'dateUpdated' => $this->dateTime()->notNull(),
'uid' => $this->uid(),
@@ -540,6 +539,7 @@ public function createTables()
'id' => $this->primaryKey(),
'groupId' => $this->integer()->notNull(),
'primary' => $this->boolean()->notNull(),
+ 'enabled' => $this->boolean()->notNull()->defaultValue(true),
'name' => $this->string()->notNull(),
'handle' => $this->string()->notNull(),
'language' => $this->string(12)->notNull(),
@@ -917,10 +917,10 @@ public function createIndexes()
$this->addPrimaryKey($this->db->getIndexName(Table::SEARCHINDEX, 'elementId,attribute,fieldId,siteId', true), Table::SEARCHINDEX, 'elementId,attribute,fieldId,siteId');
- $sql = 'CREATE INDEX ' . $this->db->quoteTableName($this->db->getIndexName(Table::SEARCHINDEX, 'keywords_vector')) . ' ON {{%searchindex}} USING GIN([[keywords_vector]] [[pg_catalog]].[[tsvector_ops]]) WITH (FASTUPDATE=YES)';
+ $sql = 'CREATE INDEX ' . $this->db->quoteTableName($this->db->getIndexName(Table::SEARCHINDEX, 'keywords_vector')) . ' ON ' . Table::SEARCHINDEX . ' USING GIN([[keywords_vector]] [[pg_catalog]].[[tsvector_ops]]) WITH (FASTUPDATE=YES)';
$this->db->createCommand($sql)->execute();
- $sql = 'CREATE INDEX ' . $this->db->quoteTableName($this->db->getIndexName(Table::SEARCHINDEX, 'keywords')) . ' ON {{%searchindex}} USING btree(keywords)';
+ $sql = 'CREATE INDEX ' . $this->db->quoteTableName($this->db->getIndexName(Table::SEARCHINDEX, 'keywords')) . ' ON ' . Table::SEARCHINDEX . ' USING btree(keywords)';
$this->db->createCommand($sql)->execute();
}
}
@@ -1047,7 +1047,7 @@ public function insertDefaultData()
// Compare existing Craft schema version with the one that is being applied.
if (!version_compare($craftSchemaVersion, $expectedSchemaVersion, '=')) {
- throw new InvalidConfigException("Craft is installed at the wrong schema version ({$craftSchemaVersion}, but project.yaml lists {$expectedSchemaVersion}).");
+ throw new InvalidConfigException("Craft is installed at the wrong schema version ({$craftSchemaVersion}, but $projectConfig->filename lists {$expectedSchemaVersion}).");
}
// Make sure at least sites are processed
@@ -1060,8 +1060,8 @@ public function insertDefaultData()
Craft::$app->getErrorHandler()->logException($e);
// Rename project.yaml so we can create a new one
- $backupFile = pathinfo(ProjectConfig::CONFIG_FILENAME, PATHINFO_FILENAME) . date('-Ymh-His') . '.yaml';
- echo " > renaming project.yaml to {$backupFile} and moving to config backup folder ... ";
+ $backupFile = pathinfo($projectConfig->filename, PATHINFO_FILENAME) . date('-Ymh-His') . '.yaml';
+ echo " > renaming $projectConfig->filename to $backupFile and moving to config backup folder ... ";
rename($configFile, Craft::$app->getPath()->getConfigBackupPath() . '/' . $backupFile);
echo "done\n";
@@ -1140,9 +1140,8 @@ private function _installPlugins()
$plugin = $pluginsService->createPlugin($handle);
$expectedSchemaVersion = $projectConfig->get(Plugins::CONFIG_PLUGINS_KEY . '.' . $handle . '.schemaVersion', true);
- /** @var Plugin|null $plugin */
if ($plugin->schemaVersion && $expectedSchemaVersion && $plugin->schemaVersion != $expectedSchemaVersion) {
- throw new InvalidPluginException($handle, "{$handle} is installed at the wrong schema version ({$plugin->schemaVersion}, but project.yaml lists {$expectedSchemaVersion}).");
+ throw new InvalidPluginException($handle, "{$handle} is installed at the wrong schema version ({$plugin->schemaVersion}, but $projectConfig->filename lists {$expectedSchemaVersion}).");
}
}
diff --git a/src/migrations/m151002_095935_volume_cache_settings.php b/src/migrations/m151002_095935_volume_cache_settings.php
index 4e1a5e6855d..c0019ff4d4e 100644
--- a/src/migrations/m151002_095935_volume_cache_settings.php
+++ b/src/migrations/m151002_095935_volume_cache_settings.php
@@ -2,10 +2,10 @@
namespace craft\migrations;
-use Craft;
use craft\db\Migration;
use craft\db\Query;
use craft\db\Table;
+use craft\helpers\Db;
use craft\helpers\Json;
/**
@@ -35,12 +35,11 @@ public function safeUp()
if (!empty($settings['expires']) && preg_match('/(\d+)([a-z]+)/', $settings['expires'], $matches)) {
$settings['expires'] = $matches[1] . ' ' . $matches[2];
- Craft::$app->getDb()->createCommand()
- ->update(
- Table::VOLUMES,
- ['settings' => Json::encode($settings)],
- ['id' => $volume['id']])
- ->execute();
+ Db::update(Table::VOLUMES, [
+ 'settings' => Json::encode($settings)
+ ], [
+ 'id' => $volume['id'],
+ ], [], true, $this->db);
}
}
}
diff --git a/src/migrations/m151005_142750_volume_s3_storage_settings.php b/src/migrations/m151005_142750_volume_s3_storage_settings.php
index 163a715eb1f..498a5442295 100644
--- a/src/migrations/m151005_142750_volume_s3_storage_settings.php
+++ b/src/migrations/m151005_142750_volume_s3_storage_settings.php
@@ -2,10 +2,10 @@
namespace craft\migrations;
-use Craft;
use craft\db\Migration;
use craft\db\Query;
use craft\db\Table;
+use craft\helpers\Db;
use craft\helpers\Json;
/**
@@ -31,12 +31,11 @@ public function safeUp()
if (empty($settings['storageClass'])) {
$settings['storageClass'] = 'STANDARD'; // value of \craft\base\Volume::STORAGE_STANDARD
- Craft::$app->getDb()->createCommand()
- ->update(
- Table::VOLUMES,
- ['settings' => Json::encode($settings)],
- ['id' => $volume['id']])
- ->execute();
+ Db::update(Table::VOLUMES, [
+ 'settings' => Json::encode($settings)
+ ], [
+ 'id' => $volume['id'],
+ ], [], true, $this->db);
}
}
}
diff --git a/src/migrations/m160804_110002_userphotos_to_assets.php b/src/migrations/m160804_110002_userphotos_to_assets.php
index d81b242455b..0d86f5f6371 100644
--- a/src/migrations/m160804_110002_userphotos_to_assets.php
+++ b/src/migrations/m160804_110002_userphotos_to_assets.php
@@ -193,9 +193,7 @@ private function _createUserphotoVolume(): int
];
$db = Craft::$app->getDb();
- $db->createCommand()
- ->insert(Table::VOLUMES, $volumeData)
- ->execute();
+ Db::insert(Table::VOLUMES, $volumeData);
$volumeId = $db->getLastInsertID();
@@ -205,9 +203,7 @@ private function _createUserphotoVolume(): int
'name' => $name,
'path' => null
];
- $db->createCommand()
- ->insert(Table::VOLUMEFOLDERS, $folderData)
- ->execute();
+ Db::insert(Table::VOLUMEFOLDERS, $folderData);
return $volumeId;
}
@@ -266,8 +262,8 @@ private function _convertPhotosToAssets(int $volumeId, array $userList): array
$assetExists = (new Query())
->select(['assets.id'])
- ->from(['{{%assets}} assets'])
- ->innerJoin('{{%volumefolders}} volumefolders', '[[volumefolders.id]] = [[assets.folderId]]')
+ ->from(['assets' => Table::ASSETS])
+ ->innerJoin(['volumefolders' => Table::VOLUMEFOLDERS], '[[volumefolders.id]] = [[assets.folderId]]')
->where([
'assets.folderId' => $folderId,
'filename' => $user['photo']
@@ -280,9 +276,7 @@ private function _convertPhotosToAssets(int $volumeId, array $userList): array
'enabled' => 1,
'archived' => 0
];
- $db->createCommand()
- ->insert(Table::ELEMENTS, $elementData)
- ->execute();
+ Db::insert(Table::ELEMENTS, $elementData);
$elementId = $db->getLastInsertID();
@@ -294,18 +288,14 @@ private function _convertPhotosToAssets(int $volumeId, array $userList): array
'uri' => null,
'enabled' => 1
];
- $db->createCommand()
- ->insert('{{%elements_i18n}}', $elementI18nData)
- ->execute();
+ Db::insert('{{%elements_i18n}}', $elementI18nData);
$contentData = [
'elementId' => $elementId,
'locale' => $locale,
'title' => AssetsHelper::filename2Title(pathinfo($user['photo'], PATHINFO_FILENAME))
];
- $db->createCommand()
- ->insert(Table::CONTENT, $contentData)
- ->execute();
+ Db::insert(Table::CONTENT, $contentData);
}
$imageSize = Image::imageSize($filePath);
@@ -320,9 +310,7 @@ private function _convertPhotosToAssets(int $volumeId, array $userList): array
'height' => $imageSize[1],
'dateModified' => Db::prepareDateForDb(filemtime($filePath))
];
- $db->createCommand()
- ->insert(Table::ASSETS, $assetData)
- ->execute();
+ Db::insert(Table::ASSETS, $assetData);
$changes[$user['id']] = $elementId;
}
@@ -339,11 +327,12 @@ private function _convertPhotosToAssets(int $volumeId, array $userList): array
private function _setPhotoIdValues(array $userlist)
{
if (is_array($userlist)) {
- $db = Craft::$app->getDb();
foreach ($userlist as $userId => $assetId) {
- $db->createCommand()
- ->update(Table::USERS, ['photoId' => $assetId], ['id' => $userId])
- ->execute();
+ Db::update(Table::USERS, [
+ 'photoId' => $assetId,
+ ], [
+ 'id' => $userId,
+ ], [], true, $this->db);
}
}
}
diff --git a/src/migrations/m160807_144858_sites.php b/src/migrations/m160807_144858_sites.php
index 8835fa39a3c..50c8440c021 100644
--- a/src/migrations/m160807_144858_sites.php
+++ b/src/migrations/m160807_144858_sites.php
@@ -529,7 +529,7 @@ protected function locale2handle(string $locale): string
$localeParts = array_filter(preg_split('/[^a-zA-Z0-9]/', $locale));
// Prefix with a random string so there's no chance of a conflict with other locales
- return StringHelper::randomStringWithChars('abcdefghijklmnopqrstuvwxyz', 7) . ($localeParts ? '_' . implode('_', $localeParts) : '');
+ return StringHelper::randomString(7) . ($localeParts ? '_' . implode('_', $localeParts) : '');
}
return $locale;
diff --git a/src/migrations/m160829_000000_pending_user_content_cleanup.php b/src/migrations/m160829_000000_pending_user_content_cleanup.php
index 20f72654e99..ccae5b75cff 100644
--- a/src/migrations/m160829_000000_pending_user_content_cleanup.php
+++ b/src/migrations/m160829_000000_pending_user_content_cleanup.php
@@ -22,8 +22,8 @@ public function safeUp(): bool
// Find any orphaned entries.
$ids = (new Query())
->select(['el.id'])
- ->from(['{{%elements}} el'])
- ->leftJoin('{{%entries}} en', '[[en.id]] = [[el.id]]')
+ ->from(['el' => Table::ELEMENTS])
+ ->leftJoin(['en' => Table::ENTRIES], '[[en.id]] = [[el.id]]')
->where([
'el.type' => Entry::class,
'en.id' => null
diff --git a/src/migrations/m160912_230520_require_entry_type_id.php b/src/migrations/m160912_230520_require_entry_type_id.php
index fc31752b3f2..8c5bda2fc90 100644
--- a/src/migrations/m160912_230520_require_entry_type_id.php
+++ b/src/migrations/m160912_230520_require_entry_type_id.php
@@ -21,7 +21,7 @@ public function safeUp()
// Get all of the sections' primary entry type IDs
$subQuery = (new Query())
->select(['et.id'])
- ->from(['{{%entrytypes}} et'])
+ ->from(['et' => Table::ENTRYTYPES])
->where('[[et.sectionId]] = [[s.id]]')
->orderBy(['sortOrder' => SORT_ASC])
->limit(1);
@@ -31,7 +31,7 @@ public function safeUp()
'sectionId' => 's.id',
'typeId' => $subQuery
])
- ->from(['{{%sections}} s'])
+ ->from(['s' => Table::SECTIONS])
->all($this->db);
if (!empty($results)) {
diff --git a/src/migrations/m170306_150500_asset_temporary_uploads.php b/src/migrations/m170306_150500_asset_temporary_uploads.php
index 5baf356b319..64b4bdb7a0c 100644
--- a/src/migrations/m170306_150500_asset_temporary_uploads.php
+++ b/src/migrations/m170306_150500_asset_temporary_uploads.php
@@ -26,8 +26,8 @@ public function safeUp()
// Get indexed temporary uploads
$assets = (new Query())
->select(['assets.id', 'assets.filename', 'assets.folderId', 'volumeFolders.path'])
- ->from('{{%assets}} assets')
- ->innerJoin('{{%volumefolders}} volumeFolders', $folderId . ' = ' . $volumeFoldersId)
+ ->from(['assets' => Table::ASSETS])
+ ->innerJoin(['volumeFolders' => Table::VOLUMEFOLDERS], $folderId . ' = ' . $volumeFoldersId)
->where(['assets.volumeId' => null])
->all($this->db);
diff --git a/src/migrations/m171107_000000_assign_group_permissions.php b/src/migrations/m171107_000000_assign_group_permissions.php
index 71ba5ae45c6..1ebd6dc23c4 100644
--- a/src/migrations/m171107_000000_assign_group_permissions.php
+++ b/src/migrations/m171107_000000_assign_group_permissions.php
@@ -19,15 +19,15 @@ public function safeUp()
// See which users & groups already have the "assignUserPermissions" permission
$userIds = (new Query())
->select(['up_u.userId'])
- ->from(['{{%userpermissions_users}} up_u'])
- ->innerJoin('{{%userpermissions}} up', '[[up.id]] = [[up_u.permissionId]]')
+ ->from(['up_u' => Table::USERPERMISSIONS_USERS])
+ ->innerJoin(['up' => Table::USERPERMISSIONS], '[[up.id]] = [[up_u.permissionId]]')
->where(['up.name' => 'assignuserpermissions'])
->column($this->db);
$groupIds = (new Query())
->select(['up_ug.groupId'])
- ->from(['{{%userpermissions_usergroups}} up_ug'])
- ->innerJoin('{{%userpermissions}} up', '[[up.id]] = [[up_ug.permissionId]]')
+ ->from(['up_ug' => Table::USERPERMISSIONS_USERGROUPS])
+ ->innerJoin(['up' => Table::USERPERMISSIONS], '[[up.id]] = [[up_ug.permissionId]]')
->where(['up.name' => 'assignuserpermissions'])
->column($this->db);
diff --git a/src/migrations/m180128_235202_set_tag_slugs.php b/src/migrations/m180128_235202_set_tag_slugs.php
index e51b1439604..00ca87a1bb8 100644
--- a/src/migrations/m180128_235202_set_tag_slugs.php
+++ b/src/migrations/m180128_235202_set_tag_slugs.php
@@ -2,9 +2,9 @@
namespace craft\migrations;
-use Craft;
use craft\db\Migration;
use craft\elements\Tag;
+use craft\helpers\Queue;
use craft\queue\jobs\ResaveElements;
/**
@@ -17,7 +17,7 @@ class m180128_235202_set_tag_slugs extends Migration
*/
public function safeUp()
{
- Craft::$app->getQueue()->push(new ResaveElements([
+ Queue::push(new ResaveElements([
'elementType' => Tag::class,
'criteria' => [
'slug' => ':empty:',
diff --git a/src/migrations/m180521_173000_initial_yml_and_snapshot.php b/src/migrations/m180521_173000_initial_yml_and_snapshot.php
index 76a18a00532..df45c7a286d 100644
--- a/src/migrations/m180521_173000_initial_yml_and_snapshot.php
+++ b/src/migrations/m180521_173000_initial_yml_and_snapshot.php
@@ -30,8 +30,8 @@ public function safeUp()
$configFile = Craft::$app->getPath()->getProjectConfigFilePath();
if (file_exists($configFile)) {
// Make a backup of the old config
- $backupFile = pathinfo(ProjectConfig::CONFIG_FILENAME, PATHINFO_FILENAME) . date('-Ymh-His') . '.yaml';
- echo " > renaming project.yaml to {$backupFile} and moving to config backup folder ... ";
+ $backupFile = pathinfo($projectConfig->filename, PATHINFO_FILENAME) . date('-Ymh-His') . '.yaml';
+ echo " > renaming $projectConfig->filename to $backupFile and moving to config backup folder ... ";
rename($configFile, Craft::$app->getPath()->getConfigBackupPath() . '/' . $backupFile);
// Forget everything we knew about the old config
@@ -134,8 +134,8 @@ private function _getSiteData(): array
'sites.primary',
'siteGroups.uid AS siteGroup',
])
- ->from(['{{%sites}} sites'])
- ->innerJoin('{{%sitegroups}} siteGroups', '[[sites.groupId]] = [[siteGroups.id]]')
+ ->from(['sites' => Table::SITES])
+ ->innerJoin(['siteGroups' => Table::SITEGROUPS], '[[siteGroups.id]] = [[sites.groupId]]')
->all();
foreach ($sites as $site) {
@@ -171,8 +171,8 @@ private function _getSectionData(): array
'structures.uid AS structure',
'structures.maxLevels AS structureMaxLevels',
])
- ->from(['{{%sections}} sections'])
- ->leftJoin('{{%structures}} structures', '[[structures.id]] = [[sections.structureId]]')
+ ->from(['sections' => Table::SECTIONS])
+ ->leftJoin(['structures' => Table::STRUCTURES], '[[structures.id]] = [[sections.structureId]]')
->all();
$sectionData = [];
@@ -207,9 +207,9 @@ private function _getSectionData(): array
'sites.uid AS siteUid',
'sections.uid AS sectionUid',
])
- ->from(['{{%sections_sites}} sections_sites'])
- ->innerJoin('{{%sites}} sites', '[[sites.id]] = [[sections_sites.siteId]]')
- ->innerJoin('{{%sections}} sections', '[[sections.id]] = [[sections_sites.sectionId]]')
+ ->from(['sections_sites' => Table::SECTIONS_SITES])
+ ->innerJoin(['sites' => Table::SITES], '[[sites.id]] = [[sections_sites.siteId]]')
+ ->innerJoin(['sections' => Table::SECTIONS], '[[sections.id]] = [[sections_sites.sectionId]]')
->all();
foreach ($sectionSiteRows as $sectionSiteRow) {
@@ -235,8 +235,8 @@ private function _getSectionData(): array
'entrytypes.uid',
'sections.uid AS sectionUid',
])
- ->from(['{{%entrytypes}} as entrytypes'])
- ->innerJoin('{{%sections}} sections', '[[sections.id]] = [[entrytypes.sectionId]]')
+ ->from(['entrytypes' => Table::ENTRYTYPES])
+ ->innerJoin(['sections' => Table::SECTIONS], '[[sections.id]] = [[entrytypes.sectionId]]')
->all();
$layoutIds = array_filter(ArrayHelper::getColumn($entryTypeRows, 'fieldLayoutId'));
@@ -310,8 +310,8 @@ private function _getFieldData(): array
'fields.uid',
'fieldGroups.uid AS fieldGroup',
])
- ->from(['{{%fields}} fields'])
- ->leftJoin('{{%fieldgroups}} fieldGroups', '[[fields.groupId]] = [[fieldGroups.id]]')
+ ->from(['fields' => Table::FIELDS])
+ ->leftJoin(['fieldGroups' => Table::FIELDGROUPS], '[[fieldGroups.id]] = [[fields.groupId]]')
->all();
$fields = [];
@@ -356,8 +356,8 @@ private function _getMatrixBlockTypeData(): array
'bt.uid',
'f.uid AS field',
])
- ->from(['{{%matrixblocktypes}} bt'])
- ->innerJoin('{{%fields}} f', '[[bt.fieldId]] = [[f.id]]')
+ ->from(['bt' => Table::MATRIXBLOCKTYPES])
+ ->innerJoin(['f' => Table::FIELDS], '[[f.id]] = [[bt.fieldId]]')
->all();
$layoutIds = [];
@@ -408,7 +408,7 @@ private function _getVolumeData(): array
'volumes.sortOrder',
'volumes.uid',
])
- ->from(['{{%volumes}} volumes'])
+ ->from(['volumes' => Table::VOLUMES])
->all();
$layoutIds = [];
@@ -536,8 +536,8 @@ private function _getCategoryGroupData(): array
'structures.uid AS structure',
'structures.maxLevels AS structureMaxLevels',
])
- ->from(['{{%categorygroups}} groups'])
- ->leftJoin('{{%structures}} structures', '[[structures.id]] = [[groups.structureId]]')
+ ->from(['groups' => Table::CATEGORYGROUPS])
+ ->leftJoin(['structures' => Table::STRUCTURES], '[[structures.id]] = [[groups.structureId]]')
->all();
$groupData = [];
@@ -581,9 +581,9 @@ private function _getCategoryGroupData(): array
'sites.uid AS siteUid',
'groups.uid AS groupUid',
])
- ->from(['{{%categorygroups_sites}} groups_sites'])
- ->innerJoin('{{%sites}} sites', '[[sites.id]] = [[groups_sites.siteId]]')
- ->innerJoin('{{%categorygroups}} groups', '[[groups.id]] = [[groups_sites.groupId]]')
+ ->from(['groups_sites' => Table::CATEGORYGROUPS_SITES])
+ ->innerJoin(['sites' => Table::SITES], '[[sites.id]] = [[groups_sites.siteId]]')
+ ->innerJoin(['groups' => Table::CATEGORYGROUPS], '[[groups.id]] = [[groups_sites.groupId]]')
->all();
foreach ($groupSiteRows as $groupSiteRow) {
@@ -613,7 +613,7 @@ private function _getTagGroupData(): array
'groups.uid',
'groups.fieldLayoutId',
])
- ->from(['{{%taggroups}} groups'])
+ ->from(['groups' => Table::TAGGROUPS])
->all();
$groupData = [];
@@ -655,7 +655,7 @@ private function _getGlobalSetData(): array
'sets.uid',
'sets.fieldLayoutId',
])
- ->from(['{{%globalsets}} sets'])
+ ->from(['sets' => Table::GLOBALSETS])
->all();
$setData = [];
@@ -753,10 +753,10 @@ private function _generateFieldLayoutArray(array $layoutIds): array
'tabs.uid AS tabUid',
'layouts.id AS layoutId',
])
- ->from(['{{%fieldlayoutfields}} AS layoutFields'])
- ->innerJoin('{{%fieldlayouttabs}} AS tabs', '[[layoutFields.tabId]] = [[tabs.id]]')
- ->innerJoin('{{%fieldlayouts}} AS layouts', '[[layoutFields.layoutId]] = [[layouts.id]]')
- ->innerJoin('{{%fields}} AS fields', '[[layoutFields.fieldId]] = [[fields.id]]')
+ ->from(['layoutFields' => Table::FIELDLAYOUTFIELDS])
+ ->innerJoin(['tabs' => Table::FIELDLAYOUTTABS], '[[tabs.id]] = [[layoutFields.tabId]]')
+ ->innerJoin(['layouts' => Table::FIELDLAYOUTS], '[[layouts.id]] = [[layoutFields.layoutId]]')
+ ->innerJoin(['fields' => Table::FIELDS], '[[fields.id]] = [[layoutFields.fieldId]]')
->where(['layouts.id' => $layoutIds])
->orderBy(['tabs.sortOrder' => SORT_ASC, 'layoutFields.sortOrder' => SORT_ASC])
->all();
diff --git a/src/migrations/m180901_151639_fix_matrixcontent_tables.php b/src/migrations/m180901_151639_fix_matrixcontent_tables.php
index 63e39c1a3d8..850abf75b60 100644
--- a/src/migrations/m180901_151639_fix_matrixcontent_tables.php
+++ b/src/migrations/m180901_151639_fix_matrixcontent_tables.php
@@ -136,9 +136,9 @@ private function _cleanUpTable(int $fieldId, string $tableName, array $originalF
// get all of the columns this field needs
$subFields = (new Query())
->select(['f.handle', 'mbt.handle as blockTypeHandle'])
- ->from(['{{%fields}} f'])
- ->innerJoin('{{%fieldlayoutfields}} flf', '[[flf.fieldId]] = [[f.id]]')
- ->innerJoin('{{%matrixblocktypes}} mbt', '[[mbt.fieldLayoutId]] = [[flf.layoutId]]')
+ ->from(['f' => Table::FIELDS])
+ ->innerJoin(['flf' => Table::FIELDLAYOUTFIELDS], '[[flf.fieldId]] = [[f.id]]')
+ ->innerJoin(['mbt' => Table::MATRIXBLOCKTYPES], '[[mbt.fieldLayoutId]] = [[flf.layoutId]]')
->where(['mbt.fieldId' => $fieldId])
->all();
diff --git a/src/migrations/m181211_143040_fix_entry_type_uids.php b/src/migrations/m181211_143040_fix_entry_type_uids.php
index 8c7a754b78c..71e31b93b5c 100644
--- a/src/migrations/m181211_143040_fix_entry_type_uids.php
+++ b/src/migrations/m181211_143040_fix_entry_type_uids.php
@@ -27,8 +27,8 @@ public function safeUp()
// Get all entry types from the DB
$dbEntryTypes = (new Query())
->select(['et.id', 'et.uid', 'et.handle', 's.uid as sectionUid'])
- ->from(['{{%entrytypes}} et'])
- ->innerJoin('{{%sections}} s', '[[s.id]] = [[et.sectionId]]')
+ ->from(['et' => Table::ENTRYTYPES])
+ ->innerJoin(['s' => Table::SECTIONS], '[[s.id]] = [[et.sectionId]]')
->all();
// Index by section UID, handle
diff --git a/src/migrations/m190108_113000_asset_field_setting_change.php b/src/migrations/m190108_113000_asset_field_setting_change.php
index 5bee649f790..9e6d9c4372a 100644
--- a/src/migrations/m190108_113000_asset_field_setting_change.php
+++ b/src/migrations/m190108_113000_asset_field_setting_change.php
@@ -35,8 +35,8 @@ public function safeUp()
$this->_volumesByFolderUids = (new Query())
->select(['folders.uid folderUid', 'volumes.uid volumeUid'])
- ->from(['{{%volumes}} volumes'])
- ->innerJoin(['{{%volumefolders}} folders'], '[[volumes.id]] = [[folders.volumeId]]')
+ ->from(['volumes' => Table::VOLUMES])
+ ->innerJoin(['folders' => Table::VOLUMEFOLDERS], '[[folders.volumeId]] = [[volumes.id]]')
->pairs();
$projectConfig = Craft::$app->getProjectConfig();
diff --git a/src/migrations/m190114_143000_more_asset_field_setting_changes.php b/src/migrations/m190114_143000_more_asset_field_setting_changes.php
index 42cf4924fa2..43cc44aca7b 100644
--- a/src/migrations/m190114_143000_more_asset_field_setting_changes.php
+++ b/src/migrations/m190114_143000_more_asset_field_setting_changes.php
@@ -35,8 +35,8 @@ public function safeUp()
$this->_volumesByFolderUids = (new Query())
->select(['folders.uid folderUid', 'volumes.uid volumeUid'])
- ->from(['{{%volumes}} volumes'])
- ->innerJoin(['{{%volumefolders}} folders'], '[[volumes.id]] = [[folders.volumeId]]')
+ ->from(['volumes' => Table::VOLUMES])
+ ->innerJoin(['folders' => Table::VOLUMEFOLDERS], '[[folders.volumeId]] = [[volumes.id]]')
->pairs();
$projectConfig = Craft::$app->getProjectConfig();
diff --git a/src/migrations/m190218_143000_element_index_settings_uid.php b/src/migrations/m190218_143000_element_index_settings_uid.php
index d0d4d6a9b80..58f4ef3b070 100644
--- a/src/migrations/m190218_143000_element_index_settings_uid.php
+++ b/src/migrations/m190218_143000_element_index_settings_uid.php
@@ -2,7 +2,6 @@
namespace craft\migrations;
-use Craft;
use craft\db\Migration;
use craft\db\Query;
use craft\db\Table;
@@ -10,6 +9,7 @@
use craft\elements\Category;
use craft\elements\Entry;
use craft\elements\User;
+use craft\helpers\Db;
use craft\helpers\Json;
/**
@@ -80,9 +80,11 @@ public function safeUp()
$data = Json::encode($data);
- Craft::$app->getDb()->createCommand()
- ->update(Table::ELEMENTINDEXSETTINGS, ['settings' => $data], ['id' => $row['id']], [], false)
- ->execute();
+ Db::update(Table::ELEMENTINDEXSETTINGS, [
+ 'settings' => $data,
+ ], [
+ 'id' => $row['id'],
+ ], [], false, $this->db);
}
}
}
diff --git a/src/migrations/m190312_152740_element_revisions.php b/src/migrations/m190312_152740_element_revisions.php
index 48ad26197ce..8fa45c3be26 100644
--- a/src/migrations/m190312_152740_element_revisions.php
+++ b/src/migrations/m190312_152740_element_revisions.php
@@ -2,9 +2,9 @@
namespace craft\migrations;
-use Craft;
use craft\db\Migration;
use craft\db\Table;
+use craft\helpers\Queue;
use craft\queue\jobs\ConvertEntryRevisions;
/**
@@ -66,7 +66,7 @@ public function safeUp()
$this->addForeignKey(null, '{{%entryversionerrors}}', ['versionId'], Table::ENTRYVERSIONS, ['id'], 'CASCADE');
// Queue up a ConvertEntryRevisions job
- Craft::$app->getQueue()->push(new ConvertEntryRevisions());
+ Queue::push(new ConvertEntryRevisions());
}
/**
diff --git a/src/migrations/m190417_085010_add_image_editor_permissions.php b/src/migrations/m190417_085010_add_image_editor_permissions.php
index 9050ec1fd3e..a97406974fe 100644
--- a/src/migrations/m190417_085010_add_image_editor_permissions.php
+++ b/src/migrations/m190417_085010_add_image_editor_permissions.php
@@ -94,15 +94,15 @@ public function safeUp()
// Get all the eligible users
$userIds = (new Query())
->select(['users.id'])
- ->from([Table::USERS . ' AS users'])
- ->innerJoin(Table::USERPERMISSIONS_USERS . ' AS saveUserPermissions', '[[saveUserPermissions.userId]] = [[users.id]]')
- ->innerJoin(Table::USERPERMISSIONS . ' AS saveInVolume', [
+ ->from(['users' => Table::USERS])
+ ->innerJoin(['saveUserPermissions' => Table::USERPERMISSIONS_USERS], '[[saveUserPermissions.userId]] = [[users.id]]')
+ ->innerJoin(['saveInVolume' => Table::USERPERMISSIONS], [
'and',
'[[saveInVolume.id]] = [[saveUserPermissions.permissionId]]',
['saveInVolume.name' => $savePermission]
])
- ->innerJoin(Table::USERPERMISSIONS_USERS . ' AS deleteUserPermissions', '[[deleteUserPermissions.userId]] = [[users.id]]')
- ->innerJoin(Table::USERPERMISSIONS . ' AS deleteInVolume', [
+ ->innerJoin(['deleteUserPermissions' => Table::USERPERMISSIONS_USERS], '[[deleteUserPermissions.userId]] = [[users.id]]')
+ ->innerJoin(['deleteInVolume' => Table::USERPERMISSIONS], [
'and',
'[[deleteInVolume.id]] = [[deleteUserPermissions.permissionId]]',
['deleteInVolume.name' => $deletePermission]
diff --git a/src/migrations/m191216_191635_asset_upload_tracking.php b/src/migrations/m191216_191635_asset_upload_tracking.php
index 9c01dfc3133..1560b64b707 100644
--- a/src/migrations/m191216_191635_asset_upload_tracking.php
+++ b/src/migrations/m191216_191635_asset_upload_tracking.php
@@ -2,7 +2,6 @@
namespace craft\migrations;
-use Craft;
use craft\db\Migration;
use craft\db\Table;
use craft\helpers\MigrationHelper;
diff --git a/src/migrations/m191222_002848_peer_asset_permissions.php b/src/migrations/m191222_002848_peer_asset_permissions.php
index 398f69a38e1..6caa95ec6ed 100644
--- a/src/migrations/m191222_002848_peer_asset_permissions.php
+++ b/src/migrations/m191222_002848_peer_asset_permissions.php
@@ -3,7 +3,6 @@
namespace craft\migrations;
use Craft;
-use craft\base\Volume;
use craft\db\Migration;
use craft\db\Query;
use craft\db\Table;
@@ -22,7 +21,6 @@ public function safeUp()
// mapped to the old permissions users/groups must have to gain the new ones
$newPermissions = [];
foreach (Craft::$app->getVolumes()->getAllVolumes() as $volume) {
- /** @var Volume $volume */
$suffix = ":{$volume->uid}";
$newPermissions += [
"viewvolume{$suffix}" => "viewpeerfilesinvolume{$suffix}",
@@ -37,7 +35,7 @@ public function safeUp()
$userIds = (new Query())
->select(['upu.userId'])
->from(['upu' => Table::USERPERMISSIONS_USERS])
- ->innerJoin(Table::USERPERMISSIONS . ' up', '[[up.id]] = [[upu.permissionId]]')
+ ->innerJoin(['up' => Table::USERPERMISSIONS], '[[up.id]] = [[upu.permissionId]]')
->where(['up.name' => $oldPermission])
->column($this->db);
if (!empty($userIds)) {
diff --git a/src/migrations/m200127_172522_queue_channels.php b/src/migrations/m200127_172522_queue_channels.php
index 408b8bffebb..4a0dc143930 100644
--- a/src/migrations/m200127_172522_queue_channels.php
+++ b/src/migrations/m200127_172522_queue_channels.php
@@ -2,7 +2,6 @@
namespace craft\migrations;
-use Craft;
use craft\db\Migration;
use craft\db\Table;
use craft\helpers\MigrationHelper;
diff --git a/src/migrations/m200228_195211_long_deprecation_messages.php b/src/migrations/m200228_195211_long_deprecation_messages.php
index 7e770f68057..66a9f292d5a 100644
--- a/src/migrations/m200228_195211_long_deprecation_messages.php
+++ b/src/migrations/m200228_195211_long_deprecation_messages.php
@@ -2,7 +2,6 @@
namespace craft\migrations;
-use Craft;
use craft\db\Migration;
use craft\db\Table;
diff --git a/src/migrations/m200306_054652_disabled_sites.php b/src/migrations/m200306_054652_disabled_sites.php
new file mode 100644
index 00000000000..381cf0bd2fd
--- /dev/null
+++ b/src/migrations/m200306_054652_disabled_sites.php
@@ -0,0 +1,28 @@
+addColumn(Table::SITES, 'enabled', $this->boolean()->notNull()->defaultValue(true)->after('primary'));
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function safeDown()
+ {
+ $this->dropColumn(Table::SITES, 'enabled');
+ }
+}
diff --git a/src/models/FieldLayout.php b/src/models/FieldLayout.php
index a7c93a2973c..1401f20fc84 100644
--- a/src/models/FieldLayout.php
+++ b/src/models/FieldLayout.php
@@ -8,7 +8,6 @@
namespace craft\models;
use Craft;
-use craft\base\Field;
use craft\base\FieldInterface;
use craft\base\Model;
@@ -89,7 +88,6 @@ public function getConfig()
'sortOrder' => (int)$tab->sortOrder,
];
- /** @var Field $field */
foreach ($tab->getFields() as $field) {
$tabData['fields'][$field->uid] = [
'required' => (bool)$field->required,
@@ -126,7 +124,6 @@ public static function createFromConfig(array $config): self
$layoutFields = [];
foreach ($tab['fields'] as $uid => $field) {
- /** @var Field $createdField */
$createdField = $fieldService->getFieldByUid($uid);
if ($createdField) {
@@ -177,7 +174,6 @@ public function getFieldIds(): array
$ids = [];
foreach ($this->getFields() as $field) {
- /** @var Field $field */
$ids[] = $field->id;
}
@@ -188,12 +184,11 @@ public function getFieldIds(): array
* Returns a field by its handle.
*
* @param string $handle The field handle.
- * @return Field|FieldInterface|null
+ * @return FieldInterface|null
*/
public function getFieldByHandle(string $handle)
{
foreach ($this->getFields() as $field) {
- /** @var Field $field */
if ($field->handle === $handle) {
return $field;
}
diff --git a/src/models/FieldLayoutTab.php b/src/models/FieldLayoutTab.php
index 27531fceced..2ebffb3c6d0 100644
--- a/src/models/FieldLayoutTab.php
+++ b/src/models/FieldLayoutTab.php
@@ -8,9 +8,7 @@
namespace craft\models;
use Craft;
-use craft\base\Element;
use craft\base\ElementInterface;
-use craft\base\Field;
use craft\base\FieldInterface;
use craft\base\Model;
use craft\helpers\StringHelper;
@@ -119,7 +117,6 @@ public function getFields(): array
if ($layout = $this->getLayout()) {
foreach ($layout->getFields() as $field) {
- /** @var Field $field */
if ($field->tabId == $this->id) {
$this->_fields[] = $field;
}
@@ -158,13 +155,11 @@ public function getHtmlId(): string
*/
public function elementHasErrors(ElementInterface $element): bool
{
- /** @var Element $element */
if (!$element->hasErrors()) {
return false;
}
foreach ($this->getFields() as $field) {
- /** @var Field $field */
if ($element->hasErrors("{$field->handle}.*")) {
return true;
}
diff --git a/src/models/Section.php b/src/models/Section.php
index a86e8bb8af0..d1c7ccc901d 100644
--- a/src/models/Section.php
+++ b/src/models/Section.php
@@ -37,6 +37,10 @@ class Section extends Model
const PROPAGATION_METHOD_SITE_GROUP = 'siteGroup';
const PROPAGATION_METHOD_LANGUAGE = 'language';
const PROPAGATION_METHOD_ALL = 'all';
+ /**
+ * @since 3.5.0
+ */
+ const PROPAGATION_METHOD_CUSTOM = 'custom';
/**
* @var int|null ID
@@ -168,7 +172,8 @@ protected function defineRules(): array
self::PROPAGATION_METHOD_NONE,
self::PROPAGATION_METHOD_SITE_GROUP,
self::PROPAGATION_METHOD_LANGUAGE,
- self::PROPAGATION_METHOD_ALL
+ self::PROPAGATION_METHOD_ALL,
+ self::PROPAGATION_METHOD_CUSTOM,
]
];
$rules[] = [['name', 'handle'], UniqueValidator::class, 'targetClass' => SectionRecord::class];
diff --git a/src/models/Section_SiteSettings.php b/src/models/Section_SiteSettings.php
index cc4e34fd44d..0659a09630e 100644
--- a/src/models/Section_SiteSettings.php
+++ b/src/models/Section_SiteSettings.php
@@ -62,6 +62,22 @@ class Section_SiteSettings extends Model
*/
private $_section;
+ /**
+ * @inheritdoc
+ * @since 3.5.0
+ */
+ public function init()
+ {
+ // Typecast DB values
+ $this->id = (int)$this->id ?: null;
+ $this->sectionId = (int)$this->sectionId ?: null;
+ $this->siteId = (int)$this->siteId ?: null;
+ $this->enabledByDefault = (bool)$this->enabledByDefault;
+ $this->hasUrls = (bool)$this->hasUrls;
+
+ parent::init();
+ }
+
/**
* Returns the section.
*
diff --git a/src/models/Site.php b/src/models/Site.php
index a355cb9dd76..f53fb808a56 100644
--- a/src/models/Site.php
+++ b/src/models/Site.php
@@ -55,6 +55,12 @@ class Site extends Model
*/
public $primary = false;
+ /**
+ * @var bool Enabled?
+ * @since 3.5.0
+ */
+ public $enabled = true;
+
/**
* @var bool Has URLs
*/
@@ -95,6 +101,23 @@ class Site extends Model
*/
public $dateUpdated;
+ /**
+ * @inheritdoc
+ * @since 3.5.0
+ */
+ public function init()
+ {
+ // Typecast DB values
+ $this->id = (int)$this->id ?: null;
+ $this->groupId = (int)$this->groupId ?: null;
+ $this->primary = (bool)$this->primary;
+ $this->enabled = (bool)$this->enabled;
+ $this->hasUrls = (bool)$this->hasUrls;
+ $this->sortOrder = (int)$this->sortOrder;
+
+ parent::init();
+ }
+
/**
* Returns the site’s base URL.
*
@@ -155,6 +178,14 @@ protected function defineRules(): array
$rules[] = [['name', 'handle'], UniqueValidator::class, 'targetClass' => SiteRecord::class];
}
+ $rules[] = [
+ ['enabled'], function(string $attribute) {
+ if ($this->primary && !$this->enabled) {
+ $this->addError($attribute, Craft::t('app', 'The primary site cannot be disabled.'));
+ }
+ }
+ ];
+
return $rules;
}
diff --git a/src/queue/Queue.php b/src/queue/Queue.php
index cbcc1788a9b..fad1328db27 100644
--- a/src/queue/Queue.php
+++ b/src/queue/Queue.php
@@ -192,24 +192,18 @@ public function push($job)
*/
public function retry(string $id)
{
- $this->db->createCommand()
- ->update(
- $this->tableName,
- [
- 'dateReserved' => null,
- 'timeUpdated' => null,
- 'progress' => 0,
- 'progressLabel' => null,
- 'attempt' => 0,
- 'fail' => false,
- 'dateFailed' => null,
- 'error' => null,
- ],
- ['id' => $id],
- [],
- false
- )
- ->execute();
+ Db::update($this->tableName, [
+ 'dateReserved' => null,
+ 'timeUpdated' => null,
+ 'progress' => 0,
+ 'progressLabel' => null,
+ 'attempt' => 0,
+ 'fail' => false,
+ 'dateFailed' => null,
+ 'error' => null,
+ ], [
+ 'id' => $id,
+ ], [], false, $this->db);
}
/**
@@ -220,27 +214,19 @@ public function retryAll()
// Move expired messages into waiting list
$this->_moveExpired();
- $this->db->createCommand()
- ->update(
- $this->tableName,
- [
- 'dateReserved' => null,
- 'timeUpdated' => null,
- 'progress' => 0,
- 'progressLabel' => null,
- 'attempt' => 0,
- 'fail' => false,
- 'dateFailed' => null,
- 'error' => null,
- ],
- [
- 'channel' => $this->channel,
- 'fail' => true,
- ],
- [],
- false
- )
- ->execute();
+ Db::update($this->tableName, [
+ 'dateReserved' => null,
+ 'timeUpdated' => null,
+ 'progress' => 0,
+ 'progressLabel' => null,
+ 'attempt' => 0,
+ 'fail' => false,
+ 'dateFailed' => null,
+ 'error' => null,
+ ], [
+ 'channel' => $this->channel,
+ 'fail' => true,
+ ], [], false, $this->db);
}
/**
@@ -248,9 +234,9 @@ public function retryAll()
*/
public function release(string $id)
{
- $this->db->createCommand()
- ->delete($this->tableName, ['id' => $id])
- ->execute();
+ Db::delete($this->tableName, [
+ 'id' => $id,
+ ], [], $this->db);
}
/**
@@ -258,9 +244,9 @@ public function release(string $id)
*/
public function releaseAll()
{
- $this->db->createCommand()
- ->delete($this->tableName, ['channel' => $this->channel])
- ->execute();
+ Db::delete($this->tableName, [
+ 'channel' => $this->channel,
+ ], [], $this->db);
}
/**
@@ -277,9 +263,9 @@ public function setProgress(int $progress, string $label = null)
$data['progressLabel'] = $label;
}
- $this->db->createCommand()
- ->update($this->tableName, $data, ['id' => $this->_executingJobId], [], false)
- ->execute();
+ Db::update($this->tableName, $data, [
+ 'id' => $this->_executingJobId,
+ ], [], false, $this->db);
}
/**
@@ -448,19 +434,13 @@ public function handleError(ExecEvent $event)
// Have we given up?
if (parent::handleError($event)) {
// Mark the job as failed
- $this->db->createCommand()
- ->update(
- $this->tableName,
- [
- 'fail' => true,
- 'dateFailed' => Db::prepareDateForDb(new \DateTime()),
- 'error' => $event->error ? $event->error->getMessage() : null,
- ],
- ['id' => $event->id],
- [],
- false
- )
- ->execute();
+ Db::update($this->tableName, [
+ 'fail' => true,
+ 'dateFailed' => Db::prepareDateForDb(new \DateTime()),
+ 'error' => $event->error ? $event->error->getMessage() : null,
+ ], [
+ 'id' => $event->id,
+ ], [], false, $this->db);
}
// Don't tell run() to release the job
@@ -544,10 +524,7 @@ protected function pushMessage($message, $ttr, $delay, $priority)
$data['channel'] = $this->channel;
}
- $this->db->createCommand()
- ->insert($this->tableName, $data, false)
- ->execute();
-
+ Db::insert($this->tableName, $data, false, $this->db);
return $this->db->getLastInsertID($this->tableName);
}
@@ -576,19 +553,13 @@ protected function reserve()
$payload['dateReserved'] = new \DateTime();
$payload['timeUpdated'] = $payload['dateReserved']->getTimestamp();
$payload['attempt'] = (int)$payload['attempt'] + 1;
- $this->db->createCommand()
- ->update(
- $this->tableName,
- [
- 'dateReserved' => Db::prepareDateForDb($payload['dateReserved']),
- 'timeUpdated' => $payload['timeUpdated'],
- 'attempt' => $payload['attempt']
- ],
- ['id' => $payload['id']],
- [],
- false
- )
- ->execute();
+ Db::update($this->tableName, [
+ 'dateReserved' => Db::prepareDateForDb($payload['dateReserved']),
+ 'timeUpdated' => $payload['timeUpdated'],
+ 'attempt' => $payload['attempt'],
+ ], [
+ 'id' => $payload['id'],
+ ], [], false, $this->db);
}
$this->mutex->release($lockName);
@@ -630,27 +601,21 @@ private function _moveExpired()
{
if ($this->_reserveTime !== time()) {
$this->_reserveTime = time();
- $this->db->createCommand()
- ->update(
- $this->tableName,
- [
- 'dateReserved' => null,
- 'timeUpdated' => null,
- 'progress' => 0,
- 'progressLabel' => null,
- ],
- [
- 'and',
- [
- 'channel' => $this->channel,
- 'fail' => false
- ],
- '[[timeUpdated]] < :time - [[ttr]]',
- ],
- [':time' => $this->_reserveTime],
- false
- )
- ->execute();
+ Db::update($this->tableName, [
+ 'dateReserved' => null,
+ 'timeUpdated' => null,
+ 'progress' => 0,
+ 'progressLabel' => null,
+ ], [
+ 'and',
+ [
+ 'channel' => $this->channel,
+ 'fail' => false,
+ ],
+ '[[timeUpdated]] < :time - [[ttr]]',
+ ], [
+ ':time' => $this->_reserveTime,
+ ], false, $this->db);
}
}
diff --git a/src/queue/jobs/ApplyNewPropagationMethod.php b/src/queue/jobs/ApplyNewPropagationMethod.php
index 35f74185d63..16ebe90f855 100644
--- a/src/queue/jobs/ApplyNewPropagationMethod.php
+++ b/src/queue/jobs/ApplyNewPropagationMethod.php
@@ -8,11 +8,8 @@
namespace craft\queue\jobs;
use Craft;
-use craft\base\Element;
use craft\base\ElementInterface;
-use craft\elements\MatrixBlock;
use craft\events\BatchElementActionEvent;
-use craft\fields\Matrix;
use craft\helpers\ArrayHelper;
use craft\helpers\ElementHelper;
use craft\queue\BaseJob;
@@ -81,7 +78,6 @@ public function execute($queue)
}
// Load the element in any sites that it's about to be deleted for
- /** @var Element $element */
$element = $e->element;
$otherSiteElements = $elementType::find()
->id($element->id)
@@ -94,7 +90,6 @@ public function execute($queue)
// Duplicate those blocks so their content can live on
while (!empty($otherSiteElements)) {
$otherSiteElement = array_pop($otherSiteElements);
- /** @var Element $newElement */
$newElement = $elementsService->duplicateElement($otherSiteElement);
// This may support more than just the site it was saved in
$newElementSiteIds = ArrayHelper::getColumn(ElementHelper::supportedSitesForElement($newElement), 'siteId');
diff --git a/src/queue/jobs/ConvertEntryRevisions.php b/src/queue/jobs/ConvertEntryRevisions.php
index dfc97dc62a2..7ba92868aa3 100644
--- a/src/queue/jobs/ConvertEntryRevisions.php
+++ b/src/queue/jobs/ConvertEntryRevisions.php
@@ -9,16 +9,15 @@
use Craft;
use craft\base\Element;
-use craft\base\Field;
use craft\behaviors\DraftBehavior;
use craft\behaviors\RevisionBehavior;
-use craft\db\Connection;
use craft\db\Query;
use craft\db\Table;
use craft\elements\Entry;
use craft\elements\User;
use craft\helpers\ArrayHelper;
use craft\helpers\DateTimeHelper;
+use craft\helpers\Db;
use craft\helpers\Json;
use craft\queue\BaseJob;
use craft\services\Drafts;
@@ -38,8 +37,6 @@
class ConvertEntryRevisions extends BaseJob
{
private $queue;
- /** @var Connection */
- private $db;
/** @var Elements */
private $elementsService;
/** @var Entries */
@@ -61,7 +58,6 @@ class ConvertEntryRevisions extends BaseJob
public function execute($queue)
{
$this->queue = $queue;
- $this->db = Craft::$app->getDb();
$this->elementsService = Craft::$app->getElements();
$this->entriesService = Craft::$app->getEntries();
$this->fieldsService = Craft::$app->getFields();
@@ -106,7 +102,6 @@ private function setFieldValues(Entry $entry, $fieldValues)
}
foreach ($fieldValues as $id => $value) {
- /** @var Field $field */
$field = $this->fieldsService->getFieldById($id);
if ($field) {
$entry->setFieldValue($field->handle, $value);
@@ -116,7 +111,7 @@ private function setFieldValues(Entry $entry, $fieldValues)
private function convertDrafts()
{
- if (!$this->db->tableExists(Table::ENTRYDRAFTS)) {
+ if (!Craft::$app->getDb()->tableExists(Table::ENTRYDRAFTS)) {
return;
}
@@ -133,17 +128,15 @@ private function convertDrafts()
try {
$this->convertDraft($result);
} catch (\Throwable $e) {
- $this->db->createCommand()
- ->insert('{{%entrydrafterrors}}', [
- 'draftId' => $result['id'],
- 'error' => $e->getMessage(),
- ], false)
- ->execute();
+ Db::insert('{{%entrydrafterrors}}', [
+ 'draftId' => $result['id'],
+ 'error' => $e->getMessage(),
+ ], false);
continue;
}
- $this->db->createCommand()
- ->delete(Table::ENTRYDRAFTS, ['id' => $result['id']])
- ->execute();
+ Db::delete(Table::ENTRYDRAFTS, [
+ 'id' => $result['id'],
+ ], []);
}
}
@@ -197,7 +190,7 @@ private function convertVersions()
// If maxRevisions is set, filter out versions that would have been deleted by now
$maxRevisions = Craft::$app->getConfig()->getGeneral()->maxRevisions;
if ($maxRevisions > 0) {
- $numSql = $this->db->getIsMysql() ? 'cast([[num]] as signed)' : '[[num]]';
+ $numSql = Craft::$app->getDb()->getIsMysql() ? 'cast([[num]] as signed)' : '[[num]]';
$query->andWhere([
'>', 'num', (new Query())
->select(new Expression("max({$numSql})" . ($maxRevisions ? " - {$maxRevisions}" : '')))
@@ -213,17 +206,15 @@ private function convertVersions()
try {
$this->convertVersion($result);
} catch (\Throwable $e) {
- $this->db->createCommand()
- ->insert('{{%entryversionerrors}}', [
- 'versionId' => $result['id'],
- 'error' => $e->getMessage(),
- ], false)
- ->execute();
+ Db::insert('{{%entryversionerrors}}', [
+ 'versionId' => $result['id'],
+ 'error' => $e->getMessage(),
+ ], false);
continue;
}
- $this->db->createCommand()
- ->delete(Table::ENTRYVERSIONS, ['id' => $result['id']])
- ->execute();
+ Db::delete(Table::ENTRYVERSIONS, [
+ 'id' => $result['id'],
+ ], []);
}
}
@@ -240,7 +231,7 @@ private function convertVersion(array $result)
$lowestNum = (new Query())
->select(['min([[num]])'])
->from(['r' => Table::REVISIONS])
- ->innerJoin(Table::ELEMENTS . ' e', '[[e.revisionId]] = [[r.id]]')
+ ->innerJoin(['e' => Table::ELEMENTS], '[[e.revisionId]] = [[r.id]]')
->where(['sourceId' => $entry->id])
->andWhere(['>=', 'e.dateCreated', $result['dateCreated']])
->scalar();
@@ -248,13 +239,13 @@ private function convertVersion(array $result)
if ($lowestNum) {
$diff = ($result['num'] - $lowestNum) + 1;
if ($diff) {
- $this->db->createCommand()->update(Table::REVISIONS, [
- 'num' => new Expression('[[num]]' . ($diff > 0 ? '+' : '-') . abs($diff))
+ Db::update(Table::REVISIONS, [
+ 'num' => new Expression('[[num]]' . ($diff > 0 ? '+' : '-') . abs($diff)),
], [
'and',
['sourceId' => $entry->id],
['>=', 'num', $lowestNum],
- ], [], false)->execute();
+ ], [], false);
}
}
diff --git a/src/queue/jobs/FindAndReplace.php b/src/queue/jobs/FindAndReplace.php
index 635069224fc..c857798ecde 100644
--- a/src/queue/jobs/FindAndReplace.php
+++ b/src/queue/jobs/FindAndReplace.php
@@ -8,10 +8,10 @@
namespace craft\queue\jobs;
use Craft;
-use craft\base\Field;
use craft\base\FieldInterface;
use craft\db\Table;
use craft\fields\Matrix;
+use craft\helpers\Db;
use craft\queue\BaseJob;
use yii\base\Exception;
@@ -62,9 +62,7 @@ public function execute($queue)
foreach ($this->_textColumns as $i => list($table, $column)) {
$this->setProgress($queue, $i / $totalTextColumns);
- Craft::$app->getDb()->createCommand()
- ->replace($table, $column, $this->find, $this->replace)
- ->execute();
+ Db::replace($table, $column, $this->find, $this->replace);
}
}
@@ -88,7 +86,6 @@ protected function defaultDescription(): string
*/
private function _checkField(FieldInterface $field, string $table, string $fieldColumnPrefix)
{
- /** @var Field $field */
if (!$field::hasContentColumn()) {
return;
}
diff --git a/src/queue/jobs/LocalizeRelations.php b/src/queue/jobs/LocalizeRelations.php
index d4d5815b4de..55ba86966d8 100644
--- a/src/queue/jobs/LocalizeRelations.php
+++ b/src/queue/jobs/LocalizeRelations.php
@@ -10,6 +10,7 @@
use Craft;
use craft\db\Query;
use craft\db\Table;
+use craft\helpers\Db;
use craft\queue\BaseJob;
/**
@@ -42,32 +43,26 @@ public function execute($queue)
$totalRelations = count($relations);
$allSiteIds = Craft::$app->getSites()->getAllSiteIds();
$primarySiteId = array_shift($allSiteIds);
- $db = Craft::$app->getDb();
foreach ($relations as $i => $relation) {
$this->setProgress($queue, $i / $totalRelations);
// Set the existing relation to the primary site
- $db->createCommand()
- ->update(
- Table::RELATIONS,
- ['sourceSiteId' => $primarySiteId],
- ['id' => $relation['id']])
- ->execute();
+ Db::update(Table::RELATIONS, [
+ 'sourceSiteId' => $primarySiteId,
+ ], [
+ 'id' => $relation['id'],
+ ]);
// Duplicate it for the other sites
foreach ($allSiteIds as $siteId) {
- $db->createCommand()
- ->insert(
- Table::RELATIONS,
- [
- 'fieldId' => $this->fieldId,
- 'sourceId' => $relation['sourceId'],
- 'sourceSiteId' => $siteId,
- 'targetId' => $relation['targetId'],
- 'sortOrder' => $relation['sortOrder'],
- ])
- ->execute();
+ Db::insert(Table::RELATIONS, [
+ 'fieldId' => $this->fieldId,
+ 'sourceId' => $relation['sourceId'],
+ 'sourceSiteId' => $siteId,
+ 'targetId' => $relation['targetId'],
+ 'sortOrder' => $relation['sortOrder'],
+ ]);
}
}
}
diff --git a/src/queue/jobs/PruneRevisions.php b/src/queue/jobs/PruneRevisions.php
new file mode 100644
index 00000000000..69012f2593a
--- /dev/null
+++ b/src/queue/jobs/PruneRevisions.php
@@ -0,0 +1,77 @@
+
+ * @since 3.5.0
+ */
+class PruneRevisions extends BaseJob
+{
+ /**
+ * @var string|ElementInterface The type of elements to update.
+ */
+ public $elementType;
+
+ /**
+ * @var int The ID of the source element.
+ */
+ public $sourceId;
+
+ /**
+ * @var int The site ID of the source element
+ */
+ public $siteId;
+
+ /**
+ * @inheritdoc
+ */
+ public function execute($queue)
+ {
+ // Make sure maxRevisions is still set
+ $generalConfig = Craft::$app->getConfig()->getGeneral();
+ if (!$generalConfig->maxRevisions) {
+ return;
+ }
+
+ $class = $this->elementType;
+ $extraRevisions = $class::find()
+ ->revisionOf($this->sourceId)
+ ->siteId($this->siteId)
+ ->anyStatus()
+ ->orderBy(['num' => SORT_DESC])
+ ->offset($generalConfig->maxRevisions)
+ ->all();
+
+ if (empty($extraRevisions)) {
+ return;
+ }
+
+ $total = count($extraRevisions);
+ $elementsService = Craft::$app->getElements();
+
+ foreach ($extraRevisions as $i => $extraRevision) {
+ $this->setProgress($queue, ($i + 1) / $total);
+ $elementsService->deleteElement($extraRevision, true);
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function defaultDescription(): string
+ {
+ return Craft::t('app', 'Pruning extra revisions');
+ }
+}
diff --git a/src/queue/jobs/ResaveElements.php b/src/queue/jobs/ResaveElements.php
index 9f772ff6228..40a73b08b50 100644
--- a/src/queue/jobs/ResaveElements.php
+++ b/src/queue/jobs/ResaveElements.php
@@ -14,7 +14,6 @@
use craft\events\BatchElementActionEvent;
use craft\queue\BaseJob;
use craft\services\Elements;
-use yii\db\Exception;
/**
* ResaveElements job
diff --git a/src/queue/jobs/UpdateElementSlugsAndUris.php b/src/queue/jobs/UpdateElementSlugsAndUris.php
index 57948f9dd3a..7d720e7e065 100644
--- a/src/queue/jobs/UpdateElementSlugsAndUris.php
+++ b/src/queue/jobs/UpdateElementSlugsAndUris.php
@@ -8,7 +8,6 @@
namespace craft\queue\jobs;
use Craft;
-use craft\base\Element;
use craft\base\ElementInterface;
use craft\elements\db\ElementQuery;
use craft\elements\db\ElementQueryInterface;
@@ -88,7 +87,6 @@ protected function defaultDescription(): string
*/
private function _createElementQuery(): ElementQueryInterface
{
- /** @var Element $class */
$class = $this->elementType;
return $class::find()
diff --git a/src/queue/jobs/UpdateSearchIndex.php b/src/queue/jobs/UpdateSearchIndex.php
index b92b774fb97..d38e5eca89b 100644
--- a/src/queue/jobs/UpdateSearchIndex.php
+++ b/src/queue/jobs/UpdateSearchIndex.php
@@ -8,7 +8,6 @@
namespace craft\queue\jobs;
use Craft;
-use craft\base\Element;
use craft\base\ElementInterface;
use craft\queue\BaseJob;
@@ -46,7 +45,6 @@ class UpdateSearchIndex extends BaseJob
*/
public function execute($queue)
{
- /** @var Element $class */
$class = $this->elementType;
$elements = $class::find()
->id($this->elementId)
diff --git a/src/records/GlobalSet.php b/src/records/GlobalSet.php
index 3339d5a6715..16f62083a52 100644
--- a/src/records/GlobalSet.php
+++ b/src/records/GlobalSet.php
@@ -50,7 +50,7 @@ public static function find()
$query
->where([
'exists', (new Query())
- ->from([Table::ELEMENTS . ' e'])
+ ->from(['e' => Table::ELEMENTS])
->where('[[e.id]] = ' . static::tableName() . '.[[id]]')
->andWhere(['e.dateDeleted' => null])
]);
@@ -74,7 +74,7 @@ public static function findTrashed(): ActiveQuery
{
return static::find()->where([
'not exists', (new Query())
- ->from([Table::ELEMENTS . ' e'])
+ ->from(['e' => Table::ELEMENTS])
->where('[[e.id]] = ' . static::tableName() . '.[[id]]')
->andWhere(['e.dateDeleted' => null])
]);
diff --git a/src/records/Site.php b/src/records/Site.php
index b85d182aea4..306a56394aa 100644
--- a/src/records/Site.php
+++ b/src/records/Site.php
@@ -21,6 +21,7 @@
* @property string $handle Handle
* @property string $language Language
* @property bool $primary Primary
+ * @property bool $enabled Enabled
* @property bool $hasUrls Has URLs
* @property bool $baseUrl Base URL
* @property int $sortOrder Sort order
diff --git a/src/services/AssetIndexer.php b/src/services/AssetIndexer.php
index 521dd2c59b2..00240f1ae50 100644
--- a/src/services/AssetIndexer.php
+++ b/src/services/AssetIndexer.php
@@ -4,7 +4,6 @@
use Craft;
use craft\base\LocalVolumeInterface;
-use craft\base\Volume;
use craft\base\VolumeInterface;
use craft\db\Query;
use craft\db\Table;
@@ -57,7 +56,6 @@ public function getIndexingSessionId(): string
public function prepareIndexList(string $sessionId, int $volumeId, string $directory = ''): array
{
try {
- /** @var Volume $volume */
$volume = Craft::$app->getVolumes()->getVolumeById($volumeId);
// Get the file list.
@@ -209,12 +207,7 @@ public function storeIndexList(array $indexList, string $sessionId, int $volumeI
$values[] = [$volumeId, $sessionId, $entry['path'], $entry['size'], Db::prepareDateForDb(new \DateTime('@' . $entry['timestamp'])), false, false];
}
- Craft::$app->getDb()->createCommand()
- ->batchInsert(
- Table::ASSETINDEXDATA,
- $attributes,
- $values)
- ->execute();
+ Db::batchInsert(Table::ASSETINDEXDATA, $attributes, $values);
}
/**
@@ -298,13 +291,9 @@ public function updateIndexEntry(int $entryId, array $data)
{
// Only allow a few fields to be updated.
$data = array_intersect_key($data, array_flip(['inProgress', 'completed', 'recordId']));
-
- Craft::$app->getDb()->createCommand()
- ->update(
- Table::ASSETINDEXDATA,
- $data,
- ['id' => $entryId])
- ->execute();
+ Db::update(Table::ASSETINDEXDATA, $data, [
+ 'id' => $entryId,
+ ]);
}
@@ -345,10 +334,10 @@ public function getMissingFiles(string $sessionId): array
$processedFiles = array_flip($processedFiles);
$assets = (new Query())
->select(['fi.volumeId', 'fi.id AS assetId', 'fi.filename', 'fo.path', 's.name AS volumeName'])
- ->from(['{{%assets}} fi'])
- ->innerJoin('{{%volumefolders}} fo', '[[fi.folderId]] = [[fo.id]]')
- ->innerJoin('{{%volumes}} s', '[[s.id]] = [[fi.volumeId]]')
- ->innerJoin('{{%elements}} e', '[[e.id]] = [[fi.id]]')
+ ->from(['fi' => Table::ASSETS])
+ ->innerJoin(['fo' => Table::VOLUMEFOLDERS], '[[fo.id]] = [[fi.folderId]]')
+ ->innerJoin(['s' => Table::VOLUMES], '[[s.id]] = [[fi.volumeId]]')
+ ->innerJoin(['e' => Table::ELEMENTS], '[[e.id]] = [[fi.id]]')
->where(['fi.volumeId' => $volumeIds])
->andWhere(['e.dateDeleted' => null])
->all();
@@ -365,7 +354,7 @@ public function getMissingFiles(string $sessionId): array
/**
* Index a single file by Volume and path.
*
- * @param Volume $volume
+ * @param VolumeInterface $volume
* @param string $path
* @param string $sessionId optional indexing session id.
* @param bool $cacheImages Whether remotely-stored images should be downloaded and stored locally, to speed up transform generation.
@@ -374,7 +363,7 @@ public function getMissingFiles(string $sessionId): array
* @throws MissingAssetException if the asset record doesn't exist and $createIfMissing is false
* @throws VolumeObjectNotFoundException If the file to be indexed cannot be found.
*/
- public function indexFile(Volume $volume, string $path, string $sessionId = '', bool $cacheImages = false, bool $createIfMissing = true)
+ public function indexFile(VolumeInterface $volume, string $path, string $sessionId = '', bool $cacheImages = false, bool $createIfMissing = true)
{
$fileInfo = $volume->getFileMetadata($path);
$folderPath = dirname($path);
@@ -412,9 +401,6 @@ public function indexFileByEntry(AssetIndexData $indexEntry, bool $cacheImages =
$indexEntry->completed = false;
$recordData = $indexEntry->toArray();
- // For some reason Postgres chokes if we don't do that.
- unset($recordData['id']);
-
$record = new AssetIndexDataRecord($recordData);
$record->save();
@@ -462,7 +448,6 @@ private function _indexFileByIndexData(AssetIndexData $indexEntry, bool $createI
throw new AssetLogicException("The folder {$path} does not exist");
}
- /** @var Volume $volume */
$volume = $folder->getVolume();
// Check if the extension is allowed
diff --git a/src/services/AssetTransforms.php b/src/services/AssetTransforms.php
index ebd3cb9829a..3e91668c8fa 100644
--- a/src/services/AssetTransforms.php
+++ b/src/services/AssetTransforms.php
@@ -267,11 +267,9 @@ public function handleChangedTransform(ConfigEvent $event)
}
if ($deleteTransformIndexes) {
- Craft::$app->getDb()->createCommand()
- ->delete(
- Table::ASSETTRANSFORMINDEX,
- ['location' => $this->_getNamedTransformFolderName($transformRecord->handle)])
- ->execute();
+ Db::delete(Table::ASSETTRANSFORMINDEX, [
+ 'location' => $this->_getNamedTransformFolderName($transformRecord->handle),
+ ]);
}
// Clear caches
@@ -357,11 +355,9 @@ public function handleDeletedTransform(ConfigEvent $event)
]));
}
- Craft::$app->getDb()->createCommand()
- ->delete(
- Table::ASSETTRANSFORMS,
- ['uid' => $transformUid])
- ->execute();
+ Db::delete(Table::ASSETTRANSFORMS, [
+ 'uid' => $transformUid,
+ ]);
// Clear caches
$this->_transforms = null;
@@ -377,6 +373,23 @@ public function handleDeletedTransform(ConfigEvent $event)
/**
* Eager-loads transform indexes for a given set of file IDs.
*
+ * You can include `srcset`-style sizes (e.g. `100w` or `2x`) following a normal transform definition, for example:
+ *
+ * ::: code
+ *
+ * ```twig
+ * [{width: 1000, height: 600}, '1.5x', '2x', '3x']
+ * ```
+ *
+ * ```php
+ * [['width' => 1000, 'height' => 600], '1.5x', '2x', '3x']
+ * ```
+ *
+ * :::
+ *
+ * When a `srcset`-style size is encountered, the preceding normal transform definition will be used as a
+ * reference when determining the resulting transform dimensions.
+ *
* @param Asset[]|array $assets The files to eager-load tranforms for
* @param array $transforms The transform definitions to eager-load
*/
@@ -393,26 +406,66 @@ public function eagerLoadTransforms(array $assets, array $transforms)
$transformsByFingerprint = [];
$indexCondition = ['or'];
- foreach ($transforms as $transform) {
- $transform = $this->normalizeTransform($transform);
+ /** @var AssetTransform|null $refTransform */
+ $refTransform = null;
- if ($transform !== null) {
- $location = $fingerprint = $this->_getTransformFolderName($transform);
+ foreach ($transforms as $transform) {
+ // Is this a srcset-style size (2x, 100w, etc.)?
+ try {
+ list($sizeValue, $sizeUnit) = AssetsHelper::parseSrcsetSize($transform);
+ } catch (InvalidArgumentException $e) {
+ // All good.
+ }
- $transformCondition = ['and', ['location' => $location]];
+ if (isset($sizeValue, $sizeUnit)) {
+ if ($refTransform === null || !$refTransform->width) {
+ throw new InvalidArgumentException("Can’t eager-load transform “{$transform}” without a prior transform that specifies the base width");
+ }
- if ($transform->format === null) {
- $transformCondition[] = ['format' => null];
+ $transform = [];
+ if ($sizeUnit === 'w') {
+ $transform['width'] = (int)$sizeValue;
} else {
- $transformCondition[] = ['format' => $transform->format];
- $fingerprint .= ':' . $transform->format;
+ $transform['width'] = (int)ceil($refTransform->width * $sizeValue);
}
- $indexCondition[] = $transformCondition;
- $transformsByFingerprint[$fingerprint] = $transform;
+ // Only set the height if the reference transform has a height set on it
+ if ($refTransform && $refTransform->height) {
+ if ($sizeUnit === 'w') {
+ $transform['height'] = (int)ceil($refTransform->height * $transform['width'] / $refTransform->width);
+ } else {
+ $transform['height'] = (int)ceil($refTransform->height * $sizeValue);
+ }
+ }
+ }
+
+ $transform = $this->normalizeTransform($transform);
+ if ($transform === null) {
+ continue;
+ }
+
+ $location = $fingerprint = $this->_getTransformFolderName($transform);
+
+ $transformCondition = ['and', ['location' => $location]];
+
+ if ($transform->format === null) {
+ $transformCondition[] = ['format' => null];
+ } else {
+ $transformCondition[] = ['format' => $transform->format];
+ $fingerprint .= ':' . $transform->format;
+ }
+
+ $indexCondition[] = $transformCondition;
+ $transformsByFingerprint[$fingerprint] = $transform;
+
+ if (!isset($sizeValue)) {
+ // Use this as the reference transform in case any srcset-style transforms follow it
+ $refTransform = $transform;
}
}
+ unset($refTransform);
+
// Query for the indexes
$results = $this->_createTransformIndexQuery()
->where([
@@ -447,11 +500,9 @@ public function eagerLoadTransforms(array $assets, array $transforms)
// Delete any invalid indexes
if (!empty($invalidIndexIds)) {
- Craft::$app->getDb()->createCommand()
- ->delete(
- Table::ASSETTRANSFORMINDEX,
- ['id' => $invalidIndexIds])
- ->execute();
+ Db::delete(Table::ASSETTRANSFORMINDEX, [
+ 'id' => $invalidIndexIds,
+ ]);
}
}
@@ -478,7 +529,6 @@ public function getTransformIndex(Asset $asset, $transform): AssetTransformIndex
if (isset($this->_eagerLoadedTransformIndexes[$fingerprint])) {
$result = $this->_eagerLoadedTransformIndexes[$fingerprint];
-
return new AssetTransformIndex($result);
}
@@ -505,9 +555,9 @@ public function getTransformIndex(Asset $asset, $transform): AssetTransformIndex
}
// Delete the out-of-date record
- Craft::$app->getDb()->createCommand()
- ->delete(Table::ASSETTRANSFORMINDEX, ['id' => $result['id']])
- ->execute();
+ Db::delete(Table::ASSETTRANSFORMINDEX, [
+ 'id' => $result['id'],
+ ]);
// And the file.
$transformUri = $asset->getFolder()->path . $this->getTransformSubpath($asset, new AssetTransformIndex($result));
@@ -772,16 +822,13 @@ public function storeTransformIndexData(AssetTransformIndex $index): AssetTransf
], [], false)
);
- $dbConnection = Craft::$app->getDb();
- if (null !== $index->id) {
- $dbConnection->createCommand()
- ->update(Table::ASSETTRANSFORMINDEX, $values, ['id' => $index->id])
- ->execute();
+ if ($index->id !== null) {
+ Db::update(Table::ASSETTRANSFORMINDEX, $values, [
+ 'id' => $index->id,
+ ]);
} else {
- $dbConnection->createCommand()
- ->insert(Table::ASSETTRANSFORMINDEX, $values)
- ->execute();
- $index->id = $dbConnection->getLastInsertID(Table::ASSETTRANSFORMINDEX);
+ Db::insert(Table::ASSETTRANSFORMINDEX, $values);
+ $index->id = Craft::$app->getDb()->getLastInsertID(Table::ASSETTRANSFORMINDEX);
}
return $index;
@@ -869,9 +916,9 @@ public function getUrlForTransformByAssetAndTransformIndex(Asset $asset, AssetTr
*/
public function deleteTransformIndexDataByAssetId(int $assetId)
{
- Craft::$app->getDb()->createCommand()
- ->delete(Table::ASSETTRANSFORMINDEX, ['assetId' => $assetId])
- ->execute();
+ Db::delete(Table::ASSETTRANSFORMINDEX, [
+ 'assetId' => $assetId,
+ ]);
}
/**
@@ -881,9 +928,9 @@ public function deleteTransformIndexDataByAssetId(int $assetId)
*/
public function deleteTransformIndex(int $indexId)
{
- Craft::$app->getDb()->createCommand()
- ->delete(Table::ASSETTRANSFORMINDEX, ['id' => $indexId])
- ->execute();
+ Db::delete(Table::ASSETTRANSFORMINDEX, [
+ 'id' => $indexId,
+ ]);
}
/**
diff --git a/src/services/Assets.php b/src/services/Assets.php
index 8f1b36f5507..099d469fc97 100644
--- a/src/services/Assets.php
+++ b/src/services/Assets.php
@@ -9,11 +9,10 @@
use Craft;
use craft\assetpreviews\Image as ImagePreview;
-use craft\assetpreviews\Text;
use craft\assetpreviews\Pdf;
+use craft\assetpreviews\Text;
use craft\assetpreviews\Video;
use craft\base\AssetPreviewHandlerInterface;
-use craft\base\Volume;
use craft\base\VolumeInterface;
use craft\db\Query;
use craft\db\Table;
@@ -38,6 +37,7 @@
use craft\helpers\FileHelper;
use craft\helpers\Image;
use craft\helpers\Json;
+use craft\helpers\Queue;
use craft\helpers\UrlHelper;
use craft\image\Raster;
use craft\models\AssetTransform;
@@ -611,7 +611,7 @@ public function getAssetUrl(Asset $asset, $transform = null, bool $generateNow =
// Queue up a new Generate Pending Transforms job
if (!$this->_queuedGeneratePendingTransformsJob) {
- Craft::$app->getQueue()->push(new GeneratePendingTransforms());
+ Queue::push(new GeneratePendingTransforms());
$this->_queuedGeneratePendingTransformsJob = true;
}
@@ -629,7 +629,7 @@ public function getAssetUrl(Asset $asset, $transform = null, bool $generateNow =
* @param bool $fallbackToIcon whether to return the URL to a generic icon if a thumbnail can't be generated
* @return string
* @throws NotSupportedException if the asset can't have a thumbnail, and $fallbackToIcon is `false`
- * @see Asset::getThumbUrl()
+ * @see Asset::getIconUrl()
*/
public function getThumbUrl(Asset $asset, int $width, int $height = null, bool $generate = false, bool $fallbackToIcon = true): string
{
@@ -811,8 +811,8 @@ public function getNameReplacementInFolder(string $originalFilename, int $folder
// Get a list from DB as well
$fileList = (new Query())
->select(['assets.filename'])
- ->from(['{{%assets}} assets'])
- ->innerJoin(['{{%elements}} elements'], '[[assets.id]] = [[elements.id]]')
+ ->from(['assets' => Table::ASSETS])
+ ->innerJoin(['elements' => Table::ELEMENTS], '[[elements.id]] = [[assets.id]]')
->where([
'assets.folderId' => $folderId,
'elements.dateDeleted' => null
@@ -880,7 +880,6 @@ public function getNameReplacementInFolder(string $originalFilename, int $folder
*/
public function ensureFolderByFullPathAndVolume(string $fullPath, VolumeInterface $volume, bool $justRecord = true): int
{
- /** @var Volume $volume */
$parentId = Craft::$app->getVolumes()->ensureTopFolder($volume);
$folderId = $parentId;
@@ -992,7 +991,6 @@ public function getUserTemporaryUploadFolder(User $user = null)
if (!$volume) {
throw new VolumeException(Craft::t('app', 'The volume set for temp asset storage is not valid.'));
}
- /** @var Volume $volume */
$path = (isset($assetSettings['tempSubpath']) ? $assetSettings['tempSubpath'] . '/' : '') .
$folderName;
$folderId = $this->ensureFolderByFullPathAndVolume($path, $volume, false);
diff --git a/src/services/Categories.php b/src/services/Categories.php
index ecde161dda2..1a40191b56a 100644
--- a/src/services/Categories.php
+++ b/src/services/Categories.php
@@ -8,7 +8,6 @@
namespace craft\services;
use Craft;
-use craft\base\Field;
use craft\db\Query;
use craft\db\Table;
use craft\elements\Category;
@@ -326,8 +325,7 @@ public function handleChangedCategoryGroup(ConfigEvent $event)
ProjectConfigHelper::ensureAllSitesProcessed();
ProjectConfigHelper::ensureAllFieldsProcessed();
- $db = Craft::$app->getDb();
- $transaction = $db->beginTransaction();
+ $transaction = Craft::$app->getDb()->beginTransaction();
try {
$structureData = $data['structure'];
@@ -451,15 +449,12 @@ public function handleChangedCategoryGroup(ConfigEvent $event)
if (!empty($siteData)) {
// Drop the old category URIs for any site settings that don't have URLs
if (!empty($sitesNowWithoutUrls)) {
- $db->createCommand()
- ->update(
- Table::ELEMENTS_SITES,
- ['uri' => null],
- [
- 'elementId' => $categoryIds,
- 'siteId' => $sitesNowWithoutUrls,
- ])
- ->execute();
+ Db::update(Table::ELEMENTS_SITES, [
+ 'uri' => null,
+ ], [
+ 'elementId' => $categoryIds,
+ 'siteId' => $sitesNowWithoutUrls,
+ ]);
} else if (!empty($sitesWithNewUriFormats)) {
foreach ($categoryIds as $categoryId) {
App::maxPowerCaptain();
@@ -646,7 +641,6 @@ public function handleDeletedCategoryGroup(ConfigEvent $event)
*/
public function pruneDeletedField(FieldEvent $event)
{
- /** @var Field $field */
$field = $event->field;
$fieldUid = $field->uid;
@@ -672,7 +666,9 @@ public function pruneDeletedField(FieldEvent $event)
}
// Nuke all the layout fields from the DB
- Craft::$app->getDb()->createCommand()->delete('{{%fieldlayoutfields}}', ['fieldId' => $field->id])->execute();
+ Db::delete(Table::FIELDLAYOUTFIELDS, [
+ 'fieldId' => $field->id,
+ ]);
// Allow events again
$projectConfig->muteEvents = false;
@@ -717,8 +713,8 @@ public function getCategoryById(int $categoryId, int $siteId = null)
// Get the structure ID
$structureId = (new Query())
->select(['categorygroups.structureId'])
- ->from(['{{%categories}} categories'])
- ->innerJoin('{{%categorygroups}} categorygroups', '[[categorygroups.id]] = [[categories.groupId]]')
+ ->from(['categories' => Table::CATEGORIES])
+ ->innerJoin(['categorygroups' => Table::CATEGORYGROUPS], '[[categorygroups.id]] = [[categories.groupId]]')
->where(['categories.id' => $categoryId])
->scalar();
diff --git a/src/services/Composer.php b/src/services/Composer.php
index 0855e46dfe6..a720e47c81c 100644
--- a/src/services/Composer.php
+++ b/src/services/Composer.php
@@ -56,6 +56,12 @@ class Composer extends Component
*/
public $maxBackups = 50;
+ /**
+ * @var callable|null The previous error handler.
+ * @see run()
+ */
+ private $_errorHandler;
+
/**
* @var string[]|null
*/
@@ -165,7 +171,7 @@ public function install(array $requirements = null, IOInterface $io = null, $whi
try {
// Run the installer
- $status = $installer->run();
+ $status = $this->run($installer);
} catch (\Throwable $exception) {
$status = 1;
}
@@ -189,7 +195,7 @@ public function install(array $requirements = null, IOInterface $io = null, $whi
$contents = "_composerClasses);
foreach ($this->_composerClasses as $class) {
- $contents .= " '{$class}',\n";
+ $contents .= " $class::class,\n";
}
$contents .= "];\n";
FileHelper::writeToFile(dirname(__DIR__) . '/config/composer-classes.php', $contents);
@@ -259,7 +265,7 @@ public function uninstall(array $packages, IOInterface $io = null)
->setOptimizeAutoloader(true)
->setClassMapAuthoritative($config->get('classmap-authoritative'));
- $status = $installer->run();
+ $status = $this->run($installer);
} catch (\Throwable $exception) {
$status = 1;
}
@@ -552,4 +558,40 @@ protected function backupComposerFiles()
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
}
}
+
+ /**
+ * @param Installer $installer
+ * @return int The response status
+ * @throws \Exception
+ * @since 3.5.0
+ */
+ protected function run(Installer $installer): int
+ {
+ $this->_errorHandler = set_error_handler([$this, 'handleError'], E_USER_DEPRECATED);
+ $status = $installer->run();
+ set_error_handler($this->_errorHandler);
+ return $status;
+ }
+
+ /**
+ * Handles an error triggered by Composer
+ *
+ * @param int $code the level of the error raised.
+ * @param string $message the error message.
+ * @param string $file the filename that the error was raised in.
+ * @param int $line the line number the error was raised at.
+ * @return bool whether the normal error handler continues.
+ * @since 3.5.0
+ */
+ public function handleError(int $code, string $message, string $file, int $line): bool
+ {
+ // Ignore deprecated errors
+ if ($code === E_USER_DEPRECATED) {
+ return true;
+ }
+ if ($this->_errorHandler !== null) {
+ return ($this->_errorHandler)($code, $message, $file, $line);
+ }
+ return false;
+ }
}
diff --git a/src/services/Content.php b/src/services/Content.php
index c147faeacdc..80cd8586245 100644
--- a/src/services/Content.php
+++ b/src/services/Content.php
@@ -8,9 +8,7 @@
namespace craft\services;
use Craft;
-use craft\base\Element;
use craft\base\ElementInterface;
-use craft\base\Field;
use craft\db\Query;
use craft\db\Table;
use craft\events\ElementContentEvent;
@@ -60,7 +58,6 @@ class Content extends Component
*/
public function getContentRow(ElementInterface $element)
{
- /** @var Element $element */
if (!$element->id || !$element->siteId) {
return null;
}
@@ -99,7 +96,6 @@ public function getContentRow(ElementInterface $element)
*/
public function populateElementContent(ElementInterface $element)
{
- /** @var Element $element */
// Make sure the element has content
if (!$element->hasContent()) {
return;
@@ -116,7 +112,6 @@ public function populateElementContent(ElementInterface $element)
if ($fieldLayout) {
foreach ($fieldLayout->getFields() as $field) {
- /** @var Field $field */
if ($field::hasContentColumn()) {
$element->setFieldValue($field->handle, $row[$field->handle]);
}
@@ -135,7 +130,6 @@ public function populateElementContent(ElementInterface $element)
*/
public function saveContent(ElementInterface $element): bool
{
- /** @var Element $element */
if (!$element->id) {
throw new Exception('Cannot save the content of an unsaved element.');
}
@@ -166,7 +160,6 @@ public function saveContent(ElementInterface $element): bool
$fieldLayout = $element->getFieldLayout();
if ($fieldLayout) {
foreach ($fieldLayout->getFields() as $field) {
- /** @var Field $field */
if (
(!$element->contentId || $element->isFieldDirty($field->handle)) &&
$field::hasContentColumn()
@@ -192,14 +185,12 @@ public function saveContent(ElementInterface $element): bool
// Insert/update the DB row
if ($element->contentId) {
// Update the existing row
- Craft::$app->getDb()->createCommand()
- ->update($this->contentTable, $values, ['id' => $element->contentId])
- ->execute();
+ Db::update($this->contentTable, $values, [
+ 'id' => $element->contentId,
+ ]);
} else {
// Insert a new row and store its ID on the element
- Craft::$app->getDb()->createCommand()
- ->insert($this->contentTable, $values)
- ->execute();
+ Db::insert($this->contentTable, $values);
$element->contentId = Craft::$app->getDb()->getLastInsertID($this->contentTable);
}
diff --git a/src/services/Dashboard.php b/src/services/Dashboard.php
index 0ea073128da..5f30bf4fcf1 100644
--- a/src/services/Dashboard.php
+++ b/src/services/Dashboard.php
@@ -8,7 +8,6 @@
namespace craft\services;
use Craft;
-use craft\base\Widget;
use craft\base\WidgetInterface;
use craft\db\Query;
use craft\db\Table;
@@ -17,6 +16,7 @@
use craft\events\RegisterComponentTypesEvent;
use craft\events\WidgetEvent;
use craft\helpers\Component as ComponentHelper;
+use craft\helpers\Db;
use craft\records\Widget as WidgetRecord;
use craft\widgets\CraftSupport as CraftSupportWidget;
use craft\widgets\Feed as FeedWidget;
@@ -116,7 +116,6 @@ public function createWidget($config): WidgetInterface
}
try {
- /** @var Widget $widget */
$widget = ComponentHelper::createComponent($config, WidgetInterface::class);
} catch (MissingComponentException $e) {
$config['errorMessage'] = $e->getMessage();
@@ -189,7 +188,6 @@ public function getWidgetById(int $id)
*/
public function saveWidget(WidgetInterface $widget, bool $runValidation = true): bool
{
- /** @var Widget $widget */
$isNewWidget = $widget->getIsNew();
// Fire a 'beforeSaveWidget' event
@@ -277,7 +275,6 @@ public function deleteWidgetById(int $widgetId): bool
*/
public function deleteWidget(WidgetInterface $widget): bool
{
- /** @var Widget $widget */
// Fire a 'beforeDeleteWidget' event
if ($this->hasEventHandlers(self::EVENT_BEFORE_DELETE_WIDGET)) {
$this->trigger(self::EVENT_BEFORE_DELETE_WIDGET, new WidgetEvent([
@@ -384,9 +381,11 @@ private function _addDefaultUserWidgets()
// Update the user record
$user->hasDashboard = true;
- Craft::$app->getDb()->createCommand()
- ->update(Table::USERS, ['hasDashboard' => true], ['id' => $user->id])
- ->execute();
+ Db::update(Table::USERS, [
+ 'hasDashboard' => true,
+ ], [
+ 'id' => $user->id,
+ ]);
}
/**
diff --git a/src/services/Deprecator.php b/src/services/Deprecator.php
index 932d7ee99a6..52ae6e2408f 100644
--- a/src/services/Deprecator.php
+++ b/src/services/Deprecator.php
@@ -122,23 +122,17 @@ public function storeLogs()
$db = Craft::$app->getDb();
foreach ($this->_requestLogs as $log) {
- $command = $db->createCommand()
- ->upsert(
- Table::DEPRECATIONERRORS,
- [
- 'key' => $log->key,
- 'fingerprint' => $log->fingerprint
- ],
- [
- 'lastOccurrence' => Db::prepareDateForDb($log->lastOccurrence),
- 'file' => $log->file,
- 'line' => $log->line,
- 'message' => $log->message,
- 'traces' => Json::encode($log->traces),
- ]);
-
try {
- $command->execute();
+ Db::upsert(Table::DEPRECATIONERRORS, [
+ 'key' => $log->key,
+ 'fingerprint' => $log->fingerprint,
+ ], [
+ 'lastOccurrence' => Db::prepareDateForDb($log->lastOccurrence),
+ 'file' => $log->file,
+ 'line' => $log->line,
+ 'message' => $log->message,
+ 'traces' => Json::encode($log->traces),
+ ]);
$log->id = $db->getLastInsertID();
} catch (IntegrityException $e) {
// todo: remove this try/catch after the next breakpoint
@@ -218,9 +212,9 @@ public function getLogById(int $logId)
*/
public function deleteLogById(int $id): bool
{
- $affectedRows = Craft::$app->getDb()->createCommand()
- ->delete(Table::DEPRECATIONERRORS, ['id' => $id])
- ->execute();
+ $affectedRows = Db::delete(Table::DEPRECATIONERRORS, [
+ 'id' => $id,
+ ]);
return (bool)$affectedRows;
}
@@ -232,9 +226,7 @@ public function deleteLogById(int $id): bool
*/
public function deleteAllLogs(): bool
{
- $affectedRows = Craft::$app->getDb()->createCommand()
- ->delete(Table::DEPRECATIONERRORS)
- ->execute();
+ $affectedRows = Db::delete(Table::DEPRECATIONERRORS);
return (bool)$affectedRows;
}
diff --git a/src/services/Drafts.php b/src/services/Drafts.php
index 6db6db58210..c35dc64c575 100644
--- a/src/services/Drafts.php
+++ b/src/services/Drafts.php
@@ -8,7 +8,6 @@
namespace craft\services;
use Craft;
-use craft\base\Element;
use craft\base\ElementInterface;
use craft\behaviors\DraftBehavior;
use craft\db\Query;
@@ -79,7 +78,6 @@ public function getEditableDrafts(ElementInterface $element, string $permission
return [];
}
- /** @var Element $element */
$query = $element::find()
->draftOf($element)
->siteId($element->siteId)
@@ -107,7 +105,6 @@ public function getEditableDrafts(ElementInterface $element, string $permission
public function createDraft(ElementInterface $source, int $creatorId, string $name = null, string $notes = null, array $newAttributes = []): ElementInterface
{
// Make sure the source isn't a draft or revision
- /** @var Element $source */
if ($source->getIsDraft() || $source->getIsRevision()) {
throw new InvalidArgumentException('Cannot create a draft from another draft or revision.');
}
@@ -152,7 +149,6 @@ public function createDraft(ElementInterface $source, int $creatorId, string $na
];
// Duplicate the element
- /** @var Element $draft */
$draft = Craft::$app->getElements()->duplicateElement($source, $newAttributes);
$transaction->commit();
@@ -194,7 +190,6 @@ public function saveElementAsDraft(ElementInterface $element, int $creatorId, st
// Create the draft row
$draftId = $this->_insertDraftRow(null, $creatorId, $name, $notes);
- /** @var Element $element */
$element->draftId = $draftId;
$element->attachBehavior('draft', new DraftBehavior([
'creatorId' => $creatorId,
@@ -214,7 +209,7 @@ public function saveElementAsDraft(ElementInterface $element, int $creatorId, st
*/
public function mergeSourceChanges(ElementInterface $draft)
{
- /** @var Element|DraftBehavior $draft */
+ /** @var ElementInterface|DraftBehavior $draft */
/** @var DraftBehavior $behavior */
$behavior = $draft->getBehavior('draft');
@@ -227,7 +222,6 @@ public function mergeSourceChanges(ElementInterface $draft)
return;
}
- /** @var Element[] $sourceElements */
$sourceElements = $draft::find()
->id($sourceId)
->siteId('*')
@@ -236,11 +230,6 @@ public function mergeSourceChanges(ElementInterface $draft)
->indexBy('siteId')
->all();
- // Make sure a source element exists for the draft's current site ID
- if (!isset($sourceElements[$draft->siteId])) {
- throw new Exception('Attempting to merge source changes for a draft in an unsupported site.');
- }
-
// Make sure the draft actually supports its own site ID
$supportedSites = ElementHelper::supportedSitesForElement($draft);
$supportedSiteIds = ArrayHelper::getColumn($supportedSites, 'siteId');
@@ -251,7 +240,7 @@ public function mergeSourceChanges(ElementInterface $draft)
// Fire a 'beforeMergeSource' event
if ($this->hasEventHandlers(self::EVENT_BEFORE_MERGE_SOURCE_CHANGES)) {
$this->trigger(self::EVENT_BEFORE_MERGE_SOURCE_CHANGES, new DraftEvent([
- 'source' => $sourceElements[$draft->siteId],
+ 'source' => $sourceElements[$draft->siteId] ?? reset($sourceElements),
'creatorId' => $behavior->creatorId,
'draftName' => $behavior->draftName,
'draftNotes' => $behavior->draftNotes,
@@ -262,10 +251,12 @@ public function mergeSourceChanges(ElementInterface $draft)
$transaction = Craft::$app->getDb()->beginTransaction();
try {
// Start with $draft's site
- $this->_mergeSourceChangesInternal($sourceElements[$draft->siteId], $draft);
+ if (isset($sourceElements[$draft->siteId])) {
+ $this->_mergeSourceChangesInternal($sourceElements[$draft->siteId], $draft);
+ }
// Now the other sites
- /** @var ElementInterface[]|Element[]|DraftBehavior[] $otherSiteDrafts */
+ /** @var ElementInterface[]|DraftBehavior[] $otherSiteDrafts */
$otherSiteDrafts = $draft::find()
->drafts()
->id($draft->id)
@@ -293,7 +284,7 @@ public function mergeSourceChanges(ElementInterface $draft)
// Fire an 'afterMergeSource' event
if ($this->hasEventHandlers(self::EVENT_AFTER_MERGE_SOURCE_CHANGES)) {
$this->trigger(self::EVENT_AFTER_MERGE_SOURCE_CHANGES, new DraftEvent([
- 'source' => $sourceElements[$draft->siteId],
+ 'source' => $sourceElements[$draft->siteId] ?? reset($sourceElements),
'creatorId' => $behavior->creatorId,
'draftName' => $behavior->draftName,
'draftNotes' => $behavior->draftNotes,
@@ -310,7 +301,7 @@ public function mergeSourceChanges(ElementInterface $draft)
*/
private function _mergeSourceChangesInternal(ElementInterface $source, ElementInterface $draft)
{
- /** @var Element|DraftBehavior $draft */
+ /** @var ElementInterface|DraftBehavior $draft */
/** @var DraftBehavior $behavior */
$behavior = $draft->getBehavior('draft');
@@ -344,12 +335,34 @@ private function _mergeSourceChangesInternal(ElementInterface $source, ElementIn
*/
public function applyDraft(ElementInterface $draft): ElementInterface
{
- /** @var Element|DraftBehavior $draft */
+ /** @var ElementInterface|DraftBehavior $draft */
/** @var DraftBehavior $behavior */
$behavior = $draft->getBehavior('draft');
- /** @var Element $source */
$source = ElementHelper::sourceElement($draft);
+ // If there is no source element for the same site, find a different site to do this from
+ if ($source === null) {
+ $draftSiteIds = ArrayHelper::getColumn(ElementHelper::supportedSitesForElement($draft), 'siteId');
+ $source = $draft::find()
+ ->id($draft->getSourceId())
+ ->siteId($draftSiteIds)
+ ->unique()
+ ->anyStatus()
+ ->one();
+ if ($source === null) {
+ throw new Exception('Could not find a source element for the draft in any of its supported sites.');
+ }
+ $draft = $draft::find()
+ ->drafts()
+ ->id($draft->id)
+ ->siteId($source->siteId)
+ ->anyStatus()
+ ->one();
+ if ($draft === null) {
+ throw new Exception("Could not load the draft for site ID $source->siteId");
+ }
+ }
+
// Fire a 'beforeApplyDraft' event
if ($this->hasEventHandlers(self::EVENT_BEFORE_APPLY_DRAFT)) {
$this->trigger(self::EVENT_BEFORE_APPLY_DRAFT, new DraftEvent([
@@ -453,13 +466,12 @@ public function purgeUnsavedDrafts()
$drafts = (new Query())
->select(['e.draftId', 'e.type'])
->from(['e' => Table::ELEMENTS])
- ->innerJoin(Table::DRAFTS . ' d', '[[d.id]] = [[e.draftId]]')
+ ->innerJoin(['d' => Table::DRAFTS], '[[d.id]] = [[e.draftId]]')
->where(['d.sourceId' => null])
->andWhere(['<', 'e.dateCreated', Db::prepareDateForDb($pastTime)])
->all();
$elementsService = Craft::$app->getElements();
- $db = Craft::$app->getDb();
foreach ($drafts as $draftInfo) {
/** @var ElementInterface|string $elementType */
@@ -476,9 +488,9 @@ public function purgeUnsavedDrafts()
// Perhaps the draft's row in the `entries` table was deleted manually or something.
// Just drop its row in the `drafts` table, and let that cascade to `elements` and whatever other tables
// still have rows for the draft.
- $db->createCommand()
- ->delete(Table::DRAFTS, ['id' => $draftInfo['draftId']])
- ->execute();
+ Db::delete(Table::DRAFTS, [
+ 'id' => $draftInfo['draftId'],
+ ]);
}
Craft::info("Just deleted unsaved draft ID {$draftInfo['draftId']}", __METHOD__);
@@ -498,16 +510,13 @@ public function purgeUnsavedDrafts()
*/
private function _insertDraftRow(int $sourceId = null, int $creatorId, string $name = null, string $notes = null, bool $trackChanges = false): int
{
- $db = Craft::$app->getDb();
- $db->createCommand()
- ->insert(Table::DRAFTS, [
- 'sourceId' => $sourceId,
- 'creatorId' => $creatorId,
- 'name' => $name,
- 'notes' => $notes,
- 'trackChanges' => $trackChanges,
- ], false)
- ->execute();
- return $db->getLastInsertID(Table::DRAFTS);
+ Db::insert(Table::DRAFTS, [
+ 'sourceId' => $sourceId,
+ 'creatorId' => $creatorId,
+ 'name' => $name,
+ 'notes' => $notes,
+ 'trackChanges' => $trackChanges,
+ ], false);
+ return Craft::$app->getDb()->getLastInsertID(Table::DRAFTS);
}
}
diff --git a/src/services/ElementIndexes.php b/src/services/ElementIndexes.php
index 0831582136e..167acc19c69 100644
--- a/src/services/ElementIndexes.php
+++ b/src/services/ElementIndexes.php
@@ -9,11 +9,11 @@
use Craft;
use craft\base\ElementInterface;
-use craft\base\Field;
use craft\base\FieldInterface;
use craft\base\PreviewableFieldInterface;
use craft\db\Query;
use craft\db\Table;
+use craft\helpers\Db;
use craft\helpers\Json;
use yii\base\Component;
@@ -119,20 +119,18 @@ public function saveSettings(string $elementType, array $newSettings): bool
}
}
- $affectedRows = Craft::$app->getDb()->createCommand()
- ->upsert(
- Table::ELEMENTINDEXSETTINGS,
- ['type' => $elementType],
- ['settings' => Json::encode($settings)])
- ->execute();
+ $success = (bool)Db::upsert(Table::ELEMENTINDEXSETTINGS, [
+ 'type' => $elementType,
+ ], [
+ 'settings' => Json::encode($settings),
+ ]);
- if ($affectedRows) {
- $this->_indexSettings[$elementType] = $settings;
-
- return true;
+ if (!$success) {
+ return false;
}
- return false;
+ $this->_indexSettings[$elementType] = $settings;
+ return true;
}
/**
@@ -213,7 +211,6 @@ public function getAvailableTableAttributes(string $elementType, bool $includeFi
if ($includeFields) {
// Mix in custom fields
foreach ($this->getAvailableTableFields($elementType) as $field) {
- /** @var Field $field */
$attributes['field:' . $field->id] = ['label' => Craft::t('site', $field->name)];
}
}
diff --git a/src/services/Elements.php b/src/services/Elements.php
index 0819fcc1214..9780221103f 100644
--- a/src/services/Elements.php
+++ b/src/services/Elements.php
@@ -9,11 +9,9 @@
use Craft;
use craft\base\Element;
-use craft\base\ElementAction;
use craft\base\ElementActionInterface;
use craft\base\ElementExporterInterface;
use craft\base\ElementInterface;
-use craft\base\Field;
use craft\behaviors\DraftBehavior;
use craft\behaviors\RevisionBehavior;
use craft\db\Query;
@@ -21,7 +19,6 @@
use craft\db\Table;
use craft\elements\Asset;
use craft\elements\Category;
-use craft\elements\db\ElementQuery;
use craft\elements\db\ElementQueryInterface;
use craft\elements\Entry;
use craft\elements\GlobalSet;
@@ -43,6 +40,7 @@
use craft\helpers\DateTimeHelper;
use craft\helpers\Db;
use craft\helpers\ElementHelper;
+use craft\helpers\Queue;
use craft\helpers\StringHelper;
use craft\queue\jobs\FindAndReplace;
use craft\queue\jobs\UpdateElementSlugsAndUris;
@@ -50,12 +48,12 @@
use craft\records\Element as ElementRecord;
use craft\records\Element_SiteSettings as Element_SiteSettingsRecord;
use craft\records\StructureElement as StructureElementRecord;
+use craft\validators\HandleValidator;
use craft\validators\SlugValidator;
use yii\base\Behavior;
use yii\base\Component;
use yii\base\Exception;
use yii\base\InvalidArgumentException;
-use yii\base\NotSupportedException;
use yii\db\Exception as DbException;
/**
@@ -272,6 +270,23 @@ public function createElement($config): ElementInterface
return ComponentHelper::createComponent($config, ElementInterface::class);
}
+ /**
+ * Creates an element query for a given element type.
+ *
+ * @param string $elementType The element class
+ * @return ElementQueryInterface The element query
+ * @throws InvalidArgumentException if $elementType is not a valid element
+ * @since 3.5.0
+ */
+ public function createElementQuery(string $elementType): ElementQueryInterface
+ {
+ if (!is_subclass_of($elementType, ElementInterface::class)) {
+ throw new InvalidArgumentException("$elementType is not a valid element.");
+ }
+
+ return $elementType::find();
+ }
+
// Finding Elements
// -------------------------------------------------------------------------
@@ -308,9 +323,7 @@ public function getElementById(int $elementId, string $elementType = null, $site
return null;
}
- /** @var Element $elementType */
- /** @var ElementQuery $query */
- $query = $elementType::find();
+ $query = $this->createElementQuery($elementType);
$query->id = $elementId;
$query->siteId = $siteId;
$query->anyStatus();
@@ -364,8 +377,8 @@ public function getElementByUri(string $uri, int $siteId = null, bool $enabledOn
// First get the element ID and type
$query = (new Query())
->select(['elements.id', 'elements.type'])
- ->from(['{{%elements}} elements'])
- ->innerJoin('{{%elements_sites}} elements_sites', '[[elements_sites.elementId]] = [[elements.id]]')
+ ->from(['elements' => Table::ELEMENTS])
+ ->innerJoin(['elements_sites' => Table::ELEMENTS_SITES], '[[elements_sites.elementId]] = [[elements.id]]')
->where([
'elements_sites.siteId' => $siteId,
]);
@@ -525,7 +538,6 @@ public function getEnabledSiteIdsForElement(int $elementId): array
public function saveElement(ElementInterface $element, bool $runValidation = true, bool $propagate = true, bool $updateSearchIndex = null): bool
{
// Force propagation for new elements
- /** @var Element $element */
$propagate = !$element->id || $propagate;
return $this->_saveElementInternal($element, $runValidation, $propagate, $updateSearchIndex);
@@ -554,11 +566,9 @@ public function resaveElements(ElementQueryInterface $query, bool $continueOnErr
$position = 0;
try {
- /** @var ElementQuery $query */
foreach ($query->each() as $element) {
$position++;
- /** @var Element $element */
$element->setScenario(Element::SCENARIO_ESSENTIALS);
$element->resaving = true;
@@ -651,14 +661,11 @@ public function propagateElements(ElementQueryInterface $query, $siteIds = null,
$position = 0;
try {
- /** @var ElementQuery $query */
foreach ($query->each() as $element) {
$position++;
- /** @var Element $element */
$element->setScenario(Element::SCENARIO_ESSENTIALS);
$elementSiteIds = $siteIds ?? ArrayHelper::getColumn(ElementHelper::supportedSitesForElement($element), 'siteId');
- /** @var ElementInterface|string $elementType */
$elementType = get_class($element);
// Fire a 'beforePropagateElement' event
@@ -677,7 +684,6 @@ public function propagateElements(ElementQueryInterface $query, $siteIds = null,
foreach ($elementSiteIds as $siteId) {
if ($siteId != $element->siteId) {
// Make sure the site element wasn't updated more recently than the main one
- /** @var Element $siteElement */
$siteElement = $this->getElementById($element->id, $elementType, $siteId);
if ($siteElement === null || $siteElement->dateUpdated < $element->dateUpdated) {
$this->propagateElement($element, $siteId, $siteElement ?? false);
@@ -729,17 +735,16 @@ public function propagateElements(ElementQueryInterface $query, $siteIds = null,
public function duplicateElement(ElementInterface $element, array $newAttributes = []): ElementInterface
{
// Make sure the element exists
- /** @var Element $element */
if (!$element->id) {
throw new Exception('Attempting to duplicate an unsaved element.');
}
// Create our first clone for the $element's site
$element->getFieldValues();
- /** @var Element $mainClone */
$mainClone = clone $element;
$mainClone->id = null;
$mainClone->uid = null;
+ $mainClone->elementSiteId = null;
$mainClone->contentId = null;
$mainClone->dateCreated = null;
$mainClone->duplicateOf = $element;
@@ -757,7 +762,7 @@ public function duplicateElement(ElementInterface $element, array $newAttributes
}
// Make sure the element actually supports its own site ID
- $supportedSites = ElementHelper::supportedSitesForElement($mainClone);
+ $supportedSites = ElementHelper::supportedSitesForElement($element);
$supportedSiteIds = ArrayHelper::getColumn($supportedSites, 'siteId');
if (!in_array($mainClone->siteId, $supportedSiteIds, false)) {
throw new Exception('Attempting to duplicate an element in an unsupported site.');
@@ -791,7 +796,7 @@ public function duplicateElement(ElementInterface $element, array $newAttributes
// Propagate it
foreach ($supportedSites as $siteInfo) {
if ($siteInfo['siteId'] != $mainClone->siteId) {
- $siteQuery = $element::find()
+ $siteQuery = $this->createElementQuery(get_class($element))
->id($element->id ?: false)
->siteId($siteInfo['siteId'])
->anyStatus();
@@ -810,13 +815,13 @@ public function duplicateElement(ElementInterface $element, array $newAttributes
}
$siteElement->getFieldValues();
- /** @var Element $siteClone */
$siteClone = clone $siteElement;
$siteClone->duplicateOf = $siteElement;
$siteClone->propagating = true;
$siteClone->id = $mainClone->id;
$siteClone->uid = $mainClone->uid;
$siteClone->enabled = $mainClone->enabled;
+ $siteClone->elementSiteId = null;
$siteClone->contentId = null;
$siteClone->dateCreated = null;
@@ -877,9 +882,8 @@ public function duplicateElement(ElementInterface $element, array $newAttributes
*/
public function updateElementSlugAndUri(ElementInterface $element, bool $updateOtherSites = true, bool $updateDescendants = true, bool $queue = false)
{
- /** @var Element $element */
if ($queue) {
- Craft::$app->getQueue()->push(new UpdateElementSlugsAndUris([
+ Queue::push(new UpdateElementSlugsAndUris([
'elementId' => $element->id,
'elementType' => get_class($element),
'siteId' => $element->siteId,
@@ -901,18 +905,13 @@ public function updateElementSlugAndUri(ElementInterface $element, bool $updateO
]));
}
- Craft::$app->getDb()->createCommand()
- ->update(
- Table::ELEMENTS_SITES,
- [
- 'slug' => $element->slug,
- 'uri' => $element->uri
- ],
- [
- 'elementId' => $element->id,
- 'siteId' => $element->siteId
- ])
- ->execute();
+ Db::update(Table::ELEMENTS_SITES, [
+ 'slug' => $element->slug,
+ 'uri' => $element->uri,
+ ], [
+ 'elementId' => $element->id,
+ 'siteId' => $element->siteId,
+ ]);
// Fire a 'afterUpdateSlugAndUri' event
if ($this->hasEventHandlers(self::EVENT_AFTER_UPDATE_SLUG_AND_URI)) {
@@ -940,13 +939,12 @@ public function updateElementSlugAndUri(ElementInterface $element, bool $updateO
*/
public function updateElementSlugAndUriInOtherSites(ElementInterface $element)
{
- /** @var Element $element */
foreach (Craft::$app->getSites()->getAllSiteIds() as $siteId) {
if ($siteId == $element->siteId) {
continue;
}
- $elementInOtherSite = $element::find()
+ $elementInOtherSite = $this->createElementQuery(get_class($element))
->id($element->id)
->siteId($siteId)
->one();
@@ -966,9 +964,7 @@ public function updateElementSlugAndUriInOtherSites(ElementInterface $element)
*/
public function updateDescendantSlugsAndUris(ElementInterface $element, bool $updateOtherSites = true, bool $queue = false)
{
- /** @var Element $element */
- /** @var ElementQuery $query */
- $query = $element::find()
+ $query = $this->createElementQuery(get_class($element))
->descendantOf($element)
->descendantDist(1)
->anyStatus()
@@ -978,7 +974,7 @@ public function updateDescendantSlugsAndUris(ElementInterface $element, bool $up
$childIds = $query->ids();
if (!empty($childIds)) {
- Craft::$app->getQueue()->push(new UpdateElementSlugsAndUris([
+ Queue::push(new UpdateElementSlugsAndUris([
'elementId' => $childIds,
'elementType' => get_class($element),
'siteId' => $element->siteId,
@@ -1041,10 +1037,7 @@ public function mergeElementsByIds(int $mergedElementId, int $prevailingElementI
*/
public function mergeElements(ElementInterface $mergedElement, ElementInterface $prevailingElement): bool
{
- /** @var Element $mergedElement */
- /** @var Element $prevailingElement */
- $db = Craft::$app->getDb();
- $transaction = $db->beginTransaction();
+ $transaction = Craft::$app->getDb()->beginTransaction();
try {
// Update any relations that point to the merged element
$relations = (new Query())
@@ -1066,16 +1059,11 @@ public function mergeElements(ElementInterface $mergedElement, ElementInterface
->exists();
if (!$persistingElementIsRelatedToo) {
- $db->createCommand()
- ->update(
- Table::RELATIONS,
- [
- 'targetId' => $prevailingElement->id
- ],
- [
- 'id' => $relation['id']
- ])
- ->execute();
+ Db::update(Table::RELATIONS, [
+ 'targetId' => $prevailingElement->id,
+ ], [
+ 'id' => $relation['id'],
+ ]);
}
}
@@ -1097,15 +1085,11 @@ public function mergeElements(ElementInterface $mergedElement, ElementInterface
->exists();
if (!$persistingElementIsInStructureToo) {
- $db->createCommand()
- ->update(Table::STRUCTUREELEMENTS,
- [
- 'elementId' => $prevailingElement->id
- ],
- [
- 'id' => $structureElement['id']
- ])
- ->execute();
+ Db::update(Table::STRUCTUREELEMENTS, [
+ 'elementId' => $prevailingElement->id,
+ ], [
+ 'id' => $structureElement['id'],
+ ]);
}
}
@@ -1115,15 +1099,14 @@ public function mergeElements(ElementInterface $mergedElement, ElementInterface
if ($elementType !== null && ($refHandle = $elementType::refHandle()) !== null) {
$refTagPrefix = "{{$refHandle}:";
- $queue = Craft::$app->getQueue();
- $queue->push(new FindAndReplace([
+ Queue::push(new FindAndReplace([
'description' => Craft::t('app', 'Updating element references'),
'find' => $refTagPrefix . $mergedElement->id . ':',
'replace' => $refTagPrefix . $prevailingElement->id . ':',
]));
- $queue->push(new FindAndReplace([
+ Queue::push(new FindAndReplace([
'description' => Craft::t('app', 'Updating element references'),
'find' => $refTagPrefix . $mergedElement->id . '}',
'replace' => $refTagPrefix . $prevailingElement->id . '}',
@@ -1205,7 +1188,6 @@ public function deleteElementById(int $elementId, string $elementType = null, in
*/
public function deleteElement(ElementInterface $element, bool $hardDelete = false): bool
{
- /** @var Element $element */
// Fire a 'beforeDeleteElement' event
$event = new DeleteElementEvent([
'element' => $element,
@@ -1239,12 +1221,12 @@ public function deleteElement(ElementInterface $element, bool $hardDelete = fals
Craft::$app->getTemplateCaches()->deleteCachesByElementId($element->id, false);
if ($element->hardDelete) {
- $db->createCommand()
- ->delete(Table::ELEMENTS, ['id' => $element->id])
- ->execute();
- $db->createCommand()
- ->delete(Table::SEARCHINDEX, ['elementId' => $element->id])
- ->execute();
+ Db::delete(Table::ELEMENTS, [
+ 'id' => $element->id,
+ ]);
+ Db::delete(Table::SEARCHINDEX, [
+ 'elementId' => $element->id,
+ ]);
} else {
// Soft delete the elements table row
$db->createCommand()
@@ -1299,7 +1281,6 @@ public function restoreElements(array $elements): bool
{
// Fire "before" events
foreach ($elements as $element) {
- /** @var Element $element */
// Fire a 'beforeRestoreElement' event
if ($this->hasEventHandlers(self::EVENT_BEFORE_RESTORE_ELEMENT)) {
$this->trigger(self::EVENT_BEFORE_RESTORE_ELEMENT, new ElementEvent([
@@ -1330,12 +1311,11 @@ public function restoreElements(array $elements): bool
// Get the element in each supported site
$siteElements = [];
- /** @var Element|string $class */
$class = get_class($element);
foreach ($supportedSites as $siteInfo) {
$siteId = $siteInfo['siteId'];
if ($siteId != $element->siteId) {
- $siteElement = $class::find()
+ $siteElement = $this->createElementQuery($class)
->id($element->id)
->siteId($siteId)
->anyStatus()
@@ -1442,10 +1422,7 @@ public function getAllElementTypes(): array
*/
public function createAction($config): ElementActionInterface
{
- /** @var ElementAction $action */
- $action = ComponentHelper::createComponent($config, ElementActionInterface::class);
-
- return $action;
+ return ComponentHelper::createComponent($config, ElementActionInterface::class);
}
/**
@@ -1505,37 +1482,42 @@ public function parseRefs(string $str, int $defaultSiteId = null): string
$sitesService = Craft::$app->getSites();
$allRefTagTokens = [];
$str = preg_replace_callback(
- '/\{([\w\\\\]+)\:([^@\:\}]+)(?:@([^\:\}]+))?(?:\:([^\}]+))?\}/',
- function($matches) use (
+ '/\{([\w\\\\]+)\:([^@\:\}]+)(?:@([^\:\}]+))?(?:\:([^\}\| ]+))?(?: *\|\| *([^\}]+))?\}/',
+ function(array $matches) use (
$defaultSiteId,
$sitesService,
&$allRefTagTokens
) {
+ $matches = array_pad($matches, 6, null);
+ list($fullMatch, $elementType, $ref, $siteId, $attribute, $fallback) = $matches;
+ if ($fallback === null) {
+ $fallback = $fullMatch;
+ }
+
// Does it already have a full element type class name?
- if (is_subclass_of($matches[1], ElementInterface::class)) {
- $elementType = $matches[1];
- } else if (($elementType = $this->getElementTypeByRefHandle($matches[1])) === null) {
- // Leave the tag alone
- return $matches[0];
+ if (
+ !is_subclass_of($elementType, ElementInterface::class) &&
+ ($elementType = $this->getElementTypeByRefHandle($elementType)) === null
+ ) {
+ return $fallback;
}
// Get the site
- if (!empty($matches[3])) {
- if (is_numeric($matches[3])) {
- $siteId = (int)$matches[3];
+ if (!empty($siteId)) {
+ if (is_numeric($siteId)) {
+ $siteId = (int)$siteId;
} else {
try {
- if (StringHelper::isUUID($matches[3])) {
- $site = $sitesService->getSiteByUid($matches[3]);
+ if (StringHelper::isUUID($siteId)) {
+ $site = $sitesService->getSiteByUid($siteId);
} else {
- $site = $sitesService->getSiteByHandle($matches[3]);
+ $site = $sitesService->getSiteByHandle($siteId);
}
} catch (SiteNotFoundException $e) {
$site = null;
}
if (!$site) {
- // Leave the tag alone
- return $matches[0];
+ return $fallback;
}
$siteId = $site->id;
}
@@ -1543,9 +1525,9 @@ function($matches) use (
$siteId = $defaultSiteId;
}
- $refType = is_numeric($matches[2]) ? 'id' : 'ref';
+ $refType = is_numeric($ref) ? 'id' : 'ref';
$token = '{' . StringHelper::randomString(9) . '}';
- $allRefTagTokens[$siteId][$elementType][$refType][$matches[2]][] = [$token, $matches];
+ $allRefTagTokens[$siteId][$elementType][$refType][$ref][] = [$token, $attribute, $fallback, $fullMatch];
return $token;
}, $str, -1, $count);
@@ -1561,11 +1543,10 @@ function($matches) use (
foreach ($allRefTagTokens as $siteId => $siteTokens) {
foreach ($siteTokens as $elementType => $tokensByType) {
- /** @var Element|string $elementType */
foreach ($tokensByType as $refType => $tokensByName) {
// Get the elements, indexed by their ref value
$refNames = array_keys($tokensByName);
- $elementQuery = $elementType::find()
+ $elementQuery = $this->createElementQuery($elementType)
->siteId($siteId)
->anyStatus();
@@ -1581,9 +1562,9 @@ function($matches) use (
foreach ($tokensByName as $refName => $tokens) {
$element = $elements[$refName] ?? null;
- foreach ($tokens as list($token, $matches)) {
+ foreach ($tokens as list($token, $attribute, $fallback, $fullMatch)) {
$search[] = $token;
- $replace[] = $this->_getRefTokenReplacement($element, $matches);
+ $replace[] = $this->_getRefTokenReplacement($element, $attribute, $fallback, $fullMatch);
}
}
}
@@ -1608,7 +1589,6 @@ function($matches) use (
*/
public function setPlaceholderElement(ElementInterface $element)
{
- /** @var Element $element */
// Won't be able to do anything with this if it doesn't have an ID or site ID
if (!$element->id || !$element->siteId) {
throw new InvalidArgumentException('Placeholder element is missing an ID');
@@ -1658,215 +1638,198 @@ public function getPlaceholderElement(int $sourceId, int $siteId)
*/
public function eagerLoadElements(string $elementType, array $elements, $with)
{
- /** @var Element[] $elements */
+ /** @var ElementInterface|string $elementType */
// Bail if there aren't even any elements
if (empty($elements)) {
return;
}
- // Normalize the paths and find any custom path criterias
+ $elements = array_values($elements);
+
+ // Normalize the paths and group based on the top level eager loading handle
if (is_string($with)) {
$with = StringHelper::split($with);
}
- $paths = [];
- $pathCriterias = [];
- $countPaths = [];
+ $groups = [];
foreach ($with as $path) {
- // Using the array syntax?
- // ['foo.bar'] or ['foo.bar', criteria]
+ // Separate the path and the criteria
if (is_array($path)) {
- if (!empty($path[1])) {
- // Is this a count path?
- if (ArrayHelper::remove($path[1], 'count', false)) {
- $countPaths[$path[0]] = true;
- }
- $pathCriterias['__root__.' . $path[0]] = $path[1];
- }
-
- $paths[] = $path[0];
+ $criteria = $path[1] ?? null;
+ $path = $path[0];
} else {
- $paths[] = $path;
+ $criteria = null;
}
- }
- // Load 'em up!
- $elementsByPath = ['__root__' => $elements];
- $elementTypesByPath = ['__root__' => $elementType];
- $eagerLoadingMapsByPath = [];
+ // Split the path into the top segment and subpath
+ if (($dot = strpos($path, '.')) !== false) {
+ $handle = substr($path, 0, $dot);
+ $subpath = substr($path, $dot + 1);
+ } else {
+ $handle = $path;
+ $subpath = null;
+ }
- foreach ($paths as $path) {
- $pathSegments = explode('.', $path);
- $totalSegments = count($pathSegments);
- $sourcePath = '__root__';
+ // Get the handle & alias
+ if (preg_match('/^(' . HandleValidator::$handlePattern . ')\s+as\s+(' . HandleValidator::$handlePattern . ')$/', $handle, $match)) {
+ $handle = $match[1];
+ $alias = $match[2];
+ } else {
+ $alias = $handle;
+ }
- foreach ($pathSegments as $segIndex => $segment) {
- $targetPath = $sourcePath . '.' . $segment;
- $pathCriteria = $pathCriterias[$targetPath] ?? [];
+ if (!isset($groups[$alias])) {
+ // [handle, criteria, getCount, subWith]
+ $groups[$alias] = [$handle, [], false, []];
+ }
- // Are we just fetching the count?
- $getCount = isset($countPaths[$path]) && $segIndex === $totalSegments - 1;
+ // Only set the criteria if there's no subpath
+ if ($subpath === null) {
+ if ($criteria !== null) {
+ $getCount = ArrayHelper::remove($criteria, 'count', false);
+ $groups[$alias][1] = $criteria;
+ $groups[$alias][2] = $groups[$alias][2] || $getCount;
+ }
+ } else {
+ // Add this as a nested "with"
+ $groups[$alias][3][] = [$subpath, $criteria];
+ }
+ }
- // Figure out the path mapping wants a custom order
- $useCustomOrder = !$getCount && (
- !empty($pathCriteria['orderBy']) ||
- !empty($pathCriteria['order'])
- );
+ foreach ($groups as $alias => list($handle, $criteria, $getCount, $subWith)) {
+ /** @var string $handle */
+ /** @var array $criteria */
+ /** @var bool $getCount */
+ /** @var array $subWith */
- // Make sure we haven't already eager-loaded this target path
- if ($getCount || !isset($elementsByPath[$targetPath])) {
- // Have we already fetched the map from an earlier `count` path?
- if (array_key_exists($targetPath, $eagerLoadingMapsByPath)) {
- $map = $eagerLoadingMapsByPath[$targetPath];
- } else {
- // Get the eager-loading map from the source element type
- /** @var Element $sourceElementType */
- $sourceElementType = $elementTypesByPath[$sourcePath];
- $map = $eagerLoadingMapsByPath[$targetPath] = $sourceElementType::eagerLoadingMap(array_values($elementsByPath[$sourcePath]), $segment);
- }
+ // Figure out the path mapping wants a custom order
+ $useCustomOrder = !empty($criteria['orderBy']) || !empty($criteria['order']);
- if ($map === null) {
- break;
- }
+ // Get the eager-loading map from the source element type
+ $map = $elementType::eagerLoadingMap($elements, $handle);
- $targetElementIdsBySourceIds = null;
- $query = null;
- $offset = 0;
- $limit = null;
-
- if ($map && !empty($map['map'])) {
- // Remember the element type in case there are more segments after this
- $elementTypesByPath[$targetPath] = $map['elementType'];
+ if ($map === null) {
+ // Null means to skip eager-loading this segment
+ continue;
+ }
- // Loop through the map to find:
- // - unique target element IDs
- // - target element IDs indexed by source element IDs
- $uniqueTargetElementIds = [];
- $targetElementIdsBySourceIds = [];
+ $targetElementIdsBySourceIds = null;
+ $query = null;
+ $offset = 0;
+ $limit = null;
+
+ if (!empty($map['map'])) {
+ // Loop through the map to find:
+ // - unique target element IDs
+ // - target element IDs indexed by source element IDs
+ $uniqueTargetElementIds = [];
+ $targetElementIdsBySourceIds = [];
+
+ foreach ($map['map'] as $mapping) {
+ $uniqueTargetElementIds[$mapping['target']] = true;
+ $targetElementIdsBySourceIds[$mapping['source']][$mapping['target']] = true;
+ }
- foreach ($map['map'] as $mapping) {
- if (!in_array($mapping['target'], $uniqueTargetElementIds, false)) {
- $uniqueTargetElementIds[] = $mapping['target'];
- }
+ // Get the target elements
+ $query = $this->createElementQuery($map['elementType']);
- $targetElementIdsBySourceIds[$mapping['source']][$mapping['target']] = true;
- }
+ // Default to no order, offset, or limit, but allow the element type/path criteria to override
+ $query->orderBy = null;
+ $query->offset = null;
+ $query->limit = null;
- // Get the target elements
- /** @var Element $targetElementType */
- $targetElementType = $map['elementType'];
- /** @var ElementQuery $query */
- $query = $targetElementType::find();
+ $criteria = array_merge(
+ $map['criteria'] ?? [],
+ $criteria
+ );
- // Default to no order, offset, or limit, but allow the element type/path criteria to override
- $query->orderBy = null;
- $query->offset = null;
- $query->limit = null;
+ // Save the offset & limit params for later
+ $offset = ArrayHelper::remove($criteria, 'offset', 0);
+ $limit = ArrayHelper::remove($criteria, 'limit');
- $criteria = array_merge(
- $map['criteria'] ?? [],
- $pathCriteria
- );
+ Craft::configure($query, $criteria);
- // Save the offset & limit params for later
- $offset = ArrayHelper::remove($criteria, 'offset', 0);
- $limit = ArrayHelper::remove($criteria, 'limit');
+ if (!$query->siteId) {
+ $query->siteId = reset($elements)->siteId;
+ }
- Craft::configure($query, $criteria);
+ $query->andWhere([
+ 'elements.id' => array_keys($uniqueTargetElementIds),
+ ]);
+ }
- if (!$query->siteId) {
- $query->siteId = reset($elements)->siteId;
+ // Do we just need the count?
+ if ($getCount && empty($subWith)) {
+ // Just fetch the target elements’ IDs
+ $targetElementIds = $query ? array_flip($query->ids()) : [];
+
+ // Loop through the source elements and count up their targets
+ foreach ($elements as $sourceElement) {
+ $count = 0;
+ if (!empty($targetElementIds) && isset($targetElementIdsBySourceIds[$sourceElement->id])) {
+ foreach (array_keys($targetElementIdsBySourceIds[$sourceElement->id]) as $targetElementId) {
+ if (isset($targetElementIds[$targetElementId])) {
+ $count++;
+ }
}
-
- $query->andWhere(['elements.id' => $uniqueTargetElementIds]);
}
-
- if ($getCount) {
- // Just fetch the target elements’ IDs
- $targetElementIds = $query ? array_flip($query->ids()) : [];
-
- // Loop through the source elements and count up their targets
- foreach ($elementsByPath[$sourcePath] as $sourceElement) {
- $count = 0;
- if (!empty($targetElementIds) && isset($targetElementIdsBySourceIds[$sourceElement->id])) {
- foreach (array_keys($targetElementIdsBySourceIds[$sourceElement->id]) as $targetElementId) {
- if (isset($targetElementIds[$targetElementId])) {
- $count++;
- }
+ $sourceElement->setEagerLoadedElementCount($alias, $count);
+ }
+ } else {
+ /** @var array|ElementInterface[] $targetElementData */
+ $targetElementData = $query ? $query->asArray()->indexBy('id')->all() : [];
+ $targetElements = [];
+
+ // Tell the source elements about their eager-loaded elements
+ foreach ($elements as $sourceElement) {
+ $targetElementIdsForSource = [];
+ $targetElementsForSource = [];
+
+ if (isset($targetElementIdsBySourceIds[$sourceElement->id])) {
+ if ($useCustomOrder) {
+ // Assign the elements in the order they were returned from the query
+ foreach ($targetElementData as &$elementData) {
+ /** @var array|ElementInterface $elementData */
+ if (isset($targetElementIdsBySourceIds[$sourceElement->id][$elementData['id']])) {
+ $targetElementIdsForSource[] = $elementData['id'];
}
}
- $sourceElement->setEagerLoadedElementCount($segment, $count);
- }
- } else {
- /** @var array|ElementInterface[] $targetElements */
- $targetElements = $query ? $query->asArray()->all() : [];
- $elementsByPath[$targetPath] = [];
-
- // Index the target elements by their IDs if we are using the map-defined order
- if (!$useCustomOrder) {
- /** @var array|ElementInterface[] $targetElementsById */
- $targetElementsById = [];
- foreach ($targetElements as &$targetElement) {
- $targetElementsById[$targetElement['id']] = &$targetElement;
+ unset($elementData);
+ } else {
+ // Assign the elements in the order defined by the map
+ foreach (array_keys($targetElementIdsBySourceIds[$sourceElement->id]) as $targetElementId) {
+ if (isset($targetElementData[$targetElementId])) {
+ $targetElementIdsForSource[] = $targetElementId;
+ }
}
}
- // Tell the source elements about their eager-loaded elements (or lack thereof, as the case may be)
- foreach ($elementsByPath[$sourcePath] as $sourceElement) {
- /** @var Element $sourceElement */
- $targetElementsForSource = [];
-
- if (isset($targetElementIdsBySourceIds[$sourceElement->id])) {
- if ($useCustomOrder) {
- // Assign the elements in the order they were returned from the query
- foreach ($targetElements as &$targetElement) {
- /** @var array|ElementInterface $targetElement */
- $targetElementId = ArrayHelper::getValue($targetElement, 'id');
- if (isset($targetElementIdsBySourceIds[$sourceElement->id][$targetElementId])) {
- $targetElementsForSource[] = &$targetElement;
- }
- }
- } else {
- // Assign the elements in the order defined by the map
- foreach (array_keys($targetElementIdsBySourceIds[$sourceElement->id]) as $targetElementId) {
- if (isset($targetElementsById[$targetElementId])) {
- $targetElementsForSource[] = &$targetElementsById[$targetElementId];
- }
- }
- }
-
- // Ignore elements that don't fall within the offset & limit
- if ($offset || $limit) {
- $targetElementsForSource = array_slice($targetElementsForSource, $offset, $limit);
- }
-
- foreach ($targetElementsForSource as &$targetElement) {
- // Make sure the element has been instantiated
- if (is_array($targetElement)) {
- $targetElement = $query->createElement($targetElement);
- }
+ // Ignore elements that don't fall within the offset & limit
+ if ($offset || $limit) {
+ $targetElementIdsForSource = array_slice($targetElementIdsForSource, $offset, $limit);
+ }
- // Store it on $elementsByPath FFR
- if (!isset($elementsByPath[$targetPath][$targetElement->id])) {
- $elementsByPath[$targetPath][$targetElement->id] = $targetElement;
- }
- }
- unset($targetElement);
+ // Create the elements
+ foreach ($targetElementIdsForSource as $elementId) {
+ if (!isset($targetElements[$elementId])) {
+ $targetElements[$elementId] = $query->createElement($targetElementData[$elementId]);
}
-
- $sourceElement->setEagerLoadedElements($segment, $targetElementsForSource);
+ $targetElementsForSource[] = $targetElements[$elementId];
}
}
- }
- if (empty($elementsByPath[$targetPath])) {
- // Dead end - stop wasting time on this path
- break;
+ $sourceElement->setEagerLoadedElements($alias, $targetElementsForSource);
+
+ if ($getCount) {
+ $sourceElement->setEagerLoadedElementCount($alias, count($targetElementsForSource));
+ }
}
- // Update the source path
- $sourcePath = $targetPath;
+ // Now eager-load any sub paths
+ if (!empty($map['map']) && !empty($subWith)) {
+ $this->eagerLoadElements($map['elementType'], $targetElements, $subWith);
+ }
}
}
}
@@ -1913,23 +1876,22 @@ public function propagateElement(ElementInterface $element, int $siteId, $siteEl
*/
private function _saveElementInternal(ElementInterface $element, bool $runValidation = true, bool $propagate = true, bool $updateSearchIndex = null): bool
{
- /** @var Element|DraftBehavior|RevisionBehavior $element */
+ /** @var ElementInterface|DraftBehavior|RevisionBehavior $element */
$isNewElement = !$element->id;
/** @var DraftBehavior|null $draftBehavior */
$draftBehavior = $element->getIsDraft() ? $element->getBehavior('draft') : null;
- $db = Craft::$app->getDb();
-
// Are we tracking changes?
// todo: remove the tableExists condition after the next breakpoint
$trackChanges = (
!$isNewElement &&
+ $element->elementSiteId &&
$element->duplicateOf === null &&
$element::trackChanges() &&
($draftBehavior->trackChanges ?? true) &&
!($draftBehavior->mergingChanges ?? false) &&
- $db->tableExists(Table::CHANGEDATTRIBUTES)
+ Craft::$app->getDb()->tableExists(Table::CHANGEDATTRIBUTES)
);
$dirtyAttributes = [];
@@ -2085,7 +2047,7 @@ private function _saveElementInternal(ElementInterface $element, bool $runValida
]);
}
- if (empty($siteSettingsRecord)) {
+ if ($isNewSiteElement = empty($siteSettingsRecord)) {
// First time we've saved the element for this site
$siteSettingsRecord = new Element_SiteSettingsRecord();
$siteSettingsRecord->elementId = $element->id;
@@ -2102,7 +2064,7 @@ private function _saveElementInternal(ElementInterface $element, bool $runValida
}
// Update our list of dirty attributes
- if ($trackChanges && !$siteSettingsRecord->getIsNewRecord()) {
+ if ($trackChanges && !$isNewSiteElement) {
ArrayHelper::append($dirtyAttributes, ...array_keys($siteSettingsRecord->getDirtyAttributes([
'slug',
'uri',
@@ -2116,6 +2078,8 @@ private function _saveElementInternal(ElementInterface $element, bool $runValida
throw new Exception('Couldn’t save elements’ site settings record.');
}
+ $element->elementSiteId = $siteSettingsRecord->id;
+
// Save the content
if ($element::hasContent()) {
Craft::$app->getContent()->saveContent($element);
@@ -2194,19 +2158,12 @@ private function _saveElementInternal(ElementInterface $element, bool $runValida
if (Craft::$app->getRequest()->getIsConsoleRequest()) {
Craft::$app->getSearch()->indexElementAttributes($element);
} else {
- $queue = Craft::$app->getQueue();
- $job = new UpdateSearchIndex([
+ Queue::push(new UpdateSearchIndex([
'elementType' => get_class($element),
'elementId' => $element->id,
'siteId' => $propagate ? '*' : $element->siteId,
'fieldHandles' => $element->getDirtyFields(),
- ]);
- try {
- $queue->priority(2048)->push($job);
- } catch (NotSupportedException $e) {
- // The queue probably doesn't support custom push priorities. Try again without one.
- $queue->push($job);
- }
+ ]), 2048);
}
}
@@ -2225,33 +2182,29 @@ private function _saveElementInternal(ElementInterface $element, bool $runValida
ArrayHelper::append($dirtyAttributes, ...$element->getDirtyAttributes());
foreach ($dirtyAttributes as $attributeName) {
- $db->createCommand()
- ->upsert(Table::CHANGEDATTRIBUTES, [
- 'elementId' => $element->id,
- 'siteId' => $element->siteId,
- 'attribute' => $attributeName,
- ], [
- 'dateUpdated' => $timestamp,
- 'propagated' => $element->propagating,
- 'userId' => $userId,
- ], [], false)
- ->execute();
+ Db::upsert(Table::CHANGEDATTRIBUTES, [
+ 'elementId' => $element->id,
+ 'siteId' => $element->siteId,
+ 'attribute' => $attributeName,
+ ], [
+ 'dateUpdated' => $timestamp,
+ 'propagated' => $element->propagating,
+ 'userId' => $userId,
+ ], [], false);
}
if (($fieldLayout = $element->getFieldLayout()) !== null) {
foreach ($element->getDirtyFields() as $fieldHandle) {
if (($field = $fieldLayout->getFieldByHandle($fieldHandle)) !== null) {
- $db->createCommand()
- ->upsert(Table::CHANGEDFIELDS, [
- 'elementId' => $element->id,
- 'siteId' => $element->siteId,
- 'fieldId' => $field->id,
- ], [
- 'dateUpdated' => $timestamp,
- 'propagated' => $element->propagating,
- 'userId' => $userId,
- ], [], false)
- ->execute();
+ Db::upsert(Table::CHANGEDFIELDS, [
+ 'elementId' => $element->id,
+ 'siteId' => $element->siteId,
+ 'fieldId' => $field->id,
+ ], [
+ 'dateUpdated' => $timestamp,
+ 'propagated' => $element->propagating,
+ 'userId' => $userId,
+ ], [], false);
}
}
}
@@ -2273,9 +2226,7 @@ private function _saveElementInternal(ElementInterface $element, bool $runValida
*/
private function _propagateElement(ElementInterface $element, array $siteInfo, $siteElement = null)
{
- /** @var Element $element */
// Try to fetch the element in this site
- /** @var Element|null $siteElement */
if ($siteElement === null && $element->id) {
$siteElement = $this->getElementById($element->id, get_class($element), $siteInfo['siteId']);
} else if (!$siteElement) {
@@ -2286,6 +2237,7 @@ private function _propagateElement(ElementInterface $element, array $siteInfo, $
if ($isNewSiteForElement = ($siteElement === null)) {
$siteElement = clone $element;
$siteElement->siteId = $siteInfo['siteId'];
+ $siteElement->elementSiteId = null;
$siteElement->contentId = null;
$siteElement->enabledForSite = $siteInfo['enabledByDefault'];
@@ -2316,7 +2268,6 @@ private function _propagateElement(ElementInterface $element, array $siteInfo, $
} else if (($fieldLayout = $element->getFieldLayout()) !== null) {
// Only copy the non-translatable field values
foreach ($fieldLayout->getFields() as $field) {
- /** @var Field $field */
// Has this field changed, and does it produce the same translation key as it did for the master element?
if (
$element->isFieldDirty($field->handle) &&
@@ -2358,21 +2309,22 @@ private function _cascadeDeleteDraftsAndRevisions(int $sourceId, bool $delete =
];
$db = Craft::$app->getDb();
+ $elementsTable = Table::ELEMENTS;
foreach (['draftId' => Table::DRAFTS, 'revisionId' => Table::REVISIONS] as $fk => $table) {
if ($db->getIsMysql()) {
$sql = <<{$matches[4]})) {
+ if (empty($attribute) || !isset($element->$attribute)) {
// Default to the URL
return (string)$element->getUrl();
}
try {
- $value = $element->{$matches[4]};
+ $value = $element->$attribute;
if (is_object($value) && !method_exists($value, '__toString')) {
throw new Exception('Object of class ' . get_class($value) . ' could not be converted to string');
@@ -2411,10 +2365,10 @@ private function _getRefTokenReplacement(ElementInterface $element = null, array
return $this->parseRefs((string)$value);
} catch (\Throwable $e) {
// Log it
- Craft::error('An exception was thrown when parsing the ref tag "' . $matches[0] . "\":\n" . $e->getMessage(), __METHOD__);
+ Craft::error("An exception was thrown when parsing the ref tag \"$fullMatch\":\n" . $e->getMessage(), __METHOD__);
- // Replace the token with the original ref tag
- return $matches[0];
+ // Replace the token with the default value
+ return $fallback;
}
}
}
diff --git a/src/services/Entries.php b/src/services/Entries.php
index 9996eced9e1..303bdfb98ba 100644
--- a/src/services/Entries.php
+++ b/src/services/Entries.php
@@ -9,6 +9,7 @@
use Craft;
use craft\db\Query;
+use craft\db\Table;
use craft\elements\Entry;
use yii\base\Component;
@@ -29,26 +30,28 @@ class Entries extends Component
* ```
*
* @param int $entryId The entry’s ID.
- * @param int|null $siteId The site to fetch the entry in. Defaults to the current site.
+ * @param int|int[]|string|null $siteId The site(s) to fetch the entry in.
+ * Defaults to the current site.
+ * @param array $criteria
* @return Entry|null The entry with the given ID, or `null` if an entry could not be found.
*/
- public function getEntryById(int $entryId, int $siteId = null)
+ public function getEntryById(int $entryId, $siteId = null, array $criteria = [])
{
if (!$entryId) {
return null;
}
// Get the structure ID
- $structureId = (new Query())
- ->select(['sections.structureId'])
- ->from(['{{%entries}} entries'])
- ->innerJoin('{{%sections}} sections', '[[sections.id]] = [[entries.sectionId]]')
- ->where(['entries.id' => $entryId])
- ->scalar();
+ if (!isset($criteria['structureId'])) {
+ $criteria['structureId'] = (new Query())
+ ->select(['sections.structureId'])
+ ->from(['entries' => Table::ENTRIES])
+ ->innerJoin(['sections' => Table::SECTIONS], '[[sections.id]] = [[entries.sectionId]]')
+ ->where(['entries.id' => $entryId])
+ ->scalar();
+ }
/** @noinspection PhpIncompatibleReturnTypeInspection */
- return Craft::$app->getElements()->getElementById($entryId, Entry::class, $siteId, [
- 'structureId' => $structureId,
- ]);
+ return Craft::$app->getElements()->getElementById($entryId, Entry::class, $siteId, $criteria);
}
}
diff --git a/src/services/Fields.php b/src/services/Fields.php
index 40278d3021b..9324e8bf103 100644
--- a/src/services/Fields.php
+++ b/src/services/Fields.php
@@ -179,7 +179,7 @@ class Fields extends Component
private $_groups;
/**
- * @var Field[]
+ * @var FieldInterface[]
*/
private $_fields;
@@ -411,7 +411,6 @@ public function deleteGroup(FieldGroup $group): bool
// Manually delete the fields (rather than relying on cascade deletes) so we have a chance to delete the
// content columns
- /** @var Field[] $fields */
$fields = $this->getFieldsByGroupId($group->id);
foreach ($fields as $field) {
@@ -471,7 +470,7 @@ public function getFieldTypesWithContent(): array
$fieldTypes = [];
foreach ($this->getAllFieldTypes() as $fieldType) {
- /** @var Field|string $fieldType */
+ /** @var FieldInterface|string $fieldType */
if ($fieldType::hasContentColumn()) {
$fieldTypes[] = $fieldType;
}
@@ -489,7 +488,6 @@ public function getFieldTypesWithContent(): array
*/
public function getCompatibleFieldTypes(FieldInterface $field, bool $includeCurrent = true): array
{
- /** @var Field $field */
if (!$field::hasContentColumn()) {
return $includeCurrent ? [get_class($field)] : [];
}
@@ -549,7 +547,6 @@ public function createField($config): FieldInterface
}
try {
- /** @var Field $field */
$field = ComponentHelper::createComponent($config, FieldInterface::class);
} catch (MissingComponentException $e) {
$config['errorMessage'] = $e->getMessage();
@@ -592,7 +589,6 @@ public function getAllFields($context = null): array
}
return ArrayHelper::where($this->_fields, function(FieldInterface $field) use ($context) {
- /** @var Field $field */
return in_array($field->context, $context, true);
});
}
@@ -684,8 +680,8 @@ public function getFieldsByGroupId(int $groupId): array
public function getFieldsByElementType(string $elementType): array
{
$results = $this->_createFieldQuery()
- ->innerJoin('{{%fieldlayoutfields}} flf', '[[flf.fieldId]] = [[fields.id]]')
- ->innerJoin('{{%fieldlayouts}} fl', '[[fl.id]] = [[flf.layoutId]]')
+ ->innerJoin(['flf' => Table::FIELDLAYOUTFIELDS], '[[flf.fieldId]] = [[fields.id]]')
+ ->innerJoin(['fl' => Table::FIELDLAYOUTS], '[[fl.id]] = [[flf.layoutId]]')
->where([
'fl.type' => $elementType,
'fl.dateDeleted' => null,
@@ -742,7 +738,6 @@ public function createFieldConfig(FieldInterface $field): array
*/
public function saveField(FieldInterface $field, bool $runValidation = true): bool
{
- /** @var Field $field */
$isNewField = $field->getIsNew();
// Fire a 'beforeSaveField' event
@@ -789,7 +784,6 @@ public function saveField(FieldInterface $field, bool $runValidation = true): bo
*/
public function prepFieldForSave(FieldInterface $field)
{
- /** @var Field $field */
// Clear the translation key format if not using a custom translation method
if ($field->translationMethod !== Field::TRANSLATION_METHOD_CUSTOM) {
$field->translationKeyFormat = null;
@@ -823,6 +817,10 @@ public function handleChangedField(ConfigEvent $event)
$data = $event->newValue;
$fieldUid = $event->tokenMatches[0];
+ if (!is_array($data)) {
+ return;
+ }
+
$this->applyFieldSave($fieldUid, $data, 'global');
}
@@ -852,7 +850,6 @@ public function deleteFieldById(int $fieldId): bool
*/
public function deleteField(FieldInterface $field): bool
{
- /** @var Field $field */
// Fire a 'beforeDeleteField' event
if ($this->hasEventHandlers(self::EVENT_BEFORE_DELETE_FIELD)) {
$this->trigger(self::EVENT_BEFORE_DELETE_FIELD, new FieldEvent([
@@ -908,7 +905,6 @@ public function applyFieldDelete($fieldUid)
return;
}
- /** @var Field $field */
$field = $this->getFieldById($fieldRecord->id);
// Fire a 'beforeApplyFieldDelete' event
@@ -934,9 +930,9 @@ public function applyFieldDelete($fieldUid)
}
// Delete the row in fields
- Craft::$app->getDb()->createCommand()
- ->delete(Table::FIELDS, ['id' => $fieldRecord->id])
- ->execute();
+ Db::delete(Table::FIELDS, [
+ 'id' => $fieldRecord->id,
+ ]);
$field->afterDelete();
@@ -1103,8 +1099,8 @@ public function getFieldsByLayoutId(int $layoutId): array
'flf.required',
'flf.sortOrder',
])
- ->innerJoin('{{%fieldlayoutfields}} flf', '[[flf.fieldId]] = [[fields.id]]')
- ->innerJoin('{{%fieldlayouttabs}} flt', '[[flt.id]] = [[flf.tabId]]')
+ ->innerJoin(['flf' => Table::FIELDLAYOUTFIELDS], '[[flf.fieldId]] = [[fields.id]]')
+ ->innerJoin(['flt' => Table::FIELDLAYOUTTABS], '[[flt.id]] = [[flf.tabId]]')
->where(['flf.layoutId' => $layoutId])
->orderBy(['flt.sortOrder' => SORT_ASC, 'flf.sortOrder' => SORT_ASC])
->all();
@@ -1240,14 +1236,14 @@ public function saveLayout(FieldLayout $layout, bool $runValidation = true): boo
if (!$isNewLayout) {
// Delete the old tabs/fields
- Craft::$app->getDb()->createCommand()
- ->delete(Table::FIELDLAYOUTTABS, ['layoutId' => $layout->id])
- ->execute();
+ Db::delete(Table::FIELDLAYOUTTABS, [
+ 'layoutId' => $layout->id,
+ ]);
// Because in MySQL, you can't even rely on cascading deletes to work. ¯\_(ツ)_/¯
- Craft::$app->getDb()->createCommand()
- ->delete(Table::FIELDLAYOUTFIELDS, ['layoutId' => $layout->id])
- ->execute();
+ Db::delete(Table::FIELDLAYOUTFIELDS, [
+ 'layoutId' => $layout->id,
+ ]);
// Get the current layout
$layoutRecord = FieldLayoutRecord::findWithTrashed()
@@ -1302,7 +1298,6 @@ public function saveLayout(FieldLayout $layout, bool $runValidation = true): boo
$tab->uid = $tabRecord->uid;
foreach ($tab->getFields() as $field) {
- /** @var Field $field */
$fieldRecord = new FieldLayoutFieldRecord();
$fieldRecord->layoutId = $layout->id;
$fieldRecord->tabId = $tab->id;
@@ -1447,7 +1442,6 @@ public function applyFieldSave(string $fieldUid, array $data, string $context)
$isNewField = $fieldRecord->getIsNewRecord();
$oldSettings = $fieldRecord->getOldAttribute('settings');
- /** @var Field $class */
$class = $data['type'];
// Create/alter the content table column
@@ -1547,7 +1541,6 @@ public function applyFieldSave(string $fieldUid, array $data, string $context)
CustomFieldBehavior::$fieldHandles[$fieldRecord->handle] = true;
// For CP save requests, make sure we have all the custom data already saved on the object.
- /** @var Field $field */
if (isset($this->_savingFields[$fieldUid])) {
$field = $this->_savingFields[$fieldUid];
@@ -1615,7 +1608,7 @@ private function _createFieldQuery(): Query
'fields.settings',
'fields.uid'
])
- ->from(['{{%fields}} fields'])
+ ->from(['fields' => Table::FIELDS])
->orderBy(['fields.name' => SORT_ASC, 'fields.handle' => SORT_ASC]);
// todo: remove schema version condition after next beakpoint
diff --git a/src/services/Gc.php b/src/services/Gc.php
index e16c2508782..ca00f36d907 100644
--- a/src/services/Gc.php
+++ b/src/services/Gc.php
@@ -111,12 +111,8 @@ public function hardDelete($tables)
$tables = [$tables];
}
- $db = Craft::$app->getDb();
-
foreach ($tables as $table) {
- $db->createCommand()
- ->delete($table, $condition)
- ->execute();
+ Db::delete($table, $condition);
}
}
@@ -135,8 +131,6 @@ private function _deleteStaleSessions()
$expire = DateTimeHelper::currentUTCDateTime();
$pastTime = $expire->sub($interval);
- Craft::$app->getDb()->createCommand()
- ->delete(Table::SESSIONS, ['<', 'dateUpdated', Db::prepareDateForDb($pastTime)])
- ->execute();
+ Db::delete(Table::SESSIONS, ['<', 'dateUpdated', Db::prepareDateForDb($pastTime)]);
}
}
diff --git a/src/services/Globals.php b/src/services/Globals.php
index 96cf19e3a76..b87bf9e5a2e 100644
--- a/src/services/Globals.php
+++ b/src/services/Globals.php
@@ -8,7 +8,6 @@
namespace craft\services;
use Craft;
-use craft\base\Field;
use craft\db\Query;
use craft\db\Table;
use craft\elements\GlobalSet;
@@ -527,7 +526,6 @@ public function handleDeletedGlobalSet(ConfigEvent $event)
*/
public function pruneDeletedField(FieldEvent $event)
{
- /** @var Field $field */
$field = $event->field;
$fieldUid = $field->uid;
@@ -553,7 +551,9 @@ public function pruneDeletedField(FieldEvent $event)
}
// Nuke all the layout fields from the DB
- Craft::$app->getDb()->createCommand()->delete('{{%fieldlayoutfields}}', ['fieldId' => $field->id])->execute();
+ Db::delete(Table::FIELDLAYOUTFIELDS, [
+ 'fieldId' => $field->id,
+ ]);
// Allow events again
$projectConfig->muteEvents = false;
diff --git a/src/services/Gql.php b/src/services/Gql.php
index 7bdb0eaf872..83c14fcfee3 100644
--- a/src/services/Gql.php
+++ b/src/services/Gql.php
@@ -17,8 +17,10 @@
use craft\events\DefineGqlValidationRulesEvent;
use craft\events\ExecuteGqlQueryEvent;
use craft\events\RegisterGqlDirectivesEvent;
+use craft\events\RegisterGqlMutationsEvent;
use craft\events\RegisterGqlPermissionsEvent;
use craft\events\RegisterGqlQueriesEvent;
+use craft\events\RegisterGqlSchemaComponentsEvent;
use craft\events\RegisterGqlTypesEvent;
use craft\gql\base\Directive;
use craft\gql\base\GeneratorInterface;
@@ -35,6 +37,12 @@
use craft\gql\interfaces\elements\MatrixBlock as MatrixBlockInterface;
use craft\gql\interfaces\elements\Tag as TagInterface;
use craft\gql\interfaces\elements\User as UserInterface;
+use craft\gql\mutations\Asset as AssetMutation;
+use craft\gql\mutations\Category as CategoryMutation;
+use craft\gql\mutations\Entry as EntryMutation;
+use craft\gql\mutations\GlobalSet as GlobalSetMutation;
+use craft\gql\mutations\Ping as PingMutation;
+use craft\gql\mutations\Tag as TagMutation;
use craft\gql\queries\Asset as AssetQuery;
use craft\gql\queries\Category as CategoryQuery;
use craft\gql\queries\Entry as EntryQuery;
@@ -45,6 +53,7 @@
use craft\gql\TypeLoader;
use craft\gql\TypeManager;
use craft\gql\types\DateTime;
+use craft\gql\types\Mutation;
use craft\gql\types\Number;
use craft\gql\types\Query;
use craft\gql\types\QueryArgument;
@@ -54,10 +63,10 @@
use craft\helpers\StringHelper;
use craft\models\GqlSchema;
use craft\models\GqlToken;
+use craft\models\Section;
use craft\records\GqlSchema as GqlSchemaRecord;
use craft\records\GqlToken as GqlTokenRecord;
use GraphQL\GraphQL;
-use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use GraphQL\Validator\DocumentValidator;
use GraphQL\Validator\Rules\FieldsOnCorrectType;
@@ -121,6 +130,31 @@ class Gql extends Component
*/
const EVENT_REGISTER_GQL_QUERIES = 'registerGqlQueries';
+ /**
+ * @event RegisterGqlMutationsEvent The event that is triggered when registering GraphQL mutations.
+ *
+ * Plugins get a chance to add their own GraphQL mutations.
+ * See [GraphQL](https://docs.craftcms.com/v3/graphql.html) for documentation on adding GraphQL support.
+ *
+ * ---
+ * ```php
+ * use craft\events\RegisterGqlMutationsEvent;
+ * use craft\services\GraphQl;
+ * use yii\base\Event;
+ * use GraphQL\Type\Definition\Type;
+ *
+ * Event::on(Gql::class, Gql::EVENT_REGISTER_GQL_MUTATIONS, function(RegisterGqlMutationsEvent $event) {
+ * // Add my GraphQL queries
+ * $event->queries['mutationPluginData'] =
+ * [
+ * 'type' => Type::listOf(MyType::getType())),
+ * 'args' => MyArguments::getArguments(),
+ * ];
+ * });
+ * ```
+ */
+ const EVENT_REGISTER_GQL_MUTATIONS = 'registerGqlMutations';
+
/**
* @event RegisterGqlDirectivesEvent The event that is triggered when registering GraphQL directives.
*
@@ -146,9 +180,16 @@ class Gql extends Component
/**
* @event RegisterGqlPermissionsEvent The event that is triggered when registering user permissions.
* @since 3.4.0
+ * @deprecated in 3.5.0. Use the [[EVENT_REGISTER_GQL_SCHEMA_COMPONENTS]] event instead.
*/
const EVENT_REGISTER_GQL_PERMISSIONS = 'registerGqlPermissions';
+ /**
+ * @event RegisterGqlSchemaComponentsEvent The event that is triggered when registering GraphQL schema components.
+ * @since 3.5.0
+ */
+ const EVENT_REGISTER_GQL_SCHEMA_COMPONENTS = 'registerGqlSchemaComponents';
+
/**
* @event DefineGqlValidationRulesEvent The event that is triggered when defining validation rules to be used.
*
@@ -268,10 +309,12 @@ public function getSchemaDef(GqlSchema $schema = null, $prebuildSchema = false):
// Either cached version was not found or we need a pre-built schema.
$registeredTypes = $this->_registerGqlTypes();
$this->_registerGqlQueries();
+ $this->_registerGqlMutations();
$schemaConfig = [
'typeLoader' => TypeLoader::class . '::loadType',
'query' => TypeLoader::loadType('Query'),
+ 'mutation' => TypeLoader::loadType('Mutation'),
'directives' => $this->_loadGqlDirectives(),
];
@@ -363,7 +406,7 @@ public function executeQuery(GqlSchema $schema, string $query, $variables = [],
$event->result = $cachedResult;
} else {
$schemaDef = $this->getSchemaDef($schema, $debugMode || StringHelper::contains($query, '__schema'));
- $event->result = GraphQL::executeQuery($schemaDef, $query, $event->rootValue, $event->context, $event->variables, $event->operationName, null, $this->getValidationRules($debugMode))->toArray(true);
+ $event->result = GraphQL::executeQuery($schemaDef, $query, $event->rootValue, $event->context, $event->variables, $event->operationName, null, $this->getValidationRules($debugMode))->toArray($debugMode);
if (empty($event->result['errors']) && $cacheKey) {
$this->setCachedResult($cacheKey, $event->result);
@@ -493,44 +536,90 @@ public function getPublicSchema()
* Returns all of the known GraphQL permissions, sorted by category.
*
* @return array
+ * @deprecated in 3.5.0. Use [[\craft\services\Gql::get()]] instead.
*/
public function getAllPermissions(): array
{
- $permissions = [];
+ return $this->getAllSchemaComponents()['queries'];
+ }
+
+ /**
+ * Returns all of the known GraphQL schema components.
+ *
+ * @return array
+ * @since 3.5.0
+ */
+ public function getAllSchemaComponents(): array
+ {
+ $queries = [];
+ $mutations = [];
// Entries
// ---------------------------------------------------------------------
- $permissions = array_merge($permissions, $this->_getSectionPermissions());
+ $components = $this->_getSectionSchemaComponents();
+ $label = Craft::t('app', 'Entries');
+ $queries[$label] = $components['query'] ?? [];
+ $mutations[$label] = $components['mutation'] ?? [];
// Assets
// ---------------------------------------------------------------------
- $permissions = array_merge($permissions, $this->_getVolumePermissions());
+ $components = $this->_getVolumeSchemaComponents();
+ $label = Craft::t('app', 'Assets');
+ $queries[$label] = $components['query'] ?? [];
+ $mutations[$label] = $components['mutation'] ?? [];
// Global Sets
// ---------------------------------------------------------------------
- $permissions = array_merge($permissions, $this->_getGlobalSetPermissions());
+ $components = $this->_getGlobalSetSchemaComponents();
+ $label = Craft::t('app', 'Global sets');
+ $queries[$label] = $components['query'] ?? [];
+ $mutations[$label] = $components['mutation'] ?? [];
// Users
// ---------------------------------------------------------------------
- $permissions = array_merge($permissions, $this->_getUserPermissions());
+ $components = $this->_getUserSchemaComponents();
+ $label = Craft::t('app', 'Users');
+ $queries[$label] = $components['query'] ?? [];
+ $mutations[$label] = $components['mutation'] ?? [];
// Categories
// ---------------------------------------------------------------------
- $permissions = array_merge($permissions, $this->_getCategoryPermissions());
+ $components = $this->_getCategorySchemaComponents();
+ $label = Craft::t('app', 'Categories');
+ $queries[$label] = $components['query'] ?? [];
+ $mutations[$label] = $components['mutation'] ?? [];
// Tags
// ---------------------------------------------------------------------
- $permissions = array_merge($permissions, $this->_getTagPermissions());
+ $components = $this->_getTagSchemaComponents();
+ $label = Craft::t('app', 'Tags');
+ $queries[$label] = $components['query'] ?? [];
+ $mutations[$label] = $components['mutation'] ?? [];
// Let plugins customize them and add new ones
// ---------------------------------------------------------------------
- $event = new RegisterGqlPermissionsEvent([
- 'permissions' => $permissions
+ if ($this->hasEventHandlers(self::EVENT_REGISTER_GQL_PERMISSIONS)) {
+ $deprecatedEvent = new RegisterGqlPermissionsEvent([
+ 'permissions' => $queries
+ ]);
+
+ $this->trigger(self::EVENT_REGISTER_GQL_PERMISSIONS, $deprecatedEvent);
+
+ $queries = $deprecatedEvent->permissions;
+ }
+
+ $event = new RegisterGqlSchemaComponentsEvent([
+ 'queries' => $queries,
+ 'mutations' => $mutations
]);
- $this->trigger(self::EVENT_REGISTER_GQL_PERMISSIONS, $event);
- return $event->permissions;
+ $this->trigger(self::EVENT_REGISTER_GQL_SCHEMA_COMPONENTS, $event);
+
+ return [
+ 'queries' => $event->queries,
+ 'mutations' => $event->mutations
+ ];
}
/**
@@ -641,7 +730,7 @@ public function saveToken(GqlToken $token, $runValidation = true): bool
$isNewToken = !$token->id;
if ($runValidation && !$token->validate()) {
- Craft::info('Schema not saved due to validation error.', __METHOD__);
+ Craft::info('Token not saved due to validation error.', __METHOD__);
return false;
}
@@ -700,7 +789,7 @@ public function saveSchema(GqlSchema $schema, $runValidation = true): bool
$isNewScope = !$schema->id;
if ($runValidation && !$schema->validate()) {
- Craft::info('Scope not saved due to validation error.', __METHOD__);
+ Craft::info('Schema not saved due to validation error.', __METHOD__);
return false;
}
@@ -827,9 +916,9 @@ public function handleDeletedSchema(ConfigEvent $event)
try {
// Delete the scope
- $db->createCommand()
- ->delete(Table::GQLSCHEMAS, ['id' => $schemaRecord->id])
- ->execute();
+ Db::delete(Table::GQLSCHEMAS, [
+ 'id' => $schemaRecord->id,
+ ]);
$transaction->commit();
} catch (\Throwable $e) {
@@ -911,10 +1000,7 @@ public function getContentArguments(array $contexts, $elementClass): array
foreach ($context->getFields() as $contentField) {
if (!$contentField instanceof GqlInlineFragmentFieldInterface) {
- $contentArguments[$contentField->handle] = [
- 'name' => $contentField->handle,
- 'type' => Type::listOf(QueryArgument::getType()),
- ];
+ $contentArguments[$contentField->handle] = $contentField->getContentGqlQueryArgumentType();
}
}
}
@@ -1029,6 +1115,35 @@ private function _registerGqlQueries()
});
}
+ /**
+ * Get GraphQL mutation definitions
+ *
+ * @return void
+ */
+ private function _registerGqlMutations()
+ {
+ $mutationList = [
+ // Mutations
+ PingMutation::getMutations(),
+ EntryMutation::getMutations(),
+ TagMutation::getMutations(),
+ CategoryMutation::getMutations(),
+ GlobalSetMutation::getMutations(),
+ AssetMutation::getMutations(),
+ ];
+
+
+ $event = new RegisterGqlMutationsEvent([
+ 'mutations' => array_merge(...$mutationList)
+ ]);
+
+ $this->trigger(self::EVENT_REGISTER_GQL_MUTATIONS, $event);
+
+ TypeLoader::registerType('Mutation', function() use ($event) {
+ return call_user_func(Mutation::class . '::getType', $event->mutations);
+ });
+ }
+
/**
* Get GraphQL query definitions
*
@@ -1064,35 +1179,53 @@ private function _loadGqlDirectives(): array
*
* @return array
*/
- private function _getSectionPermissions(): array
+ private function _getSectionSchemaComponents(): array
{
- $permissions = [];
-
$sortedEntryTypes = [];
foreach (Craft::$app->getSections()->getAllEntryTypes() as $entryType) {
$sortedEntryTypes[$entryType->sectionId][] = $entryType;
}
- if (!empty($sortedEntryTypes)) {
- $label = Craft::t('app', 'Entries');
+ $queryComponents = [];
+ $mutationComponents = [];
- $sectionPermissions = [];
+ if (!empty($sortedEntryTypes)) {
foreach (Craft::$app->getSections()->getAllSections() as $section) {
- $nested = ['label' => Craft::t('app', 'View section - {section}', ['section' => Craft::t('site', $section->name)])];
+ $query = ['label' => Craft::t('app', 'Section - {section}', ['section' => Craft::t('site', $section->name)])];
+ $mutate = ['label' => Craft::t('app', 'Section - {section}', ['section' => Craft::t('site', $section->name)])];
foreach ($sortedEntryTypes[$section->id] as $entryType) {
- $nested['nested']['entrytypes.' . $entryType->uid . ':read'] = ['label' => Craft::t('app', 'View entry type - {entryType}', ['entryType' => Craft::t('site', $entryType->name)])];
+ $suffix = 'entrytypes.' . $entryType->uid;
+
+ if ($section->type == Section::TYPE_SINGLE) {
+ $mutate['nested'][$suffix . ':save'] = ['label' => Craft::t('app', 'Edit “{entryType}”', ['entryType' => Craft::t('site', $entryType->name)])];
+ } else {
+ $mutate['nested'][$suffix . ':edit'] = [
+ 'label' => Craft::t('app', 'Edit entries with the “{entryType}” entry type', ['entryType' => Craft::t('site', $entryType->name)]),
+ 'nested' => [
+ $suffix . ':create' => ['label' => Craft::t('app', 'Create entries with the “{entryType}” entry type', ['entryType' => Craft::t('site', $entryType->name)])],
+ $suffix . ':save' => ['label' => Craft::t('app', 'Save entries with the “{entryType}” entry type', ['entryType' => Craft::t('site', $entryType->name)])],
+ $suffix . ':delete' => ['label' => Craft::t('app', 'Delete entries with the “{entryType}” entry type', ['entryType' => Craft::t('site', $entryType->name)])],
+ ],
+ ];
+ }
+
+ $query['nested'][$suffix . ':read'] = [
+ 'label' => Craft::t('app', 'View entries with the “{entryType}” entry type', ['entryType' => Craft::t('site', $entryType->name)]),
+ ];
}
- $sectionPermissions['sections.' . $section->uid . ':read'] = $nested;
+ $queryComponents['sections.' . $section->uid . ':read'] = $query;
+ $mutationComponents['sections.' . $section->uid . ':edit'] = $mutate;
}
-
- $permissions[$label] = $sectionPermissions;
}
- return $permissions;
+ return [
+ 'query' => $queryComponents,
+ 'mutation' => $mutationComponents,
+ ];
}
/**
@@ -1100,24 +1233,32 @@ private function _getSectionPermissions(): array
*
* @return array
*/
- private function _getVolumePermissions(): array
+ private function _getVolumeSchemaComponents(): array
{
- $permissions = [];
+ $queryComponents = [];
+ $mutationComponents = [];
$volumes = Craft::$app->getVolumes()->getAllVolumes();
if (!empty($volumes)) {
- $label = Craft::t('app', 'Assets');
- $volumePermissions = [];
-
foreach ($volumes as $volume) {
- $volumePermissions['volumes.' . $volume->uid . ':read'] = ['label' => Craft::t('app', 'View volume - {volume}', ['volume' => Craft::t('site', $volume->name)])];
+ $suffix = 'volumes.' . $volume->uid;
+ $queryComponents[$suffix . ':read'] = ['label' => Craft::t('app', 'View volume - {volume}', ['volume' => Craft::t('site', $volume->name)])];
+ $mutationComponents[$suffix . ':edit'] = [
+ 'label' => Craft::t('app', 'Edit assets in the “{volume}” volume', ['volume' => Craft::t('site', $volume->name)]),
+ 'nested' => [
+ $suffix . ':create' => ['label' => Craft::t('app', 'Create assets in the “{volume}” volume', ['volume' => Craft::t('site', $volume->name)])],
+ $suffix . ':save' => ['label' => Craft::t('app', 'Modify assets in the “{volume}” volume', ['volume' => Craft::t('site', $volume->name)])],
+ $suffix . ':delete' => ['label' => Craft::t('app', 'Delete assets from the “{volume}” volume', ['volume' => Craft::t('site', $volume->name)])],
+ ]
+ ];
}
-
- $permissions[$label] = $volumePermissions;
}
- return $permissions;
+ return [
+ 'query' => $queryComponents,
+ 'mutation' => $mutationComponents,
+ ];
}
/**
@@ -1125,25 +1266,25 @@ private function _getVolumePermissions(): array
*
* @return array
*/
- private function _getGlobalSetPermissions(): array
+ private function _getGlobalSetSchemaComponents(): array
{
- $permissions = [];
+ $queryComponents = [];
+ $mutationComponents = [];
$globalSets = Craft::$app->getGlobals()->getAllSets();
if (!empty($globalSets)) {
- $label = Craft::t('app', 'Globals');
- $globalSetPermissions = [];
-
foreach ($globalSets as $globalSet) {
$suffix = 'globalsets.' . $globalSet->uid;
- $globalSetPermissions[$suffix . ':read'] = ['label' => Craft::t('app', 'View global set - {globalSet}', ['globalSet' => Craft::t('site', $globalSet->name)])];
+ $queryComponents[$suffix . ':read'] = ['label' => Craft::t('app', 'View global set - {globalSet}', ['globalSet' => Craft::t('site', $globalSet->name)])];
+ $mutationComponents[$suffix . ':edit'] = ['label' => Craft::t('app', 'Edit the “{globalSet}” global set.', ['globalSet' => Craft::t('site', $globalSet->name)])];
}
-
- $permissions[$label] = $globalSetPermissions;
}
- return $permissions;
+ return [
+ 'query' => $queryComponents,
+ 'mutation' => $mutationComponents,
+ ];
}
/**
@@ -1151,25 +1292,31 @@ private function _getGlobalSetPermissions(): array
*
* @return array
*/
- private function _getCategoryPermissions(): array
+ private function _getCategorySchemaComponents(): array
{
- $permissions = [];
+ $queryComponents = [];
+ $mutationComponents = [];
$categoryGroups = Craft::$app->getCategories()->getAllGroups();
if (!empty($categoryGroups)) {
- $label = Craft::t('app', 'Categories');
- $categoryPermissions = [];
-
foreach ($categoryGroups as $categoryGroup) {
$suffix = 'categorygroups.' . $categoryGroup->uid;
- $categoryPermissions[$suffix . ':read'] = ['label' => Craft::t('app', 'View category group - {categoryGroup}', ['categoryGroup' => Craft::t('site', $categoryGroup->name)])];
+ $queryComponents[$suffix . ':read'] = ['label' => Craft::t('app', 'View category group - {categoryGroup}', ['categoryGroup' => Craft::t('site', $categoryGroup->name)])];
+ $mutationComponents[$suffix . ':edit'] = [
+ 'label' => Craft::t('app', 'Edit categories in the “{categoryGroup}” category group', ['categoryGroup' => Craft::t('site', $categoryGroup->name)]),
+ 'nested' => [
+ $suffix . ':save' => ['label' => Craft::t('app', 'Save categories in the “{categoryGroup}” category group', ['categoryGroup' => Craft::t('site', $categoryGroup->name)])],
+ $suffix . ':delete' => ['label' => Craft::t('app', 'Delete categories from the “{categoryGroup}” category group', ['categoryGroup' => Craft::t('site', $categoryGroup->name)])],
+ ]
+ ];
}
-
- $permissions[$label] = $categoryPermissions;
}
- return $permissions;
+ return [
+ 'query' => $queryComponents,
+ 'mutation' => $mutationComponents,
+ ];
}
/**
@@ -1177,25 +1324,31 @@ private function _getCategoryPermissions(): array
*
* @return array
*/
- private function _getTagPermissions(): array
+ private function _getTagSchemaComponents(): array
{
- $permissions = [];
+ $queryComponents = [];
+ $mutationComponents = [];
$tagGroups = Craft::$app->getTags()->getAllTagGroups();
if (!empty($tagGroups)) {
- $label = Craft::t('app', 'Tags');
- $tagPermissions = [];
-
foreach ($tagGroups as $tagGroup) {
$suffix = 'taggroups.' . $tagGroup->uid;
- $tagPermissions[$suffix . ':read'] = ['label' => Craft::t('app', 'View tag group - {tagGroup}', ['tagGroup' => Craft::t('site', $tagGroup->name)])];
+ $queryComponents[$suffix . ':read'] = ['label' => Craft::t('app', 'View tag group - {tagGroup}', ['tagGroup' => Craft::t('site', $tagGroup->name)])];
+ $mutationComponents[$suffix . ':edit'] = [
+ 'label' => Craft::t('app', 'Edit tags in the “{tagGroup}” tag group', ['tagGroup' => Craft::t('site', $tagGroup->name)]),
+ 'nested' => [
+ $suffix . ':save' => ['label' => Craft::t('app', 'Save tags in the “{tagGroup}” tag group', ['tagGroup' => Craft::t('site', $tagGroup->name)])],
+ $suffix . ':delete' => ['label' => Craft::t('app', 'Delete tags from the “{tagGroup}” tag group', ['tagGroup' => Craft::t('site', $tagGroup->name)])],
+ ]
+ ];
}
-
- $permissions[$label] = $tagPermissions;
}
- return $permissions;
+ return [
+ 'query' => $queryComponents,
+ 'mutation' => $mutationComponents,
+ ];
}
/**
@@ -1203,24 +1356,21 @@ private function _getTagPermissions(): array
*
* @return array
*/
- private function _getUserPermissions(): array
+ private function _getUserSchemaComponents(): array
{
- $permissions = [];
-
+ $queryComponents = [];
$userGroups = Craft::$app->getUserGroups()->getAllGroups();
- $label = Craft::t('app', 'Users');
-
- $userPermissions = ['usergroups.everyone:read' => ['label' => Craft::t('app', 'View all users')]];
+ $queryComponents['usergroups.everyone:read'] = ['label' => Craft::t('app', 'View all users')];
foreach ($userGroups as $userGroup) {
$suffix = 'usergroups.' . $userGroup->uid;
- $userPermissions[$suffix . ':read'] = ['label' => Craft::t('app', 'View user group - {userGroup}', ['userGroup' => Craft::t('site', $userGroup->name)])];
+ $queryComponents[$suffix . ':read'] = ['label' => Craft::t('app', 'View user group - {userGroup}', ['userGroup' => Craft::t('site', $userGroup->name)])];
}
- $permissions[$label] = $userPermissions;
-
- return $permissions;
+ return [
+ 'query' => $queryComponents,
+ ];
}
/**
diff --git a/src/services/Matrix.php b/src/services/Matrix.php
index 36e0ad416e2..e91a703c7d6 100644
--- a/src/services/Matrix.php
+++ b/src/services/Matrix.php
@@ -8,9 +8,7 @@
namespace craft\services;
use Craft;
-use craft\base\Element;
use craft\base\ElementInterface;
-use craft\base\Field;
use craft\db\Query;
use craft\db\Table;
use craft\elements\db\MatrixBlockQuery;
@@ -116,7 +114,7 @@ public function getBlockTypesByFieldId(int $fieldId): array
public function getAllBlockTypes(): array
{
$results = $this->_createBlockTypeQuery()
- ->innerJoin(Table::FIELDS . ' f', '[[f.id]] = [[bt.fieldId]]')
+ ->innerJoin(['f' => Table::FIELDS], '[[f.id]] = [[bt.fieldId]]')
->where(['f.type' => MatrixField::class])
->all();
@@ -186,7 +184,6 @@ public function validateBlockType(MatrixBlockType $blockType, bool $validateUniq
$contentService->fieldColumnPrefix = 'field_' . $blockType->handle . '_';
foreach ($blockType->getFields() as $field) {
- /** @var Field $field */
// Hack to allow blank field names
if (!$field->name) {
$field->name = '__blank__';
@@ -245,7 +242,6 @@ public function saveBlockType(MatrixBlockType $blockType, bool $runValidation =
$fieldsService = Craft::$app->getFields();
- /** @var Field $parentField */
$parentField = $fieldsService->getFieldById($blockType->fieldId);
$isNewBlockType = $blockType->getIsNew();
$projectConfig = Craft::$app->getProjectConfig();
@@ -265,7 +261,6 @@ public function saveBlockType(MatrixBlockType $blockType, bool $runValidation =
$configData['fields'] = [];
foreach ($blockType->getFields() as $field) {
- /** @var Field $field */
$configData['fields'][$field->uid] = $fieldsService->createFieldConfig($field);
$field->sortOrder = ++$sortOrder;
@@ -447,8 +442,7 @@ public function handleDeletedBlockType(ConfigEvent $event)
return;
}
- $db = Craft::$app->getDb();
- $transaction = $db->beginTransaction();
+ $transaction = Craft::$app->getDb()->beginTransaction();
try {
$blockType = $this->getBlockTypeById($blockTypeRecord->id);
@@ -505,9 +499,9 @@ public function handleDeletedBlockType(ConfigEvent $event)
Craft::$app->getFields()->deleteLayoutById($fieldLayoutId);
// Finally delete the actual block type
- $db->createCommand()
- ->delete(Table::MATRIXBLOCKTYPES, ['id' => $blockTypeRecord->id])
- ->execute();
+ Db::delete(Table::MATRIXBLOCKTYPES, [
+ 'id' => $blockTypeRecord->id,
+ ]);
}
$transaction->commit();
@@ -747,7 +741,6 @@ public function getBlockById(int $blockId, int $siteId = null)
*/
public function saveField(MatrixField $field, ElementInterface $owner)
{
- /** @var Element $owner */
$elementsService = Craft::$app->getElements();
/** @var MatrixBlockQuery $query */
$query = $owner->getFieldValue($field->handle);
@@ -762,7 +755,6 @@ public function saveField(MatrixField $field, ElementInterface $owner)
$blockIds = [];
$collapsedBlockIds = [];
$sortOrder = 0;
- $db = Craft::$app->getDb();
$transaction = Craft::$app->getDb()->beginTransaction();
try {
@@ -775,10 +767,11 @@ public function saveField(MatrixField $field, ElementInterface $owner)
} else if ((int)$block->sortOrder !== $sortOrder) {
// Just update its sortOrder
$block->sortOrder = $sortOrder;
- $db->createCommand()->update(Table::MATRIXBLOCKS,
- ['sortOrder' => $sortOrder],
- ['id' => $block->id], [], false)
- ->execute();
+ Db::update(Table::MATRIXBLOCKS, [
+ 'sortOrder' => $sortOrder,
+ ], [
+ 'id' => $block->id,
+ ], [], false);
}
$blockIds[] = $block->id;
@@ -809,7 +802,6 @@ public function saveField(MatrixField $field, ElementInterface $owner)
if (!empty($otherSiteIds)) {
// Get the original element and duplicated element for each of those sites
- /** @var Element[] $otherTargets */
$otherTargets = $owner::find()
->drafts($owner->getIsDraft())
->revisions($owner->getIsRevision())
@@ -871,8 +863,6 @@ public function saveField(MatrixField $field, ElementInterface $owner)
*/
public function duplicateBlocks(MatrixField $field, ElementInterface $source, ElementInterface $target, bool $checkOtherSites = false)
{
- /** @var Element $source */
- /** @var Element $target */
$elementsService = Craft::$app->getElements();
/** @var MatrixBlockQuery $query */
$query = $source->getFieldValue($field->handle);
@@ -914,7 +904,6 @@ public function duplicateBlocks(MatrixField $field, ElementInterface $source, El
if (!empty($otherSiteIds)) {
// Get the original element and duplicated element for each of those sites
- /** @var Element[] $otherSources */
$otherSources = $target::find()
->drafts($source->getIsDraft())
->revisions($source->getIsRevision())
@@ -922,7 +911,6 @@ public function duplicateBlocks(MatrixField $field, ElementInterface $source, El
->siteId($otherSiteIds)
->anyStatus()
->all();
- /** @var Element[] $otherTargets */
$otherTargets = $target::find()
->drafts($target->getIsDraft())
->revisions($target->getIsRevision())
@@ -980,7 +968,6 @@ public function getSupportedSiteIdsForField(MatrixField $field, ElementInterface
*/
public function getSupportedSiteIds(string $propagationMethod, ElementInterface $owner): array
{
- /** @var Element $owner */
/** @var Site[] $allSites */
$allSites = ArrayHelper::index(Craft::$app->getSites()->getAllSites(), 'id');
$ownerSiteIds = ArrayHelper::getColumn(ElementHelper::supportedSitesForElement($owner), 'siteId');
@@ -1092,7 +1079,6 @@ private function _createContentTable(string $tableName)
*/
private function _deleteOtherBlocks(MatrixField $field, ElementInterface $owner, array $except)
{
- /** @var Element $owner */
$deleteBlocks = MatrixBlock::find()
->anyStatus()
->ownerId($owner->id)
diff --git a/src/services/Path.php b/src/services/Path.php
index c8af202f32d..1123c9f2b08 100644
--- a/src/services/Path.php
+++ b/src/services/Path.php
@@ -70,7 +70,7 @@ public function getConfigPath(): string
*/
public function getProjectConfigFilePath(): string
{
- return $this->getConfigPath() . DIRECTORY_SEPARATOR . ProjectConfig::CONFIG_FILENAME;
+ return $this->getConfigPath() . DIRECTORY_SEPARATOR . Craft::$app->getProjectConfig()->filename;
}
/**
diff --git a/src/services/PluginStore.php b/src/services/PluginStore.php
index 052aa906878..b75cb3d67a6 100644
--- a/src/services/PluginStore.php
+++ b/src/services/PluginStore.php
@@ -10,6 +10,7 @@
use Craft;
use craft\db\Table;
use craft\helpers\DateTimeHelper;
+use craft\helpers\Db;
use craft\models\CraftIdToken;
use craft\records\CraftIdToken as OauthTokenRecord;
use DateInterval;
@@ -184,9 +185,9 @@ public function deleteTokenByUserId(int $userId): bool
return false;
}
- Craft::$app->getDb()->createCommand()
- ->delete(Table::CRAFTIDTOKENS, ['userId' => $userId])
- ->execute();
+ Db::delete(Table::CRAFTIDTOKENS, [
+ 'userId' => $userId,
+ ]);
return true;
}
diff --git a/src/services/Plugins.php b/src/services/Plugins.php
index a47805906b3..3342fccae7b 100644
--- a/src/services/Plugins.php
+++ b/src/services/Plugins.php
@@ -8,7 +8,6 @@
namespace craft\services;
use Craft;
-use craft\base\Plugin;
use craft\base\PluginInterface;
use craft\db\MigrationManager;
use craft\db\Query;
@@ -233,7 +232,6 @@ public function loadPlugins()
if ($plugin !== null) {
// If we're not updating, check if the plugin's version number changed, but not its schema version.
if (!Craft::$app->getIsInMaintenanceMode() && $this->hasPluginVersionNumberChanged($plugin) && !$this->doesPluginRequireDatabaseUpdate($plugin)) {
- /** @var Plugin $plugin */
if (
$plugin->minVersionRequired &&
strpos($row['version'], 'dev-') !== 0 &&
@@ -248,12 +246,11 @@ public function loadPlugins()
}
// Update our record of the plugin's version number
- Craft::$app->getDb()->createCommand()
- ->update(
- Table::PLUGINS,
- ['version' => $plugin->getVersion()],
- ['id' => $row['id']])
- ->execute();
+ Db::update(Table::PLUGINS, [
+ 'version' => $plugin->getVersion(),
+ ], [
+ 'id' => $row['id'],
+ ]);
}
$this->_registerPlugin($plugin);
@@ -311,7 +308,6 @@ public function getPluginByPackageName(string $packageName)
$this->loadPlugins();
foreach ($this->_plugins as $plugin) {
- /** @var Plugin $plugin */
if ($plugin->packageName === $packageName) {
return $plugin;
}
@@ -474,7 +470,6 @@ public function installPlugin(string $handle, string $edition = null): bool
$projectConfig = Craft::$app->getProjectConfig();
$configKey = self::CONFIG_PLUGINS_KEY . '.' . $handle;
- /** @var Plugin $plugin */
$plugin = $this->createPlugin($handle);
// Set the edition
@@ -505,9 +500,7 @@ public function installPlugin(string $handle, string $edition = null): bool
'installDate' => Db::prepareDateForDb(new \DateTime()),
];
- $db->createCommand()
- ->insert(Table::PLUGINS, $info)
- ->execute();
+ Db::insert(Table::PLUGINS, $info);
$info['installDate'] = DateTimeHelper::toDateTime($info['installDate']);
$info['id'] = $db->getLastInsertID(Table::PLUGINS);
@@ -519,9 +512,9 @@ public function installPlugin(string $handle, string $edition = null): bool
if ($db->getIsMysql()) {
// Explicitly remove the plugins row just in case the transaction was implicitly committed
- $db->createCommand()
- ->delete(Table::PLUGINS, ['handle' => $handle])
- ->execute();
+ Db::delete(Table::PLUGINS, [
+ 'handle' => $handle,
+ ]);
}
return false;
@@ -603,9 +596,9 @@ public function uninstallPlugin(string $handle): bool
// Clean up the plugins and migrations tables
$id = $this->getStoredPluginInfo($handle)['id'];
- Craft::$app->getDb()->createCommand()
- ->delete(Table::PLUGINS, ['id' => $id])
- ->execute();
+ Db::delete(Table::PLUGINS, [
+ 'id' => $id,
+ ]);
$transaction->commit();
} catch (\Throwable $e) {
@@ -665,7 +658,6 @@ public function switchEdition(string $handle, string $edition)
// If it's installed, update the instance and our locally stored info
$plugin = $this->getPlugin($handle);
if ($plugin !== null) {
- /** @var Plugin $plugin */
$plugin->edition = $edition;
}
}
@@ -679,7 +671,6 @@ public function switchEdition(string $handle, string $edition)
*/
public function savePluginSettings(PluginInterface $plugin, array $settings): bool
{
- /** @var Plugin $plugin */
// Save the settings on the plugin
$plugin->getSettings()->setAttributes($settings, false);
@@ -724,7 +715,6 @@ public function savePluginSettings(PluginInterface $plugin, array $settings): bo
*/
public function hasPluginVersionNumberChanged(PluginInterface $plugin): bool
{
- /** @var Plugin $plugin */
$this->loadPlugins();
if (($info = $this->getStoredPluginInfo($plugin->id)) === null) {
@@ -742,7 +732,6 @@ public function hasPluginVersionNumberChanged(PluginInterface $plugin): bool
*/
public function doesPluginRequireDatabaseUpdate(PluginInterface $plugin): bool
{
- /** @var Plugin $plugin */
$this->loadPlugins();
if (($info = $this->getStoredPluginInfo($plugin->id)) === null) {
@@ -901,7 +890,6 @@ public function createPlugin(string $handle, array $info = null)
}
// Create the plugin
- /** @var Plugin $plugin */
$plugin = Craft::createObject($config, [$handle, Craft::$app]);
$this->_setPluginMigrator($plugin, $info['id'] ?? null);
return $plugin;
@@ -947,7 +935,6 @@ public function getPluginInfo(string $handle): array
$pluginInfo = $this->_enabledPluginInfo[$handle] ?? $this->_disabledPluginInfo[$handle] ?? null;
// Get the plugin if it's enabled
- /** @var Plugin|null $plugin */
$plugin = $this->getPlugin($handle);
$info = array_merge([
@@ -1089,7 +1076,6 @@ public function getPluginIconSvg(string $handle): string
{
// If it's installed, let the plugin say where it lives
if (($plugin = $this->getPlugin($handle)) !== null) {
- /** @var Plugin $plugin */
$basePath = $plugin->getBasePath();
} else {
if (($basePath = $this->_composerPluginInfo[$handle]['basePath'] ?? false) !== false) {
@@ -1135,7 +1121,6 @@ public function setPluginLicenseKey(string $handle, string $licenseKey = null):
throw new InvalidPluginException($handle);
}
- /** @var Plugin $plugin */
// Validate the license key
$normalizedLicenseKey = $this->normalizePluginLicenseKey($licenseKey);
@@ -1211,13 +1196,12 @@ public function setPluginLicenseKeyStatus(string $handle, string $licenseKeyStat
throw new InvalidPluginException($handle);
}
- /** @var Plugin $plugin */
- Craft::$app->getDb()->createCommand()
- ->update(Table::PLUGINS, [
- 'licenseKeyStatus' => $licenseKeyStatus,
- 'licensedEdition' => $licensedEdition,
- ], ['handle' => $handle])
- ->execute();
+ Db::update(Table::PLUGINS, [
+ 'licenseKeyStatus' => $licenseKeyStatus,
+ 'licensedEdition' => $licensedEdition,
+ ], [
+ 'handle' => $handle,
+ ]);
// Update our cache of it
if (isset($this->_enabledPluginInfo[$handle])) {
@@ -1277,7 +1261,6 @@ private function _normalizeHandle(string $handle): string
*/
private function _registerPlugin(PluginInterface $plugin)
{
- /** @var Plugin $plugin */
$this->_plugins[$plugin->id] = $plugin;
Craft::$app->setModule($plugin->id, $plugin);
}
@@ -1289,7 +1272,6 @@ private function _registerPlugin(PluginInterface $plugin)
*/
private function _unregisterPlugin(PluginInterface $plugin)
{
- /** @var Plugin $plugin */
unset($this->_plugins[$plugin->id]);
Craft::$app->setModule($plugin->id, null);
}
@@ -1304,7 +1286,6 @@ private function _setPluginMigrator(PluginInterface $plugin, int $id = null)
{
$ref = new \ReflectionClass($plugin);
$ns = $ref->getNamespaceName();
- /** @var Plugin $plugin */
$plugin->set('migrator', [
'class' => MigrationManager::class,
'type' => MigrationManager::TYPE_PLUGIN,
diff --git a/src/services/ProjectConfig.php b/src/services/ProjectConfig.php
index 306d6a9df50..64bbf915132 100644
--- a/src/services/ProjectConfig.php
+++ b/src/services/ProjectConfig.php
@@ -8,7 +8,6 @@
namespace craft\services;
use Craft;
-use craft\base\Plugin;
use craft\db\Query;
use craft\db\Table;
use craft\elements\User;
@@ -17,6 +16,7 @@
use craft\events\RebuildConfigEvent;
use craft\helpers\ArrayHelper;
use craft\helpers\DateTimeHelper;
+use craft\helpers\Db;
use craft\helpers\FileHelper;
use craft\helpers\Json;
use craft\helpers\Path as PathHelper;
@@ -51,7 +51,11 @@ class ProjectConfig extends Component
// Array key to use if not using config files.
const CONFIG_KEY = 'storedConfig';
- // Filename for base config file
+ /**
+ * @var string Filename for base config file
+ * @since 3.1.0
+ * @deprecated in 3.5.0. Use [[filename]] instead.
+ */
const CONFIG_FILENAME = 'project.yaml';
/**
@@ -162,6 +166,12 @@ class ProjectConfig extends Component
*/
const EVENT_REBUILD = 'rebuild';
+ /**
+ * @var string The filename to save the project config as.
+ * @since 3.5.0
+ */
+ public $filename = 'project.yaml';
+
/**
* @var int The maximum number of project.yaml deltas to store in storage/config-backups/
* @since 3.4.0
@@ -757,9 +767,9 @@ public function saveModifiedConfigData()
$currentSet = $changeSet;
if (!empty($changeSet['removed'])) {
- $db->createCommand()
- ->delete(Table::PROJECTCONFIG, ['path' => array_keys($changeSet['removed'])])
- ->execute();
+ Db::delete(Table::PROJECTCONFIG, [
+ 'path' => array_keys($changeSet['removed']),
+ ]);
}
if (!empty($changeSet['added'])) {
@@ -810,15 +820,13 @@ public function saveModifiedConfigData()
// Store in the DB
if (!empty($batch)) {
- $db->createCommand()
- ->delete(Table::PROJECTCONFIG, ['path' => $pathsToInsert])
- ->execute();
- $db->createCommand()
- ->delete(Table::PROJECTCONFIG, ['path' => array_keys($additionalCleanupPaths)])
- ->execute();
- $db->createCommand()
- ->batchInsert(Table::PROJECTCONFIG, ['path', 'value'], $batch, false)
- ->execute();
+ Db::delete(Table::PROJECTCONFIG, [
+ 'path' => $pathsToInsert,
+ ]);
+ Db::delete(Table::PROJECTCONFIG, [
+ 'path' => array_keys($additionalCleanupPaths),
+ ]);
+ Db::batchInsert(Table::PROJECTCONFIG, ['path', 'value'], $batch, false);
}
}
@@ -910,7 +918,6 @@ public function getAreConfigSchemaVersionsCompatible(&$issues = [])
$plugins = Craft::$app->getPlugins()->getAllPlugins();
foreach ($plugins as $plugin) {
- /** @var Plugin $plugin */
$incomingSchema = (string)$this->get(Plugins::CONFIG_PLUGINS_KEY . '.' . $plugin->handle . '.schemaVersion', true);
$existingSchema = (string)$plugin->schemaVersion;
@@ -1745,7 +1752,6 @@ private function _loadInternalConfigData()
// See if we can get away with using the cached data
$dependency = new DbQueryDependency([
- 'db' => Craft::$app->getDb(),
'query' => $this->_createProjectConfigQuery()
->select(['value'])
->where(['path' => 'dateModified']),
@@ -1851,8 +1857,8 @@ private function _getSiteData(): array
'sites.primary',
'siteGroups.uid AS siteGroup',
])
- ->from(['{{%sites}} sites'])
- ->innerJoin('{{%sitegroups}} siteGroups', '[[sites.groupId]] = [[siteGroups.id]]')
+ ->from(['sites' => Table::SITES])
+ ->innerJoin(['siteGroups' => Table::SITEGROUPS], '[[siteGroups.id]] = [[sites.groupId]]')
->where(['sites.dateDeleted' => null])
->andWhere(['siteGroups.dateDeleted' => null])
->all();
@@ -1891,8 +1897,8 @@ private function _getSectionData(): array
'structures.uid AS structure',
'structures.maxLevels AS structureMaxLevels',
])
- ->from(['{{%sections}} sections'])
- ->leftJoin('{{%structures}} structures', '[[structures.id]] = [[sections.structureId]]')
+ ->from(['sections' => Table::SECTIONS])
+ ->leftJoin(['structures' => Table::STRUCTURES], '[[structures.id]] = [[sections.structureId]]')
->where(['sections.dateDeleted' => null])
->andWhere(['structures.dateDeleted' => null])
->all();
@@ -1929,9 +1935,9 @@ private function _getSectionData(): array
'sites.uid AS siteUid',
'sections.uid AS sectionUid',
])
- ->from(['{{%sections_sites}} sections_sites'])
- ->innerJoin('{{%sites}} sites', '[[sites.id]] = [[sections_sites.siteId]]')
- ->innerJoin('{{%sections}} sections', '[[sections.id]] = [[sections_sites.sectionId]]')
+ ->from(['sections_sites' => Table::SECTIONS_SITES])
+ ->innerJoin(['sites' => Table::SITES], '[[sites.id]] = [[sections_sites.siteId]]')
+ ->innerJoin(['sections' => Table::SECTIONS], '[[sections.id]] = [[sections_sites.sectionId]]')
->where(['sites.dateDeleted' => null])
->andWhere(['sections.dateDeleted' => null])
->all();
@@ -1959,8 +1965,8 @@ private function _getSectionData(): array
'entrytypes.uid',
'sections.uid AS sectionUid',
])
- ->from(['{{%entrytypes}} as entrytypes'])
- ->innerJoin('{{%sections}} sections', '[[sections.id]] = [[entrytypes.sectionId]]')
+ ->from(['entrytypes' => Table::ENTRYTYPES])
+ ->innerJoin(['sections' => Table::SECTIONS], '[[sections.id]] = [[entrytypes.sectionId]]')
->where(['sections.dateDeleted' => null])
->andWhere(['entrytypes.dateDeleted' => null])
->all();
@@ -2035,8 +2041,8 @@ private function _getFieldData(): array
'fields.uid',
'fieldGroups.uid AS fieldGroup',
])
- ->from(['{{%fields}} fields'])
- ->leftJoin('{{%fieldgroups}} fieldGroups', '[[fields.groupId]] = [[fieldGroups.id]]')
+ ->from(['fields' => Table::FIELDS])
+ ->leftJoin(['fieldGroups' => Table::FIELDGROUPS], '[[fieldGroups.id]] = [[fields.groupId]]')
->where(['fields.context' => 'global'])
->all();
@@ -2087,8 +2093,8 @@ private function _getMatrixBlockTypeData(): array
'bt.uid',
'f.uid AS field',
])
- ->from(['{{%matrixblocktypes}} bt'])
- ->innerJoin('{{%fields}} f', '[[bt.fieldId]] = [[f.id]]')
+ ->from(['bt' => Table::MATRIXBLOCKTYPES])
+ ->innerJoin(['f' => Table::FIELDS], '[[f.id]] = [[bt.fieldId]]')
->all();
$layoutIds = [];
@@ -2123,8 +2129,8 @@ private function _getMatrixBlockTypeData(): array
'fields.uid',
'fieldGroups.uid AS fieldGroup',
])
- ->from(['{{%fields}} fields'])
- ->leftJoin('{{%fieldgroups}} fieldGroups', '[[fields.groupId]] = [[fieldGroups.id]]')
+ ->from(['fields' => Table::FIELDS])
+ ->leftJoin(['fieldGroups' => Table::FIELDGROUPS], '[[fieldGroups.id]] = [[fields.groupId]]')
->where(['like', 'fields.context', 'matrixBlockType:'])
->all();
@@ -2188,7 +2194,7 @@ private function _getVolumeData(): array
'volumes.sortOrder',
'volumes.uid',
])
- ->from(['{{%volumes}} volumes'])
+ ->from(['volumes' => Table::VOLUMES])
->where(['volumes.dateDeleted' => null])
->all();
@@ -2295,8 +2301,8 @@ private function _getCategoryGroupData(): array
'structures.uid AS structure',
'structures.maxLevels AS structureMaxLevels',
])
- ->from(['{{%categorygroups}} groups'])
- ->leftJoin('{{%structures}} structures', '[[structures.id]] = [[groups.structureId]]')
+ ->from(['groups' => Table::CATEGORYGROUPS])
+ ->leftJoin(['structures' => Table::STRUCTURES], '[[structures.id]] = [[groups.structureId]]')
->where(['groups.dateDeleted' => null])
->andWhere(['structures.dateDeleted' => null])
->all();
@@ -2342,9 +2348,9 @@ private function _getCategoryGroupData(): array
'sites.uid AS siteUid',
'groups.uid AS groupUid',
])
- ->from(['{{%categorygroups_sites}} groups_sites'])
- ->innerJoin('{{%sites}} sites', '[[sites.id]] = [[groups_sites.siteId]]')
- ->innerJoin('{{%categorygroups}} groups', '[[groups.id]] = [[groups_sites.groupId]]')
+ ->from(['groups_sites' => Table::CATEGORYGROUPS_SITES])
+ ->innerJoin(['sites' => Table::SITES], '[[sites.id]] = [[groups_sites.siteId]]')
+ ->innerJoin(['groups' => Table::CATEGORYGROUPS], '[[groups.id]] = [[groups_sites.groupId]]')
->where(['groups.dateDeleted' => null])
->andWhere(['sites.dateDeleted' => null])
->all();
@@ -2376,7 +2382,7 @@ private function _getTagGroupData(): array
'groups.uid',
'groups.fieldLayoutId',
])
- ->from(['{{%taggroups}} groups'])
+ ->from(['groups' => Table::TAGGROUPS])
->where(['groups.dateDeleted' => null])
->all();
@@ -2419,7 +2425,7 @@ private function _getGlobalSetData(): array
'sets.uid',
'sets.fieldLayoutId',
])
- ->from(['{{%globalsets}} sets'])
+ ->from(['sets' => Table::GLOBALSETS])
->all();
$setData = [];
@@ -2572,10 +2578,10 @@ private function _generateFieldLayoutArray(array $layoutIds): array
'tabs.uid AS tabUid',
'layouts.id AS layoutId',
])
- ->from(['{{%fieldlayoutfields}} AS layoutFields'])
- ->innerJoin('{{%fieldlayouttabs}} AS tabs', '[[layoutFields.tabId]] = [[tabs.id]]')
- ->innerJoin('{{%fieldlayouts}} AS layouts', '[[layoutFields.layoutId]] = [[layouts.id]]')
- ->innerJoin('{{%fields}} AS fields', '[[layoutFields.fieldId]] = [[fields.id]]')
+ ->from(['layoutFields' => Table::FIELDLAYOUTFIELDS])
+ ->innerJoin(['tabs' => Table::FIELDLAYOUTTABS], '[[tabs.id]] = [[layoutFields.tabId]]')
+ ->innerJoin(['layouts' => Table::FIELDLAYOUTS], '[[layouts.id]] = [[layoutFields.layoutId]]')
+ ->innerJoin(['fields' => Table::FIELDS], '[[fields.id]] = [[layoutFields.fieldId]]')
->where(['layouts.id' => $layoutIds])
->andWhere(['layouts.dateDeleted' => null])
->orderBy(['tabs.sortOrder' => SORT_ASC, 'layoutFields.sortOrder' => SORT_ASC])
diff --git a/src/services/Relations.php b/src/services/Relations.php
index 4694f365290..89ff0836d9a 100644
--- a/src/services/Relations.php
+++ b/src/services/Relations.php
@@ -8,12 +8,12 @@
namespace craft\services;
use Craft;
-use craft\base\Element;
use craft\base\ElementInterface;
use craft\db\Command;
use craft\db\Query;
use craft\db\Table;
use craft\fields\BaseRelationField;
+use craft\helpers\Db;
use yii\base\Component;
/**
@@ -35,7 +35,6 @@ class Relations extends Component
*/
public function saveRelations(BaseRelationField $field, ElementInterface $source, array $targetIds)
{
- /** @var Element $source */
if (!is_array($targetIds)) {
$targetIds = [];
}
@@ -105,15 +104,13 @@ public function saveRelations(BaseRelationField $field, ElementInterface $source
$sortOrder + 1,
];
}
- $db->createCommand()
- ->batchInsert(Table::RELATIONS, ['fieldId', 'sourceId', 'sourceSiteId', 'targetId', 'sortOrder'], $values)
- ->execute();
+ Db::batchInsert(Table::RELATIONS, ['fieldId', 'sourceId', 'sourceSiteId', 'targetId', 'sortOrder'], $values);
}
if (!empty($deleteIds)) {
- $db->createCommand()
- ->delete(Table::RELATIONS, ['id' => $deleteIds])
- ->execute();
+ Db::delete(Table::RELATIONS, [
+ 'id' => $deleteIds,
+ ]);
}
$transaction->commit();
diff --git a/src/services/Revisions.php b/src/services/Revisions.php
index 9c4ac20c701..dd728d47ad9 100644
--- a/src/services/Revisions.php
+++ b/src/services/Revisions.php
@@ -8,7 +8,6 @@
namespace craft\services;
use Craft;
-use craft\base\Element;
use craft\base\ElementInterface;
use craft\behaviors\RevisionBehavior;
use craft\db\Query;
@@ -16,7 +15,10 @@
use craft\errors\InvalidElementException;
use craft\events\RevisionEvent;
use craft\helpers\ArrayHelper;
+use craft\helpers\Db;
use craft\helpers\ElementHelper;
+use craft\helpers\Queue;
+use craft\queue\jobs\PruneRevisions;
use yii\base\Component;
use yii\base\InvalidArgumentException;
use yii\db\Exception;
@@ -66,12 +68,10 @@ class Revisions extends Component
public function createRevision(ElementInterface $source, int $creatorId = null, string $notes = null, array $newAttributes = [], bool $force = false): ElementInterface
{
// Make sure the source isn't a draft or revision
- /** @var Element $source */
if ($source->getIsDraft() || $source->getIsRevision()) {
throw new InvalidArgumentException('Cannot create a revision from another revision or draft.');
}
- /** @var Element $source */
$lockKey = 'revision:' . $source->id;
$mutex = Craft::$app->getMutex();
if (!$mutex->acquire($lockKey)) {
@@ -92,7 +92,7 @@ public function createRevision(ElementInterface $source, int $creatorId = null,
if (!$force && $lastRevisionNum) {
// Get the revision, if it exists for the source's site
- /** @var Element|RevisionBehavior|null $lastRevision */
+ /** @var ElementInterface|RevisionBehavior|null $lastRevision */
$lastRevision = $source::find()
->revisionOf($source)
->siteId($source->siteId)
@@ -132,17 +132,14 @@ public function createRevision(ElementInterface $source, int $creatorId = null,
$transaction = Craft::$app->getDb()->beginTransaction();
try {
// Create the revision row
- $db = Craft::$app->getDb();
- $db->createCommand()
- ->insert(Table::REVISIONS, [
- 'sourceId' => $source->id,
- 'creatorId' => $creatorId,
- 'num' => $num,
- 'notes' => $notes,
- ], false)
- ->execute();
+ Db::insert(Table::REVISIONS, [
+ 'sourceId' => $source->id,
+ 'creatorId' => $creatorId,
+ 'num' => $num,
+ 'notes' => $notes,
+ ], false);
- $newAttributes['revisionId'] = $db->getLastInsertID(Table::REVISIONS);
+ $newAttributes['revisionId'] = Craft::$app->getDb()->getLastInsertID(Table::REVISIONS);
$newAttributes['behaviors']['revision'] = [
'class' => RevisionBehavior::class,
'sourceId' => $source->id,
@@ -156,7 +153,6 @@ public function createRevision(ElementInterface $source, int $creatorId = null,
}
// Duplicate the element
- /** @var Element $revision */
$revision = $elementsService->duplicateElement($source, $newAttributes);
$transaction->commit();
@@ -180,20 +176,12 @@ public function createRevision(ElementInterface $source, int $creatorId = null,
$mutex->release($lockKey);
// Prune any excess revisions
- $maxRevisions = Craft::$app->getConfig()->getGeneral()->maxRevisions;
- if ($maxRevisions > 0) {
- // Don't count the current revision
- $extraRevisions = $source::find()
- ->revisionOf($source)
- ->siteId($source->siteId)
- ->anyStatus()
- ->orderBy(['num' => SORT_DESC])
- ->offset($maxRevisions)
- ->all();
-
- foreach ($extraRevisions as $extraRevision) {
- $elementsService->deleteElement($extraRevision, true);
- }
+ if (Craft::$app->getConfig()->getGeneral()->maxRevisions) {
+ Queue::push(new PruneRevisions([
+ 'elementType' => get_class($source),
+ 'sourceId' => $source->id,
+ 'siteId' => $source->siteId,
+ ]), 2049);
}
return $revision;
@@ -210,8 +198,7 @@ public function createRevision(ElementInterface $source, int $creatorId = null,
*/
public function revertToRevision(ElementInterface $revision, int $creatorId): ElementInterface
{
- /** @var Element|RevisionBehavior $revision */
- /** @var Element $source */
+ /** @var ElementInterface|RevisionBehavior $revision */
$source = ElementHelper::sourceElement($revision);
// Fire a 'beforeRevertToRevision' event
diff --git a/src/services/Search.php b/src/services/Search.php
index 7163adb9e3e..fe226f7171f 100644
--- a/src/services/Search.php
+++ b/src/services/Search.php
@@ -8,9 +8,8 @@
namespace craft\services;
use Craft;
-use craft\base\Element;
use craft\base\ElementInterface;
-use craft\base\Field;
+use craft\base\FieldInterface;
use craft\db\Query;
use craft\db\Table;
use craft\errors\SiteNotFoundException;
@@ -107,7 +106,6 @@ public function indexElementAttributes(ElementInterface $element, array $fieldHa
{
// Acquire a lock for this element/site ID
$mutex = Craft::$app->getMutex();
- /** @var Element $element */
$lockKey = "searchindex:{$element->id}:{$element->siteId}";
if (!$mutex->acquire($lockKey)) {
@@ -116,7 +114,7 @@ public function indexElementAttributes(ElementInterface $element, array $fieldHa
}
// Figure out which fields to update, and which to ignore
- /** @var Field[] $updateFields */
+ /** @var FieldInterface[] $updateFields */
$updateFields = [];
/** @var string[] $ignoreFieldIds */
$ignoreFieldIds = [];
@@ -125,7 +123,6 @@ public function indexElementAttributes(ElementInterface $element, array $fieldHa
$fieldHandles = array_flip($fieldHandles);
}
foreach ($fieldLayout->getFields() as $field) {
- /** @var Field $field */
if ($field->searchable) {
// Are we updating this field's keywords?
if ($fieldHandles === null || isset($fieldHandles[$field->handle])) {
@@ -146,9 +143,7 @@ public function indexElementAttributes(ElementInterface $element, array $fieldHa
if (!empty($ignoreFieldIds)) {
$deleteCondition = ['and', $deleteCondition, ['not', ['fieldId' => $ignoreFieldIds]]];
}
- Craft::$app->getDb()->createCommand()
- ->delete(Table::SEARCHINDEX, $deleteCondition)
- ->execute();
+ Db::delete(Table::SEARCHINDEX, $deleteCondition);
// Update the element attributes' keywords
$searchableAttributes = array_flip($element::searchableAttributes());
@@ -337,17 +332,20 @@ public function filterElementIdsByQuery(array $elementIds, $query, bool $scoreRe
public function deleteOrphanedIndexes()
{
$db = Craft::$app->getDb();
+ $searchIndexTable = Table::SEARCHINDEX;
+ $elementsTable = Table::ELEMENTS;
+
if ($db->getIsMysql()) {
$sql = <<createCommand()
- ->insert(Table::SEARCHINDEX, $columns, false)
- ->execute();
+ Db::insert(Table::SEARCHINDEX, $columns, false);
}
/**
@@ -734,7 +730,6 @@ private function _normalizeTerm(string $term): string
private function _getFieldIdFromAttribute(string $attribute): int
{
// Get field id from service
- /** @var Field $field */
$field = Craft::$app->getFields()->getFieldByHandle($attribute);
// Fallback to 0
diff --git a/src/services/Sections.php b/src/services/Sections.php
index 5a6a0d20bdd..869251749c0 100644
--- a/src/services/Sections.php
+++ b/src/services/Sections.php
@@ -9,7 +9,6 @@
use Craft;
use craft\base\Element;
-use craft\base\Field;
use craft\db\Query;
use craft\db\Table;
use craft\elements\Entry;
@@ -24,6 +23,7 @@
use craft\helpers\Db;
use craft\helpers\Json;
use craft\helpers\ProjectConfig as ProjectConfigHelper;
+use craft\helpers\Queue;
use craft\helpers\StringHelper;
use craft\models\EntryType;
use craft\models\FieldLayout;
@@ -371,8 +371,8 @@ public function getSectionSiteSettings(int $sectionId): array
'sections_sites.uriFormat',
'sections_sites.template',
])
- ->from(['{{%sections_sites}} sections_sites'])
- ->innerJoin('{{%sites}} sites', '[[sites.id]] = [[sections_sites.siteId]]')
+ ->from(['sections_sites' => Table::SECTIONS_SITES])
+ ->innerJoin(['sites' => Table::SITES], '[[sites.id]] = [[sections_sites.siteId]]')
->where(['sections_sites.sectionId' => $sectionId])
->orderBy(['sites.sortOrder' => SORT_ASC])
->all();
@@ -728,7 +728,7 @@ public function handleChangedSection(ConfigEvent $event)
if (!$isNewSection && $resaveEntries) {
// If the propagation method just changed, we definitely need to update entries for that
if ($propagationMethodChanged) {
- Craft::$app->getQueue()->push(new ApplyNewPropagationMethod([
+ Queue::push(new ApplyNewPropagationMethod([
'description' => Craft::t('app', 'Applying new propagation method to {section} entries', [
'section' => $sectionRecord->name,
]),
@@ -738,7 +738,7 @@ public function handleChangedSection(ConfigEvent $event)
],
]));
} else if ($this->autoResaveEntries) {
- Craft::$app->getQueue()->push(new ResaveElements([
+ Queue::push(new ResaveElements([
'description' => Craft::t('app', 'Resaving {section} entries', [
'section' => $sectionRecord->name,
]),
@@ -935,7 +935,6 @@ public function pruneDeletedSite(DeleteSiteEvent $event)
*/
public function pruneDeletedField(FieldEvent $event)
{
- /** @var Field $field */
$field = $event->field;
$fieldUid = $field->uid;
@@ -965,7 +964,9 @@ public function pruneDeletedField(FieldEvent $event)
}
// Nuke all the layout fields from the DB
- Craft::$app->getDb()->createCommand()->delete('{{%fieldlayoutfields}}', ['fieldId' => $field->id])->execute();
+ Db::delete(Table::FIELDLAYOUTFIELDS, [
+ 'fieldId' => $field->id,
+ ]);
// Allow events again
$projectConfig->muteEvents = false;
@@ -1275,7 +1276,7 @@ public function handleChangedEntryType(ConfigEvent $event)
$this->_ensureSingleEntry($section);
} else if (!$isNewEntryType && $resaveEntries && $this->autoResaveEntries) {
// Re-save the entries of this type
- Craft::$app->getQueue()->push(new ResaveElements([
+ Queue::push(new ResaveElements([
'description' => Craft::t('app', 'Resaving {type} entries', [
'type' => ($section->type !== Section::TYPE_SINGLE ? $section->name . ' - ' : '') . $entryType->name,
]),
@@ -1486,8 +1487,8 @@ private function _createSectionQuery(): Query
'sections.uid',
'structures.maxLevels',
])
- ->leftJoin('{{%structures}} structures', $joinCondition)
- ->from(['{{%sections}} sections'])
+ ->leftJoin(['structures' => Table::STRUCTURES], $joinCondition)
+ ->from(['sections' => Table::SECTIONS])
->where($condition)
->orderBy(['name' => SORT_ASC]);
diff --git a/src/services/Security.php b/src/services/Security.php
index 128ea3def56..0282b81e258 100644
--- a/src/services/Security.php
+++ b/src/services/Security.php
@@ -77,7 +77,6 @@ public function hashPassword(string $password, bool $validateHash = false): stri
*/
public function getValidationKey(): string
{
- Craft::$app->getDeprecator()->log(__METHOD__, 'Craft::$app->security->getValidationKey() has been deprecated. Use Craft::$app->config->general->securityKey instead.');
return Craft::$app->getConfig()->getGeneral()->securityKey;
}
diff --git a/src/services/Sites.php b/src/services/Sites.php
index 1e8a61f4959..52e5ab95745 100644
--- a/src/services/Sites.php
+++ b/src/services/Sites.php
@@ -8,7 +8,7 @@
namespace craft\services;
use Craft;
-use craft\base\Element;
+use craft\base\ElementInterface;
use craft\db\Query;
use craft\db\Table;
use craft\elements\Asset;
@@ -24,6 +24,7 @@
use craft\helpers\App;
use craft\helpers\ArrayHelper;
use craft\helpers\Db;
+use craft\helpers\Queue;
use craft\helpers\StringHelper;
use craft\models\Site;
use craft\models\SiteGroup;
@@ -137,19 +138,13 @@ class Sites extends Component
* @var Site[]
* @see getSiteById()
*/
- private $_sitesById;
+ private $_allSitesById;
/**
* @var Site[]
- * @see getSiteByUid()
- */
- private $_sitesByUid;
-
- /**
- * @var Site[]
- * @see getSiteByHandle()
+ * @see getSiteById()
*/
- private $_sitesByHandle;
+ private $_enabledSitesById;
/**
* @var Site|null the current site
@@ -388,26 +383,29 @@ public function deleteGroup(SiteGroup $group): bool
/**
* Returns all of the site IDs.
*
+ * @param bool|null $withDisabled
* @return int[] All the sites’ IDs
*/
- public function getAllSiteIds(): array
+ public function getAllSiteIds(bool $withDisabled = null): array
{
- return array_keys($this->_sitesById);
+ return ArrayHelper::getColumn($this->_allSites($withDisabled), 'id', false);
}
/**
* Returns a site by it's UID.
*
+ * @param string $uid
+ * @param bool|null $withDisabled
* @return Site the site
* @throws SiteNotFoundException if no sites exist
*/
- public function getSiteByUid(string $uid): Site
+ public function getSiteByUid(string $uid, bool $withDisabled = null): Site
{
- if (!isset($this->_sitesByUid[$uid])) {
+ $site = ArrayHelper::firstWhere($this->_allSites($withDisabled), 'uid', $uid, true);
+ if ($site === null) {
throw new SiteNotFoundException('Site with UID ”' . $uid . '“ not found!');
}
-
- return $this->_sitesByUid[$uid];
+ return $site;
}
/**
@@ -508,9 +506,10 @@ public function getEditableSiteIds(): array
}
$this->_editableSiteIds = [];
+ $userSession = Craft::$app->getUser();
foreach ($this->getAllSites() as $site) {
- if (Craft::$app->getUser()->checkPermission('editSite:' . $site->uid)) {
+ if ($userSession->checkPermission("editSite:$site->uid")) {
$this->_editableSiteIds[] = $site->id;
}
}
@@ -521,11 +520,12 @@ public function getEditableSiteIds(): array
/**
* Returns all sites.
*
+ * @param bool|null $withDisabled
* @return Site[] All the sites
*/
- public function getAllSites(): array
+ public function getAllSites(bool $withDisabled = null): array
{
- return array_values($this->_sitesById);
+ return array_values($this->_allSites($withDisabled));
}
/**
@@ -551,17 +551,12 @@ public function getEditableSites(): array
* Returns sites by a group ID.
*
* @param int $groupId
+ * @param bool|null $withDisabled
* @return Site[]
*/
- public function getSitesByGroupId(int $groupId): array
+ public function getSitesByGroupId(int $groupId, bool $withDisabled = null): array
{
- $sites = [];
-
- foreach ($this->getAllSites() as $site) {
- if ($site->groupId == $groupId) {
- $sites[] = $site;
- }
- }
+ $sites = ArrayHelper::where($this->_allSites($withDisabled), 'groupId', $groupId);
// Using array_multisort threw a nesting error for no obvious reason, so don't use it here.
ArrayHelper::multisort($sites, 'sortOrder', SORT_ASC, SORT_NUMERIC);
@@ -593,22 +588,24 @@ public function getTotalEditableSites(): int
* Returns a site by its ID.
*
* @param int $siteId
+ * @param bool|null $withDisabled
* @return Site|null
*/
- public function getSiteById(int $siteId)
+ public function getSiteById(int $siteId, bool $withDisabled = null)
{
- return $this->_sitesById[$siteId] ?? null;
+ return $this->_allSites($withDisabled)[$siteId] ?? null;
}
/**
* Returns a site by its handle.
*
* @param string $siteHandle
+ * @param bool|null $withDisabled
* @return Site|null
*/
- public function getSiteByHandle(string $siteHandle)
+ public function getSiteByHandle(string $siteHandle, bool $withDisabled = null)
{
- return $this->_sitesByHandle[$siteHandle] ?? null;
+ return ArrayHelper::firstWhere($this->_allSites($withDisabled), 'handle', $siteHandle, true);
}
/**
@@ -624,7 +621,7 @@ public function saveSite(Site $site, bool $runValidation = true): bool
{
$isNewSite = !$site->id;
- if (!empty($this->_sitesById)) {
+ if (!empty($this->_allSitesById)) {
$oldPrimarySiteId = $this->getPrimarySite()->id;
} else {
$oldPrimarySiteId = null;
@@ -656,6 +653,7 @@ public function saveSite(Site $site, bool $runValidation = true): bool
'baseUrl' => $site->baseUrl,
'sortOrder' => (int)$site->sortOrder,
'primary' => (bool)$site->primary,
+ 'enabled' => (bool)$site->enabled,
];
if ($isNewSite) {
@@ -717,6 +715,7 @@ public function handleChangedSite(ConfigEvent $event)
$siteRecord->hasUrls = $data['hasUrls'];
$siteRecord->baseUrl = $data['baseUrl'];
$siteRecord->primary = $data['primary'];
+ $siteRecord->enabled = $data['enabled'] ?? true;
$siteRecord->sortOrder = $data['sortOrder'];
if ($siteRecord->dateDeleted) {
@@ -766,7 +765,6 @@ public function handleChangedSite(ConfigEvent $event)
// Re-save most localizable element types
// (skip entries because they only support specific sites)
// (skip Matrix blocks because they will be re-saved when their owners are re-saved).
- $queue = Craft::$app->getQueue();
$elementTypes = [
GlobalSet::class,
Asset::class,
@@ -775,7 +773,7 @@ public function handleChangedSite(ConfigEvent $event)
];
foreach ($elementTypes as $elementType) {
- $queue->push(new PropagateElements([
+ Queue::push(new PropagateElements([
'elementType' => $elementType,
'criteria' => [
'siteId' => $oldPrimarySiteId,
@@ -908,12 +906,11 @@ public function deleteSite(Site $site, int $transferContentTo = null): bool
if ($transferContentTo !== null) {
$transferContentToSite = $this->getSiteById($transferContentTo);
- Craft::$app->getDb()->createCommand()
- ->update(
- Table::SECTIONS_SITES,
- ['siteId' => $transferContentTo],
- ['sectionId' => $soloSectionIds])
- ->execute();
+ Db::update(Table::SECTIONS_SITES, [
+ 'siteId' => $transferContentTo,
+ ], [
+ 'sectionId' => $soloSectionIds,
+ ]);
// Update the project config too
$muteEvents = $projectConfig->muteEvents;
@@ -938,30 +935,25 @@ public function deleteSite(Site $site, int $transferContentTo = null): bool
Craft::$app->getTemplateCaches()->deleteCachesByElementId($entryIds);
// Update the entry tables
- Craft::$app->getDb()->createCommand()
- ->update(
- Table::CONTENT,
- ['siteId' => $transferContentTo],
- ['elementId' => $entryIds])
- ->execute();
-
- Craft::$app->getDb()->createCommand()
- ->update(
- Table::ELEMENTS_SITES,
- ['siteId' => $transferContentTo],
- ['elementId' => $entryIds])
- ->execute();
-
- Craft::$app->getDb()->createCommand()
- ->update(
- Table::RELATIONS,
- ['sourceSiteId' => $transferContentTo],
- [
- 'and',
- ['sourceId' => $entryIds],
- ['not', ['sourceSiteId' => null]]
- ])
- ->execute();
+ Db::update(Table::CONTENT, [
+ 'siteId' => $transferContentTo,
+ ], [
+ 'elementId' => $entryIds,
+ ]);
+
+ Db::update(Table::ELEMENTS_SITES, [
+ 'siteId' => $transferContentTo,
+ ], [
+ 'elementId' => $entryIds,
+ ]);
+
+ Db::update(Table::RELATIONS, [
+ 'sourceSiteId' => $transferContentTo,
+ ], [
+ 'and',
+ ['sourceId' => $entryIds],
+ ['not', ['sourceSiteId' => null]],
+ ]);
// All the Matrix tables
$blockIds = (new Query())
@@ -971,60 +963,43 @@ public function deleteSite(Site $site, int $transferContentTo = null): bool
->column();
if (!empty($blockIds)) {
- Craft::$app->getDb()->createCommand()
- ->delete(
- Table::ELEMENTS_SITES,
- [
- 'elementId' => $blockIds,
- 'siteId' => $transferContentTo
- ])
- ->execute();
-
- Craft::$app->getDb()->createCommand()
- ->update(
- Table::ELEMENTS_SITES,
- ['siteId' => $transferContentTo],
- [
- 'elementId' => $blockIds,
- 'siteId' => $site->id
- ])
- ->execute();
+ Db::delete(Table::ELEMENTS_SITES, [
+ 'elementId' => $blockIds,
+ 'siteId' => $transferContentTo,
+ ]);
+
+ Db::update(Table::ELEMENTS_SITES, [
+ 'siteId' => $transferContentTo,
+ ], [
+ 'elementId' => $blockIds,
+ 'siteId' => $site->id,
+ ]);
$matrixTablePrefix = Craft::$app->getDb()->getSchema()->getRawTableName('{{%matrixcontent_}}');
foreach (Craft::$app->getDb()->getSchema()->getTableNames() as $tableName) {
if (strpos($tableName, $matrixTablePrefix) === 0) {
- Craft::$app->getDb()->createCommand()
- ->delete(
- $tableName,
- [
- 'elementId' => $blockIds,
- 'siteId' => $transferContentTo
- ])
- ->execute();
-
- Craft::$app->getDb()->createCommand()
- ->update(
- $tableName,
- ['siteId' => $transferContentTo],
- [
- 'elementId' => $blockIds,
- 'siteId' => $site->id
- ])
- ->execute();
+ Db::delete($tableName, [
+ 'elementId' => $blockIds,
+ 'siteId' => $transferContentTo,
+ ]);
+
+ Db::update($tableName, [
+ 'siteId' => $transferContentTo,
+ ], [
+ 'elementId' => $blockIds,
+ 'siteId' => $site->id,
+ ]);
}
}
- Craft::$app->getDb()->createCommand()
- ->update(
- Table::RELATIONS,
- ['sourceSiteId' => $transferContentTo],
- [
- 'and',
- ['sourceId' => $blockIds],
- ['not', ['sourceSiteId' => null]]
- ])
- ->execute();
+ Db::update(Table::RELATIONS, [
+ 'sourceSiteId' => $transferContentTo,
+ ], [
+ 'and',
+ ['sourceId' => $blockIds],
+ ['not', ['sourceSiteId' => null]],
+ ]);
}
}
} else {
@@ -1117,7 +1092,8 @@ public function restoreSiteById(int $id): bool
*/
private function _refreshAllSites()
{
- $this->_sitesById = null;
+ $this->_allSitesById = null;
+ $this->_enabledSitesById = null;
$this->_loadAllSites();
Craft::$app->getIsMultiSite(true);
}
@@ -1127,13 +1103,12 @@ private function _refreshAllSites()
*/
private function _loadAllSites()
{
- if ($this->_sitesById !== null) {
+ if ($this->_allSitesById !== null) {
return;
}
- $this->_sitesById = [];
- $this->_sitesByHandle = [];
- $this->_sitesByUid = [];
+ $this->_allSitesById = [];
+ $this->_enabledSitesById = [];
if (!Craft::$app->getIsInstalled()) {
return;
@@ -1148,6 +1123,7 @@ private function _loadAllSites()
's.handle',
'language',
's.primary',
+ 's.enabled',
's.hasUrls',
's.baseUrl',
's.sortOrder',
@@ -1155,8 +1131,8 @@ private function _loadAllSites()
's.dateCreated',
's.dateUpdated',
])
- ->from(['{{%sites}} s'])
- ->innerJoin('{{%sitegroups}} sg', '[[sg.id]] = [[s.groupId]]')
+ ->from(['s' => Table::SITES])
+ ->innerJoin(['sg' => Table::SITEGROUPS], '[[sg.id]] = [[s.groupId]]')
->where(['s.dateDeleted' => null])
->andWhere(['sg.dateDeleted' => null])
->orderBy(['sg.name' => SORT_ASC, 's.sortOrder' => SORT_ASC])
@@ -1167,7 +1143,7 @@ private function _loadAllSites()
if (isset($e->errorInfo[0]) && in_array($e->errorInfo[0], ['42S02', '42P01'], true)) {
return;
}
- // If the error code is 42S22 (MySQL) or 42703 (PostgreSQL), then the sites table doesn't have a groupId or dateDeleted column yet
+ // If the error code is 42S22 (MySQL) or 42703 (PostgreSQL), then the sites table doesn't have a groupId, dateDeleted, or enabled column yet
if (isset($e->errorInfo[0]) && in_array($e->errorInfo[0], ['42S22', '42703'], true)) {
$results = (new Query())
->select([
@@ -1181,7 +1157,7 @@ private function _loadAllSites()
's.sortOrder',
's.uid',
])
- ->from(['{{%sites}} s'])
+ ->from(['s' => Table::SITES])
->orderBy(['s.name' => SORT_ASC])
->all();
}
@@ -1197,9 +1173,10 @@ private function _loadAllSites()
foreach ($results as $i => $result) {
$site = new Site($result);
- $this->_sitesById[$site->id] = $site;
- $this->_sitesByHandle[$site->handle] = $site;
- $this->_sitesByUid[$site->uid] = $site;
+ $this->_allSitesById[$site->id] = $site;
+ if ($site->enabled) {
+ $this->_enabledSitesById[$site->id] = $site;
+ }
if ($site->primary) {
$this->_primarySite = $site;
@@ -1259,6 +1236,21 @@ private function _getGroupRecord($criteria, bool $withTrashed = false): SiteGrou
return $query->one() ?? new SiteGroupRecord();
}
+ /**
+ * Returns all sites, or only enabled sites.
+ *
+ * @param bool|null $withDisabled
+ * @return Site[]
+ */
+ private function _allSites(bool $withDisabled = null)
+ {
+ if ($withDisabled === null) {
+ $withDisabled = Craft::$app->getRequest()->getIsCpRequest();
+ }
+
+ return $withDisabled ? $this->_allSitesById : $this->_enabledSitesById;
+ }
+
/**
* Gets a site record or creates a new one.
*
@@ -1293,18 +1285,22 @@ private function _processNewPrimarySite(int $oldPrimarySiteId, int $newPrimarySi
$transaction = $db->beginTransaction();
try {
- $db->createCommand()
- ->update(Table::SITES, ['primary' => false], ['id' => $oldPrimarySiteId])
- ->execute();
- $db->createCommand()
- ->update(Table::SITES, ['primary' => true], ['id' => $newPrimarySiteId])
- ->execute();
+ Db::update(Table::SITES, [
+ 'primary' => false,
+ ], [
+ 'id' => $oldPrimarySiteId,
+ ]);
+ Db::update(Table::SITES, [
+ 'primary' => true,
+ ], [
+ 'id' => $newPrimarySiteId,
+ ]);
// Update all of the non-localized elements
$nonLocalizedElementTypes = [];
foreach (Craft::$app->getElements()->getAllElementTypes() as $elementType) {
- /** @var Element|string $elementType */
+ /** @var ElementInterface|string $elementType */
if (!$elementType::isLocalized()) {
$nonLocalizedElementTypes[] = $elementType;
}
@@ -1326,29 +1322,17 @@ private function _processNewPrimarySite(int $oldPrimarySiteId, int $newPrimarySi
['not', ['siteId' => $oldPrimarySiteId]]
];
- $db->createCommand()
- ->delete(Table::ELEMENTS_SITES, $deleteCondition)
- ->execute();
- $db->createCommand()
- ->delete(Table::CONTENT, $deleteCondition)
- ->execute();
- $db->createCommand()
- ->delete(Table::SEARCHINDEX, $deleteCondition)
- ->execute();
+ Db::delete(Table::ELEMENTS_SITES, $deleteCondition);
+ Db::delete(Table::CONTENT, $deleteCondition);
+ Db::delete(Table::SEARCHINDEX, $deleteCondition);
// Now swap the sites
$updateColumns = ['siteId' => $newPrimarySiteId];
$updateCondition = ['elementId' => $elementIds];
- $db->createCommand()
- ->update(Table::ELEMENTS_SITES, $updateColumns, $updateCondition, [], false)
- ->execute();
- $db->createCommand()
- ->update(Table::CONTENT, $updateColumns, $updateCondition, [], false)
- ->execute();
- $db->createCommand()
- ->update(Table::SEARCHINDEX, $updateColumns, $updateCondition, [], false)
- ->execute();
+ Db::update(Table::ELEMENTS_SITES, $updateColumns, $updateCondition, [], false);
+ Db::update(Table::CONTENT, $updateColumns, $updateCondition, [], false);
+ Db::update(Table::SEARCHINDEX, $updateColumns, $updateCondition, [], false);
}
}
diff --git a/src/services/Structures.php b/src/services/Structures.php
index 1d3bf6b2707..42eadbfadcc 100644
--- a/src/services/Structures.php
+++ b/src/services/Structures.php
@@ -8,7 +8,6 @@
namespace craft\services;
use Craft;
-use craft\base\Element;
use craft\base\ElementInterface;
use craft\db\Query;
use craft\db\Table;
@@ -324,7 +323,6 @@ public function moveAfter(int $structureId, ElementInterface $element, ElementIn
*/
private function _getElementRecord(int $structureId, ElementInterface $element)
{
- /** @var Element $element */
$elementId = $element->id;
if ($elementId) {
@@ -386,7 +384,6 @@ private function _doIt($structureId, ElementInterface $element, StructureElement
$elementRecord = null;
- /** @var Element $element */
// Figure out what we're doing
if ($mode !== 'insert') {
// See if there's an existing structure element record
diff --git a/src/services/Tags.php b/src/services/Tags.php
index 628bf26c3a6..7a491681321 100644
--- a/src/services/Tags.php
+++ b/src/services/Tags.php
@@ -8,7 +8,6 @@
namespace craft\services;
use Craft;
-use craft\base\Field;
use craft\db\Table;
use craft\elements\Tag;
use craft\errors\TagGroupNotFoundException;
@@ -406,7 +405,6 @@ public function handleDeletedTagGroup(ConfigEvent $event)
*/
public function pruneDeletedField(FieldEvent $event)
{
- /** @var Field $field */
$field = $event->field;
$fieldUid = $field->uid;
@@ -432,7 +430,9 @@ public function pruneDeletedField(FieldEvent $event)
}
// Nuke all the layout fields from the DB
- Craft::$app->getDb()->createCommand()->delete('{{%fieldlayoutfields}}', ['fieldId' => $field->id])->execute();
+ Db::delete(Table::FIELDLAYOUTFIELDS, [
+ 'fieldId' => $field->id,
+ ]);
// Allow events again
$projectConfig->muteEvents = false;
diff --git a/src/services/TemplateCaches.php b/src/services/TemplateCaches.php
index 726b81b7bd6..110542c8506 100644
--- a/src/services/TemplateCaches.php
+++ b/src/services/TemplateCaches.php
@@ -8,13 +8,13 @@
namespace craft\services;
use Craft;
-use craft\base\Element;
use craft\base\ElementInterface;
use craft\db\Query;
use craft\db\Table;
use craft\elements\db\ElementQuery;
use craft\events\DeleteTemplateCachesEvent;
use craft\helpers\Db;
+use craft\helpers\Queue;
use craft\helpers\StringHelper;
use craft\queue\jobs\DeleteStaleTemplateCaches;
use DateTime;
@@ -272,18 +272,13 @@ public function endTemplateCache(string $key, bool $global, string $duration = n
$transaction = Craft::$app->getDb()->beginTransaction();
try {
- Craft::$app->getDb()->createCommand()
- ->insert(
- Table::TEMPLATECACHES,
- [
- 'cacheKey' => $key,
- 'siteId' => Craft::$app->getSites()->getCurrentSite()->id,
- 'path' => $global ? null : $this->_getPath(),
- 'expiryDate' => Db::prepareDateForDb($expiration),
- 'body' => $body
- ],
- false)
- ->execute();
+ Db::insert(Table::TEMPLATECACHES, [
+ 'cacheKey' => $key,
+ 'siteId' => Craft::$app->getSites()->getCurrentSite()->id,
+ 'path' => $global ? null : $this->_getPath(),
+ 'expiryDate' => Db::prepareDateForDb($expiration),
+ 'body' => $body,
+ ], false);
$cacheId = Craft::$app->getDb()->getLastInsertID(Table::TEMPLATECACHES);
@@ -299,13 +294,8 @@ public function endTemplateCache(string $key, bool $global, string $duration = n
// We can no longer say we’ve deleted all template caches for this element type
unset($this->_deletedCachesByElementType[$query[0]]);
}
- Craft::$app->getDb()->createCommand()
- ->batchInsert(Table::TEMPLATECACHEQUERIES, [
- 'cacheId',
- 'type',
- 'query'
- ], $values, false)
- ->execute();
+
+ Db::batchInsert(Table::TEMPLATECACHEQUERIES, ['cacheId', 'type', 'query'], $values, false);
unset($this->_cachedQueries[$key]);
}
@@ -317,14 +307,7 @@ public function endTemplateCache(string $key, bool $global, string $duration = n
$values[] = [$cacheId, $elementId];
}
- Craft::$app->getDb()->createCommand()
- ->batchInsert(
- Table::TEMPLATECACHEELEMENTS,
- ['cacheId', 'elementId'],
- $values,
- false)
- ->execute();
-
+ Db::batchInsert(Table::TEMPLATECACHEELEMENTS, ['cacheId', 'elementId'], $values, false);
unset($this->_cacheElementIds[$key]);
}
@@ -362,9 +345,9 @@ public function deleteCacheById($cacheId): bool
]));
}
- $affectedRows = Craft::$app->getDb()->createCommand()
- ->delete(Table::TEMPLATECACHES, ['id' => $cacheId])
- ->execute();
+ $affectedRows = Db::delete(Table::TEMPLATECACHES, [
+ 'id' => $cacheId,
+ ]);
// Fire an 'afterDeleteCaches' event
if ($affectedRows && $this->hasEventHandlers(self::EVENT_AFTER_DELETE_CACHES)) {
@@ -422,7 +405,7 @@ public function deleteCachesByElement($elements): bool
$deleteQueryCaches = !isset($this->_deletedCachesByElementType[$elementType]);
$elementIds = [];
- /** @var Element[] $elements */
+ /** @var ElementInterface[] $elements */
foreach ($elements as $element) {
$elementIds[] = $element->id;
}
@@ -481,7 +464,7 @@ public function handleResponse()
{
// It's possible this is already null
if ($this->_deleteCachesIndex !== null) {
- Craft::$app->getQueue()->push(new DeleteStaleTemplateCaches([
+ Queue::push(new DeleteStaleTemplateCaches([
'elementId' => array_keys($this->_deleteCachesIndex),
]));
diff --git a/src/services/Tokens.php b/src/services/Tokens.php
index 13391383802..293f593ac7e 100644
--- a/src/services/Tokens.php
+++ b/src/services/Tokens.php
@@ -132,18 +132,11 @@ public function getTokenRoute(string $token)
*/
public function incrementTokenUsageCountById(int $tokenId): bool
{
- $affectedRows = Craft::$app->getDb()->createCommand()
- ->update(
- Table::TOKENS,
- [
- 'usageCount' => new Expression('[[usageCount]] + 1')
- ],
- [
- 'id' => $tokenId
- ])
- ->execute();
-
- return (bool)$affectedRows;
+ return (bool)Db::update(Table::TOKENS, [
+ 'usageCount' => new Expression('[[usageCount]] + 1'),
+ ], [
+ 'id' => $tokenId,
+ ]);
}
/**
@@ -154,9 +147,9 @@ public function incrementTokenUsageCountById(int $tokenId): bool
*/
public function deleteTokenById(int $tokenId): bool
{
- Craft::$app->getDb()->createCommand()
- ->delete(Table::TOKENS, ['id' => $tokenId])
- ->execute();
+ Db::delete(Table::TOKENS, [
+ 'id' => $tokenId,
+ ]);
return true;
}
@@ -173,9 +166,7 @@ public function deleteExpiredTokens(): bool
return false;
}
- $affectedRows = Craft::$app->getDb()->createCommand()
- ->delete(Table::TOKENS, ['<=', 'expiryDate', Db::prepareDateForDb(new DateTime())])
- ->execute();
+ $affectedRows = Db::delete(Table::TOKENS, ['<=', 'expiryDate', Db::prepareDateForDb(new DateTime())]);
$this->_deletedExpiredTokens = true;
diff --git a/src/services/Updates.php b/src/services/Updates.php
index c1d32b5be32..4e6dcda7afa 100644
--- a/src/services/Updates.php
+++ b/src/services/Updates.php
@@ -8,11 +8,11 @@
namespace craft\services;
use Craft;
-use craft\base\Plugin;
use craft\base\PluginInterface;
use craft\db\Table;
use craft\errors\MigrateException;
use craft\helpers\ArrayHelper;
+use craft\helpers\Db;
use craft\helpers\FileHelper;
use craft\models\Updates as UpdatesModel;
use yii\base\Component;
@@ -135,16 +135,12 @@ public function cacheUpdates(array $updateData): UpdatesModel
*/
public function setNewPluginInfo(PluginInterface $plugin): bool
{
- /** @var Plugin $plugin */
- $affectedRows = Craft::$app->getDb()->createCommand()
- ->update(
- Table::PLUGINS,
- [
- 'version' => $plugin->getVersion(),
- 'schemaVersion' => $plugin->schemaVersion
- ],
- ['handle' => $plugin->id])
- ->execute();
+ $success = (bool)Db::update(Table::PLUGINS, [
+ 'version' => $plugin->getVersion(),
+ 'schemaVersion' => $plugin->schemaVersion,
+ ], [
+ 'handle' => $plugin->id,
+ ]);
// Only update the schema version if it's changed from what's in the file,
// so we don't accidentally overwrite other pending changes
@@ -155,7 +151,7 @@ public function setNewPluginInfo(PluginInterface $plugin): bool
Craft::$app->getProjectConfig()->set($key, $plugin->schemaVersion, "Update plugin schema version for “{$plugin->handle}”");
}
- return (bool)$affectedRows;
+ return $success;
}
/**
@@ -177,7 +173,6 @@ public function getPendingMigrationHandles($includeContent = false): array
$pluginsService = Craft::$app->getPlugins();
foreach ($pluginsService->getAllPlugins() as $plugin) {
- /** @var Plugin $plugin */
if ($pluginsService->doesPluginRequireDatabaseUpdate($plugin)) {
$handles[] = $plugin->id;
}
@@ -227,7 +222,6 @@ public function runMigrations(array $handles)
} else if ($handle === 'content') {
Craft::$app->getContentMigrator()->up();
} else {
- /** @var Plugin $plugin */
$plugin = Craft::$app->getPlugins()->getPlugin($handle);
$name = $plugin->name;
$plugin->getMigrator()->up();
diff --git a/src/services/UserGroups.php b/src/services/UserGroups.php
index c2a8fc4c5e2..4da7da5c26f 100644
--- a/src/services/UserGroups.php
+++ b/src/services/UserGroups.php
@@ -175,8 +175,8 @@ public function getGroupsByUserId(int $userId): array
'g.handle',
'g.uid'
])
- ->from(['{{%usergroups}} g'])
- ->innerJoin('{{%usergroups_users}} gu', '[[gu.groupId]] = [[g.id]]')
+ ->from(['g' => Table::USERGROUPS])
+ ->innerJoin(['gu' => Table::USERGROUPS_USERS], '[[gu.groupId]] = [[g.id]]')
->where(['gu.userId' => $userId])
->all();
@@ -289,9 +289,9 @@ public function handleDeletedUserGroup(ConfigEvent $event)
]));
}
- Craft::$app->getDb()->createCommand()
- ->delete(Table::USERGROUPS, ['uid' => $uid])
- ->execute();
+ Db::delete(Table::USERGROUPS, [
+ 'uid' => $uid,
+ ]);
// Fire an 'afterDeleteUserGroup' event
if ($this->hasEventHandlers(self::EVENT_AFTER_DELETE_USER_GROUP)) {
diff --git a/src/services/UserPermissions.php b/src/services/UserPermissions.php
index d0b4dfa2ad5..43fb28310f3 100644
--- a/src/services/UserPermissions.php
+++ b/src/services/UserPermissions.php
@@ -9,15 +9,14 @@
namespace craft\services;
use Craft;
-use craft\base\Plugin;
use craft\base\UtilityInterface;
-use craft\base\Volume;
use craft\db\Query;
use craft\db\Table;
use craft\elements\User;
use craft\errors\WrongEditionException;
use craft\events\ConfigEvent;
use craft\events\RegisterUserPermissionsEvent;
+use craft\helpers\Db;
use craft\helpers\ProjectConfig as ProjectConfigHelper;
use craft\models\CategoryGroup;
use craft\models\Section;
@@ -83,7 +82,6 @@ public function getAllPermissions(): array
];
foreach (Craft::$app->getPlugins()->getAllPlugins() as $plugin) {
- /** @var Plugin $plugin */
if ($plugin->hasCpSection) {
$general['accessCp']['nested']['accessPlugin-' . $plugin->id] = [
'label' => Craft::t('app', 'Access {plugin}', ['plugin' => $plugin->name])
@@ -192,7 +190,6 @@ public function getAllPermissions(): array
// Volumes
// ---------------------------------------------------------------------
- /** @var Volume[] $volumes */
$volumes = Craft::$app->getVolumes()->getAllVolumes();
foreach ($volumes as $volume) {
@@ -252,7 +249,7 @@ public function getPermissionsByGroupId(int $groupId): array
{
if (!isset($this->_permissionsByGroupId[$groupId])) {
$groupPermissions = $this->_createUserPermissionsQuery()
- ->innerJoin('{{%userpermissions_usergroups}} p_g', '[[p_g.permissionId]] = [[p.id]]')
+ ->innerJoin(['p_g' => Table::USERPERMISSIONS_USERGROUPS], '[[p_g.permissionId]] = [[p.id]]')
->where(['p_g.groupId' => $groupId])
->column();
@@ -271,8 +268,8 @@ public function getPermissionsByGroupId(int $groupId): array
public function getGroupPermissionsByUserId(int $userId): array
{
return $this->_createUserPermissionsQuery()
- ->innerJoin('{{%userpermissions_usergroups}} p_g', '[[p_g.permissionId]] = [[p.id]]')
- ->innerJoin('{{%usergroups_users}} g_u', '[[g_u.groupId]] = [[p_g.groupId]]')
+ ->innerJoin(['p_g' => Table::USERPERMISSIONS_USERGROUPS], '[[p_g.permissionId]] = [[p.id]]')
+ ->innerJoin(['g_u' => Table::USERGROUPS_USERS], '[[g_u.groupId]] = [[p_g.groupId]]')
->where(['g_u.userId' => $userId])
->column();
}
@@ -330,7 +327,7 @@ public function getPermissionsByUserId(int $userId): array
$groupPermissions = $this->getGroupPermissionsByUserId($userId);
$userPermissions = $this->_createUserPermissionsQuery()
- ->innerJoin('{{%userpermissions_users}} p_u', '[[p_u.permissionId]] = [[p.id]]')
+ ->innerJoin(['p_u' => Table::USERPERMISSIONS_USERS], '[[p_u.permissionId]] = [[p.id]]')
->where(['p_u.userId' => $userId])
->column();
@@ -369,9 +366,9 @@ public function saveUserPermissions(int $userId, array $permissions): bool
Craft::$app->requireEdition(Craft::Pro);
// Delete any existing user permissions
- Craft::$app->getDb()->createCommand()
- ->delete(Table::USERPERMISSIONS_USERS, ['userId' => $userId])
- ->execute();
+ Db::delete(Table::USERPERMISSIONS_USERS, [
+ 'userId' => $userId,
+ ]);
// Lowercase the permissions
$permissions = array_map('strtolower', $permissions);
@@ -389,12 +386,7 @@ public function saveUserPermissions(int $userId, array $permissions): bool
}
// Add the new user permissions
- Craft::$app->getDb()->createCommand()
- ->batchInsert(
- Table::USERPERMISSIONS_USERS,
- ['permissionId', 'userId'],
- $userPermissionVals)
- ->execute();
+ Db::batchInsert(Table::USERPERMISSIONS_USERS, ['permissionId', 'userId'], $userPermissionVals);
}
// Cache the new permissions
@@ -419,9 +411,9 @@ public function handleChangedGroupPermissions(ConfigEvent $event)
$userGroup = Craft::$app->getUserGroups()->getGroupByUid($uid);
// Delete any existing group permissions
- Craft::$app->getDb()->createCommand()
- ->delete(Table::USERPERMISSIONS_USERGROUPS, ['groupId' => $userGroup->id])
- ->execute();
+ Db::delete(Table::USERPERMISSIONS_USERGROUPS, [
+ 'groupId' => $userGroup->id,
+ ]);
$groupPermissionVals = [];
@@ -432,12 +424,7 @@ public function handleChangedGroupPermissions(ConfigEvent $event)
}
// Add the new group permissions
- Craft::$app->getDb()->createCommand()
- ->batchInsert(
- Table::USERPERMISSIONS_USERGROUPS,
- ['permissionId', 'groupId'],
- $groupPermissionVals)
- ->execute();
+ Db::batchInsert(Table::USERPERMISSIONS_USERGROUPS, ['permissionId', 'groupId'], $groupPermissionVals);
}
// Update caches
@@ -751,6 +738,6 @@ private function _createUserPermissionsQuery(): Query
{
return (new Query())
->select(['p.name'])
- ->from(['{{%userpermissions}} p']);
+ ->from(['p' => Table::USERPERMISSIONS]);
}
}
diff --git a/src/services/Users.php b/src/services/Users.php
index b47575eb72d..b9707b5a3f2 100644
--- a/src/services/Users.php
+++ b/src/services/Users.php
@@ -8,8 +8,6 @@
namespace craft\services;
use Craft;
-use craft\base\Field;
-use craft\base\Volume;
use craft\db\Query;
use craft\db\Table;
use craft\elements\Asset;
@@ -289,14 +287,11 @@ public function saveUserPreferences(User $user, array $preferences)
{
$preferences = $user->mergePreferences($preferences);
- Craft::$app->getDb()->createCommand()
- ->upsert(
- Table::USERPREFERENCES,
- ['userId' => $user->id],
- ['preferences' => Json::encode($preferences)],
- [],
- false)
- ->execute();
+ Db::upsert(Table::USERPREFERENCES, [
+ 'userId' => $user->id,
+ ], [
+ 'preferences' => Json::encode($preferences),
+ ], [], false);
}
/**
@@ -431,7 +426,6 @@ public function saveUserPhoto(string $fileLocation, User $user, string $filename
}
}
- /** @var Volume $volume */
$assetsService = Craft::$app->getAssets();
// If the photo exists, just replace the file.
@@ -782,19 +776,12 @@ public function unsuspendUser(User $user): bool
*/
public function shunMessageForUser(int $userId, string $message, DateTime $expiryDate = null): bool
{
- $affectedRows = Craft::$app->getDb()->createCommand()
- ->upsert(
- Table::SHUNNEDMESSAGES,
- [
- 'userId' => $userId,
- 'message' => $message
- ],
- [
- 'expiryDate' => Db::prepareDateForDb($expiryDate)
- ])
- ->execute();
-
- return (bool)$affectedRows;
+ return (bool)Db::upsert(Table::SHUNNEDMESSAGES, [
+ 'userId' => $userId,
+ 'message' => $message,
+ ], [
+ 'expiryDate' => Db::prepareDateForDb($expiryDate),
+ ]);
}
/**
@@ -806,16 +793,10 @@ public function shunMessageForUser(int $userId, string $message, DateTime $expir
*/
public function unshunMessageForUser(int $userId, string $message): bool
{
- $affectedRows = Craft::$app->getDb()->createCommand()
- ->delete(
- Table::SHUNNEDMESSAGES,
- [
- 'userId' => $userId,
- 'message' => $message
- ])
- ->execute();
-
- return (bool)$affectedRows;
+ return (bool)Db::delete(Table::SHUNNEDMESSAGES, [
+ 'userId' => $userId,
+ 'message' => $message,
+ ]);
}
/**
@@ -913,9 +894,9 @@ public function assignUserToGroups(int $userId, array $groupIds): bool
}
// Delete their existing groups
- Craft::$app->getDb()->createCommand()
- ->delete(Table::USERGROUPS_USERS, ['userId' => $userId])
- ->execute();
+ Db::delete(Table::USERGROUPS_USERS, [
+ 'userId' => $userId,
+ ]);
if (!empty($groupIds)) {
// Add the new ones
@@ -924,15 +905,7 @@ public function assignUserToGroups(int $userId, array $groupIds): bool
$values[] = [$groupId, $userId];
}
- Craft::$app->getDb()->createCommand()
- ->batchInsert(
- Table::USERGROUPS_USERS,
- [
- 'groupId',
- 'userId'
- ],
- $values)
- ->execute();
+ Db::batchInsert(Table::USERGROUPS_USERS, ['groupId', 'userId'], $values);
}
// Fire an 'afterAssignUserToGroups' event
@@ -1103,7 +1076,6 @@ public function canImpersonate(User $impersonator, User $impersonatee): bool
*/
public function pruneDeletedField(FieldEvent $event)
{
- /** @var Field $field */
$field = $event->field;
$fieldUid = $field->uid;
@@ -1125,7 +1097,9 @@ public function pruneDeletedField(FieldEvent $event)
}
// Nuke all the layout fields from the DB
- Craft::$app->getDb()->createCommand()->delete('{{%fieldlayoutfields}}', ['fieldId' => $field->id])->execute();
+ Db::delete(Table::FIELDLAYOUTFIELDS, [
+ 'fieldId' => $field->id,
+ ]);
// Allow events again
$projectConfig->muteEvents = false;
@@ -1207,7 +1181,6 @@ private function _getUserUrl(User $user, string $fePath, string $cpPath): string
$unhashedVerificationCode = $this->_setVerificationCodeOnUserRecord($userRecord);
$userRecord->save();
- $generalConfig = Craft::$app->getConfig()->getGeneral();
$params = [
'code' => $unhashedVerificationCode,
'id' => $user->uid
@@ -1221,11 +1194,11 @@ private function _getUserUrl(User $user, string $fePath, string $cpPath): string
// Only use cpUrl() if the base CP URL has been explicitly set,
// so UrlHelper won't use HTTP_HOST
- if ($generalConfig->baseCpUrl) {
+ if (Craft::$app->getConfig()->getGeneral()->baseCpUrl) {
return UrlHelper::cpUrl($cpPath, $params, $scheme);
}
- $path = $generalConfig->cpTrigger . '/' . $cpPath;
+ $path = UrlHelper::prependCpTrigger($cpPath);
return UrlHelper::siteUrl($path, $params, $scheme);
}
}
diff --git a/src/services/Volumes.php b/src/services/Volumes.php
index 5dc3a7012e7..b8b1762cf45 100644
--- a/src/services/Volumes.php
+++ b/src/services/Volumes.php
@@ -3,8 +3,6 @@
namespace craft\services;
use Craft;
-use craft\base\Field;
-use craft\base\Volume;
use craft\base\VolumeInterface;
use craft\db\Query;
use craft\db\Table;
@@ -152,7 +150,6 @@ public function getViewableVolumes(): array
{
$userSession = Craft::$app->getUser();
return ArrayHelper::where($this->getAllVolumes(), function(VolumeInterface $volume) use ($userSession) {
- /** @var Volume $volume */
return $userSession->checkPermission('viewVolume:' . $volume->uid);
});
}
@@ -280,7 +277,6 @@ public function getVolumeByHandle(string $handle)
*/
public function saveVolume(VolumeInterface $volume, bool $runValidation = true): bool
{
- /** @var Volume $volume */
$isNewVolume = $volume->getIsNew();
// Fire a 'beforeSaveVolume' event
@@ -425,7 +421,6 @@ public function handleChangedVolume(ConfigEvent $event)
// Clear caches
$this->_volumes = null;
- /** @var Volume $volume */
$volume = $this->getVolumeById($volumeRecord->id);
$volume->afterSave($isNewVolume);
@@ -513,7 +508,6 @@ public function createVolume($config): VolumeInterface
}
try {
- /** @var Volume $volume */
$volume = ComponentHelper::createComponent($config, VolumeInterface::class);
} catch (UnknownPropertyException $e) {
// Special case for Local volumes that are being converted to something else
@@ -549,7 +543,6 @@ public function createVolume($config): VolumeInterface
*/
public function ensureTopFolder(VolumeInterface $volume): int
{
- /** @var Volume $volume */
$folder = VolumeFolder::findOne(
[
'name' => $volume->name,
@@ -596,7 +589,6 @@ public function deleteVolumeById(int $volumeId): bool
*/
public function deleteVolume(VolumeInterface $volume): bool
{
- /** @var Volume $volume */
// Fire a 'beforeDeleteVolume' event
if ($this->hasEventHandlers(self::EVENT_BEFORE_DELETE_VOLUME)) {
$this->trigger(self::EVENT_BEFORE_DELETE_VOLUME, new VolumeEvent([
@@ -626,7 +618,6 @@ public function handleDeletedVolume(ConfigEvent $event)
return;
}
- /** @var Volume $volume */
$volume = $this->getVolumeById($volumeRecord->id);
// Fire a 'beforeApplyVolumeDelete' event
@@ -691,7 +682,6 @@ public function handleDeletedVolume(ConfigEvent $event)
*/
public function pruneDeletedField(FieldEvent $event)
{
- /** @var Field $field */
$field = $event->field;
$fieldUid = $field->uid;
@@ -717,7 +707,9 @@ public function pruneDeletedField(FieldEvent $event)
}
// Nuke all the layout fields from the DB
- Craft::$app->getDb()->createCommand()->delete('{{%fieldlayoutfields}}', ['fieldId' => $field->id])->execute();
+ Db::delete(Table::FIELDLAYOUTFIELDS, [
+ 'fieldId' => $field->id,
+ ]);
// Allow events again
$projectConfig->muteEvents = false;
diff --git a/src/templates/_components/fieldtypes/Assets/settings.html b/src/templates/_components/fieldtypes/Assets/settings.html
index a6f561b98d3..32a0d424fc4 100644
--- a/src/templates/_components/fieldtypes/Assets/settings.html
+++ b/src/templates/_components/fieldtypes/Assets/settings.html
@@ -33,7 +33,7 @@
{% block fieldSettings %}
{{ forms.checkboxField({
- label: "Restrict uploads to a single folder?"|t('app'),
+ label: 'Restrict uploads to a single folder'|t('app'),
id: 'useSingleFolder-toggle',
name: 'useSingleFolder',
class: 'use-single-folder-cb',
@@ -86,7 +86,7 @@
}) }}
{{ forms.checkboxField({
- label: "Restrict allowed file types?"|t('app'),
+ label: 'Restrict allowed file types'|t('app'),
class: 'restrictFiles',
id: 'restrictFiles',
name: 'restrictFiles',
diff --git a/src/templates/_components/fieldtypes/Matrix/settings.html b/src/templates/_components/fieldtypes/Matrix/settings.html
index 626da7b6e85..0e6dfbfea9d 100644
--- a/src/templates/_components/fieldtypes/Matrix/settings.html
+++ b/src/templates/_components/fieldtypes/Matrix/settings.html
@@ -101,7 +101,7 @@
{{ "Field Settings"|t('app') }}
}) }}
{{ forms.checkboxField({
- label: "Use this field’s values as search keywords?"|t('app'),
+ label: 'Use this field’s values as search keywords'|t('app'),
id: 'searchable',
name: 'searchable',
checked: field.searchable
diff --git a/src/templates/_components/utilities/AssetIndexes.html b/src/templates/_components/utilities/AssetIndexes.html
index a42a8349a4a..63e0f80d3b0 100644
--- a/src/templates/_components/utilities/AssetIndexes.html
+++ b/src/templates/_components/utilities/AssetIndexes.html
@@ -8,7 +8,7 @@
{{ forms.lightswitchField({
name: 'cacheImages',
- label: 'Cache remote images?'|t('app'),
+ label: 'Cache remote images'|t('app'),
instructions: 'Whether remotely-stored images should be downloaded and stored locally, to speed up transform generation.'|t('app'),
on: true,
}) }}
diff --git a/src/templates/_components/utilities/DbBackup.html b/src/templates/_components/utilities/DbBackup.html
index d7c5d9735cb..b250bd61965 100644
--- a/src/templates/_components/utilities/DbBackup.html
+++ b/src/templates/_components/utilities/DbBackup.html
@@ -7,7 +7,7 @@
{{ forms.checkbox({
name: 'downloadBackup',
id: 'download-backup',
- label: 'Download backup?'|t('app'),
+ label: 'Download backup'|t('app'),
checked: true,
}) }}
diff --git a/src/templates/_components/utilities/SystemMessages/message-modal.html b/src/templates/_components/utilities/SystemMessages/message-modal.html
index 5d6e66d37f5..25ddc6863df 100644
--- a/src/templates/_components/utilities/SystemMessages/message-modal.html
+++ b/src/templates/_components/utilities/SystemMessages/message-modal.html
@@ -28,7 +28,7 @@
{{ forms.checkbox({
- label: field.name|e~(field.required ? ' ' : ''),
+ label: raw(field.name|e ~ (field.required ? '')),
name: 'sections['~section.id~'][fields][]',
value: field.id,
checked: (field.required or field.id in widget.fields),
diff --git a/src/templates/_includes/field.html b/src/templates/_includes/field.html
index 2b7ab4796f7..1ad33bf0d15 100644
--- a/src/templates/_includes/field.html
+++ b/src/templates/_includes/field.html
@@ -27,6 +27,7 @@
{{ field({
status: element ? element.getFieldStatus(field.handle),
label: field.name|t('site')|e,
+ altLabel: tag('code', {text: field.handle}),
translatable: translatable,
translationDescription: field.getTranslationDescription(element),
siteId: siteId,
diff --git a/src/templates/_includes/forms.html b/src/templates/_includes/forms.html
index 6330f392754..6d6b1e1b475 100644
--- a/src/templates/_includes/forms.html
+++ b/src/templates/_includes/forms.html
@@ -17,7 +17,12 @@
{% macro password(config) %}
- {% include "_includes/forms/text" with config|merge({ type: 'password' }) only %}
+ {% include "_includes/forms/password" with config only %}
+{% endmacro %}
+
+
+{% macro copytext(config) %}
+ {% include "_includes/forms/copytext" with config only %}
{% endmacro %}
@@ -125,6 +130,12 @@
{% endmacro %}
+{% macro copytextField(config) %}
+ {% import _self as forms %}
+ {{ forms.field(config, forms.copytext(config)) }}
+{% endmacro %}
+
+
{% macro passwordField(config) %}
{% import _self as forms %}
{{ forms.field(config, forms.password(config)) }}
diff --git a/src/templates/_includes/forms/autosuggest.html b/src/templates/_includes/forms/autosuggest.html
index faeb058f4f5..b4e8aa6fc7a 100644
--- a/src/templates/_includes/forms/autosuggest.html
+++ b/src/templates/_includes/forms/autosuggest.html
@@ -7,12 +7,11 @@
{%- set id = (id is defined and id ? id : 'autosuggest'~random()) %}
{%- set containerId = id ~ '-container' %}
-{%- set class = [
+{%- set class = (class ?? [])|explodeClass|merge([
'text',
- (class is defined and class ? class : null),
- (disabled is defined and disabled ? 'disabled' : null),
- (size is defined and size ? null : 'fullwidth')
-]|filter|join(' ') %}
+ (disabled ?? false) ? 'disabled',
+ not (size ?? false) ? 'fullwidth',
+]|filter) %}
{% verbatim %}
@@ -41,7 +40,7 @@
filteredOptions: [],
suggestions: suggestions ?? [],
inputProps: {
- class: class,
+ class: class|join(' '),
initialValue: value ?? '',
style: style ?? '',
id: id|namespaceInputId,
diff --git a/src/templates/_includes/forms/checkbox.html b/src/templates/_includes/forms/checkbox.html
index adbd5e66495..a3cb4a6b60b 100644
--- a/src/templates/_includes/forms/checkbox.html
+++ b/src/templates/_includes/forms/checkbox.html
@@ -1,10 +1,9 @@
{%- spaceless %}
-{% set class = [
- (class is defined ? class : null),
- ((toggle is defined and toggle is not empty) or (reverseToggle is defined and reverseToggle is not empty) ? 'fieldtoggle' : null),
+{% set class = (class ?? [])|explodeClass|merge([
+ (toggle ?? reverseToggle ?? false) ? 'fieldtoggle',
'checkbox'
-]|filter|join(' ') %}
+]|filter) %}
{% set value = (value is defined ? value : 1) %}
{% set id = (id is defined and id ? id : 'checkbox'~random()) %}
@@ -14,7 +13,7 @@
{{ hiddenInput(name, '') }}
{% endif %}
-