Issues (13)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/services/Configuration.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * @copyright  Copyright (c) Flipbox Digital Limited
5
 * @license    https://flipboxfactory.com/software/meta/license
6
 * @link       https://www.flipboxfactory.com/software/meta/
7
 */
8
9
namespace flipbox\meta\services;
10
11
use Craft;
12
use craft\helpers\ArrayHelper;
13
use craft\helpers\MigrationHelper;
14
use craft\helpers\StringHelper;
15
use craft\models\FieldLayout;
16
use craft\models\FieldLayoutTab;
17
use craft\records\Field;
0 ignored issues
show
This use statement conflicts with another class in this namespace, flipbox\meta\services\Field.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
18
use flipbox\meta\elements\Meta as MetaElement;
19
use flipbox\meta\fields\Meta as MetaField;
20
use flipbox\meta\helpers\Field as FieldHelper;
21
use flipbox\meta\Meta as MetaPlugin;
22
use flipbox\meta\migrations\ContentTable;
23
use flipbox\meta\records\Meta as MetaRecord;
24
use yii\base\Component;
25
use yii\base\Exception;
26
27
/**
28
 * @author Flipbox Factory <[email protected]>
29
 * @since 1.0.0
30
 */
31
class Configuration extends Component
32
{
33
34
    /**
35
     * @param MetaField $metaField
36
     * @return bool
37
     */
38
    public function beforeSave(MetaField $metaField)
39
    {
40
        if (!$metaField->getIsNew()) {
41
            /** @var Field $fieldRecord */
42
            if ($oldFieldRecord = Field::findOne($metaField->id)) {
43
                /** @var Field $oldField */
44
                $oldField = Craft::$app->getFields()->createField(
45
                    $oldFieldRecord->toArray([
46
                        'id',
47
                        'type',
48
                        'name',
49
                        'handle',
50
                        'settings'
51
                    ])
52
                );
53
54
                // Delete the old field layout
55
                if ($oldField instanceof MetaField) {
56
                    return Craft::$app->getFields()->deleteLayoutById($oldField->fieldLayoutId);
57
                }
58
            }
59
        }
60
61
        return true;
62
    }
63
64
    /**
65
     * Saves an Meta field's settings.
66
     *
67
     * @param MetaField $metaField
68
     *
69
     * @throws \Exception
70
     * @return boolean Whether the settings saved successfully.
71
     */
72
    public function afterSave(MetaField $metaField)
73
    {
74
75
        $transaction = Craft::$app->getDb()->beginTransaction();
76
        try {
77
78
            /** @var \craft\services\Content $contentService */
79
            $contentService = Craft::$app->getContent();
80
81
            /** @var \craft\services\Fields $fieldsService */
82
            $fieldsService = Craft::$app->getFields();
83
84
            /** @var \flipbox\meta\services\Field $metaFieldService */
85
            $metaFieldService = MetaPlugin::getInstance()->getField();
86
87
            // Create the content table first since the element fields will need it
88
            $contentTable = $metaFieldService->getContentTableName($metaField);
89
90
            // Get the originals
91
            $originalContentTable = $contentService->contentTable;
92
            $originalFieldContext = $contentService->fieldContext;
93
            $originalFieldPrefix = $contentService->fieldColumnPrefix;
94
            $originalOldFieldPrefix = $fieldsService->oldFieldColumnPrefix;
95
96
            // Set our content table
97
            $contentService->contentTable = $contentTable;
98
            $contentService->fieldColumnPrefix = 'field_';
99
            $fieldsService->oldFieldColumnPrefix = 'field_';
100
101
            // Set our field context
102
            $contentService->fieldContext = FieldHelper::getContextById($metaField->id);
103
104
            // Get existing fields
105
            $oldFieldsById = ArrayHelper::index($fieldsService->getAllFields(), 'id');
106
107
            /** @var \craft\base\Field $field */
108
            foreach ($metaField->getFields() as $field) {
109
                if (!$field->getIsNew()) {
110
                    ArrayHelper::remove($oldFieldsById, $field->id);
111
                }
112
            }
113
114
            // Drop the old fields
115
            foreach ($oldFieldsById as $field) {
116
                if (!$fieldsService->deleteField($field)) {
117
                    throw new Exception(Craft::t('app', 'An error occurred while deleting this Meta field.'));
118
                }
119
            }
120
121
            // Refresh the schema cache
122
            Craft::$app->getDb()->getSchema()->refresh();
123
124
            // Do we need to create/rename the content table?
125
            if (!Craft::$app->getDb()->tableExists($contentTable)) {
126
                $this->createContentTable($contentTable);
127
                Craft::$app->getDb()->getSchema()->refresh();
128
            }
129
130
            // Save the fields and field layout
131
            // -------------------------------------------------------------
132
133
            $fieldLayoutFields = [];
134
            $sortOrder = 0;
135
136
            // Set our content table
137
            $contentService->contentTable = $contentTable;
138
139
            // Save field
140
            /** @var \craft\base\Field $field */
141
            foreach ($metaField->getFields() as $field) {
142
                // Save field (we validated earlier)
143
                if (!$fieldsService->saveField($field, false)) {
144
                    throw new Exception('An error occurred while saving this Meta field.');
145
                }
146
147
                // Set sort order
148
                $field->sortOrder = ++$sortOrder;
149
150
                $fieldLayoutFields[] = $field;
151
            }
152
153
            // Revert to originals
154
            $contentService->contentTable = $originalContentTable;
155
            $contentService->fieldContext = $originalFieldContext;
156
            $contentService->fieldColumnPrefix = $originalFieldPrefix;
157
            $fieldsService->oldFieldColumnPrefix = $originalOldFieldPrefix;
158
159
            $fieldLayoutTab = new FieldLayoutTab();
160
            $fieldLayoutTab->name = 'Fields';
161
            $fieldLayoutTab->sortOrder = 1;
162
            $fieldLayoutTab->setFields($fieldLayoutFields);
163
164
            $fieldLayout = new FieldLayout();
165
            $fieldLayout->type = MetaElement::class;
166
            $fieldLayout->setTabs([$fieldLayoutTab]);
167
            $fieldLayout->setFields($fieldLayoutFields);
168
169
            $fieldsService->saveLayout($fieldLayout);
170
171
            // Update the element & record with our new field layout ID
172
            $metaField->setFieldLayout($fieldLayout);
173
            $metaField->fieldLayoutId = (int)$fieldLayout->id;
174
175
            // Save the fieldLayoutId via settings
176
            /** @var Field $fieldRecord */
177
            $fieldRecord = Field::findOne($metaField->id);
178
            $fieldRecord->settings = $metaField->getSettings();
179
180
            if ($fieldRecord->save(true, ['settings'])) {
181
                // Commit field changes
182
                $transaction->commit();
183
184
                return true;
185
            } else {
186
                $metaField->addError('settings', Craft::t('meta', 'Unable to save settings.'));
187
            }
188
        } catch (\Exception $e) {
189
            $transaction->rollback();
190
191
            throw $e;
192
        }
193
194
        $transaction->rollback();
195
196
        return false;
197
    }
198
199
    /**
200
     * Deletes an Meta field and content table.
201
     *
202
     * @param MetaField $field The Meta field.
203
     *
204
     * @throws \Exception
205
     * @return boolean Whether the field was deleted successfully.
206
     */
207
    public function beforeDelete(MetaField $field)
208
    {
209
210
        $transaction = Craft::$app->getDb()->beginTransaction();
211
        try {
212
            // Delete field layout
213
            Craft::$app->getFields()->deleteLayoutById($field->fieldLayoutId);
214
215
            // Get content table name
216
            $contentTableName = MetaPlugin::getInstance()->getField()->getContentTableName($field);
217
218
            // Drop the content table
219
            Craft::$app->getDb()->createCommand()->dropTableIfExists($contentTableName)->execute();
220
221
            // find any of the context fields
222
            $subFieldRecords = Field::find()
223
                ->andWhere(['like', 'context', MetaRecord::tableAlias() . ':%', false])
224
                ->all();
225
226
            // Delete them
227
            /** @var MetaRecord $subFieldRecord */
228
            foreach ($subFieldRecords as $subFieldRecord) {
229
                Craft::$app->getFields()->deleteFieldById($subFieldRecord->id);
230
            }
231
232
            // All good
233
            $transaction->commit();
234
235
            return true;
236
        } catch (\Exception $e) {
237
            // Revert
238
            $transaction->rollback();
239
240
            throw $e;
241
        }
242
    }
243
244
245
    /**
246
     * Validates a Meta field's settings.
247
     *
248
     * If the settings don’t validate, any validation errors will be stored on the settings model.
249
     *
250
     * @param MetaField $metaField The Meta field
251
     *
252
     * @return boolean Whether the settings validated.
253
     */
254
    public function validate(MetaField $metaField): bool
255
    {
256
        $validates = true;
257
258
        // Can't validate multiple new rows at once so we'll need to give these temporary context to avoid false unique
259
        // handle validation errors, and just validate those manually. Also apply the future fieldColumnPrefix so that
260
        // field handle validation takes its length into account.
261
        $contentService = Craft::$app->getContent();
262
        $originalFieldContext = $contentService->fieldContext;
263
264
        $contentService->fieldContext = StringHelper::randomString(10);
265
266
        /** @var Field $field */
267
        foreach ($metaField->getFields() as $field) {
268
            $field->validate();
269
            if ($field->hasErrors()) {
270
                $metaField->hasFieldErrors = true;
271
                $validates = false;
272
            }
273
        }
274
275
        $contentService->fieldContext = $originalFieldContext;
276
277
        return $validates;
278
    }
279
280
    /**
281
     * @inheritdoc
282
     */
283
    private function createContentTable($tableName)
284
    {
285
        $migration = new ContentTable([
286
            'tableName' => $tableName
287
        ]);
288
289
        ob_start();
290
        $migration->up();
291
        ob_end_clean();
292
    }
293
}
294