GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — integration (#2604)
by Brendan
05:13
created

contentBlueprintsDatasources   F

Complexity

Total Complexity 249

Size/Duplication

Total Lines 1806
Duplicated Lines 17.44 %

Coupling/Cohesion

Components 0
Dependencies 18

Importance

Changes 5
Bugs 0 Features 0
Metric Value
c 5
b 0
f 0
dl 315
loc 1806
rs 0.6314
wmc 249
lcom 0
cbo 18

17 Methods

Rating   Name   Duplication   Size   Complexity  
B __isValidURL() 0 29 5
A __viewIndex() 7 9 1
A __viewNew() 0 4 1
F __form() 140 1109 142
A setContext() 0 5 1
B __appendAuthorFilter() 0 24 2
A __viewEdit() 0 4 1
D __viewInfo() 8 96 17
A __actionIndex() 0 4 1
B __actionEdit() 34 37 5
F __formAction() 116 365 54
A __isValidPageString() 0 4 1
A injectAboutInformation() 10 10 3
B __injectVarList() 0 21 6
A __injectIncludedElements() 0 11 3
A injectFilters() 0 21 4
A __actionNew() 0 6 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like contentBlueprintsDatasources often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use contentBlueprintsDatasources, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
    /**
4
     * @package content
5
     */
6
7
    /**
8
     * The Datasource Editor page allows a developer to create new datasources
9
     * from the four Symphony types, Section, Authors, Navigation and Static XML
10
     */
11
    class contentBlueprintsDatasources extends ResourcesPage
12
    {
13
        public $_errors = array();
14
15
        /**
16
         * Given a `$url` and `$timeout`, this function will use the `Gateway`
17
         * class to determine that it is a valid URL and returns successfully
18
         * before the `$timeout`. If it does not, an error message will be
19
         * returned, otherwise true.
20
         *
21
         * @since Symphony 2.3
22
         * @param string $url
23
         * @param integer $timeout
24
         *  If not provided, this will default to 6 seconds
25
         * @param string $error
26
         *  If this function returns false, this variable will be populated with the
27
         *  error message.
28
         * @return array|boolean
29
         *  Returns an array with the 'data' if it is a valid URL, otherwise a string
30
         *  containing an error message.
31
         */
32
        public static function __isValidURL($url, $timeout = 6, &$error)
33
        {
34
            if (!filter_var($url, FILTER_VALIDATE_URL)) {
35
                $error = __('Invalid URL');
36
37
                return false;
38
            }
39
40
            // Check that URL was provided
41
            $gateway = new Gateway;
42
            $gateway->init($url);
43
            $gateway->setopt('TIMEOUT', $timeout);
44
            $data = $gateway->exec();
45
46
            $info = $gateway->getInfoLast();
47
48
            // 28 is CURLE_OPERATION_TIMEDOUT
49
            if ($info['curl_error'] === 28) {
50
                $error = __('Request timed out. %d second limit reached.', array($timeout));
51
52
                return false;
53
            } elseif ($data === false || $info['http_code'] !== 200) {
54
                $error = __('Failed to load URL, status code %d was returned.', array($info['http_code']));
55
56
                return false;
57
            }
58
59
            return array('data' => $data);
60
        }
61
62
        // Both the Edit and New pages need the same form
63
64 View Code Duplication
        public function __viewIndex($resource_type)
65
        {
66
            parent::__viewIndex(ResourceManager::RESOURCE_TYPE_DS);
67
68
            $this->setTitle(__('%1$s &ndash; %2$s', array(__('Data Sources'), __('Symphony'))));
69
            $this->appendSubheading(__('Data Sources'),
70
                Widget::Anchor(__('Create New'), Administration::instance()->getCurrentPageURL() . 'new/',
71
                    __('Create a new data source'), 'create button', null, array('accesskey' => 'c')));
72
        }
73
74
        public function __viewNew()
75
        {
76
            $this->__form();
77
        }
78
79
        public function __form()
0 ignored issues
show
Coding Style introduced by
__form uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
80
        {
81
            $formHasErrors = (is_array($this->_errors) && !empty($this->_errors));
82
83 View Code Duplication
            if ($formHasErrors) {
84
                $this->pageAlert(
85
                    __('An error occurred while processing this form. See below for details.'),
86
                    Alert::ERROR
87
                );
88
89
                // These alerts are only valid if the form doesn't have errors
90
            } elseif (isset($this->_context['flag'])) {
91
                $time = Widget::Time();
92
93
                switch ($this->_context['flag']) {
94
                    case 'saved':
95
                        $message = __('Data Source updated at %s.', array($time->generate()));
96
                        break;
97
                    case 'created':
98
                        $message = __('Data Source created at %s.', array($time->generate()));
99
                }
100
101
                $this->pageAlert(
102
                    $message
0 ignored issues
show
Bug introduced by
The variable $message does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
103
                    . ' <a href="' . SYMPHONY_URL . '/blueprints/datasources/new/" accesskey="c">'
104
                    . __('Create another?')
105
                    . '</a> <a href="' . SYMPHONY_URL . '/blueprints/datasources/" accesskey="a">'
106
                    . __('View all Data Sources')
107
                    . '</a>',
108
                    Alert::SUCCESS
109
                );
110
            }
111
112
            $providers = Symphony::ExtensionManager()->getProvidersOf(iProvider::DATASOURCE);
113
            $isEditing = $this->_context['action'] === 'edit';
114
            $about = $handle = null;
115
            $fields = array(
116
                'name' => null,
117
                'source' => null,
118
                'filter' => null,
119
                'required_url_param' => null,
120
                'negate_url_param' => null,
121
                'param' => null
122
            );
123
124
            if (isset($_POST['fields'])) {
125
                $fields = $_POST['fields'];
126
127
                if (
128
                    !in_array($fields['source'], array('authors', 'navigation', 'static_xml'))
129
                    && !empty($fields['filter']) && is_array($fields['filter'])
130
                ) {
131
                    $filters = array();
132
                    foreach ($fields['filter'] as $f) {
133
                        foreach ($f as $key => $val) {
134
                            $filters[$key] = $val;
135
                        }
136
                    }
137
138
                    $fields['filter'][$fields['source']] = $filters;
139
                }
140
141
                if (!isset($fields['xml_elements']) || !is_array($fields['xml_elements'])) {
142
                    $fields['xml_elements'] = array();
143
                }
144
            } elseif ($isEditing) {
145
                $handle = $this->_context['handle'];
146
                $existing = DatasourceManager::create($handle, array(), false);
147
                $order = isset($existing->dsParamORDER) ? $existing->dsParamORDER : 'asc';
0 ignored issues
show
Bug introduced by
The property dsParamORDER does not seem to exist in Datasource.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
148
149
                if (!$existing->allowEditorToParse()) {
150
                    redirect(SYMPHONY_URL . '/blueprints/datasources/info/' . $handle . '/');
151
                }
152
153
                $about = $existing->about();
154
                $fields['name'] = $about['name'];
155
156
                $fields['order'] = ($order === 'rand') ? 'random' : $order;
157
                $fields['param'] = isset($existing->dsParamPARAMOUTPUT) ? $existing->dsParamPARAMOUTPUT : null;
0 ignored issues
show
Bug introduced by
The property dsParamPARAMOUTPUT does not seem to exist in Datasource.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
158
                $fields['required_url_param'] = isset($existing->dsParamREQUIREDPARAM) ? trim($existing->dsParamREQUIREDPARAM) : null;
0 ignored issues
show
Bug introduced by
The property dsParamREQUIREDPARAM does not seem to exist in Datasource.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
159
                $fields['negate_url_param'] = isset($existing->dsParamNEGATEPARAM) ? trim($existing->dsParamNEGATEPARAM) : null;
0 ignored issues
show
Bug introduced by
The property dsParamNEGATEPARAM does not seem to exist in Datasource.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
160
161
                if (isset($existing->dsParamINCLUDEDELEMENTS) && is_array($existing->dsParamINCLUDEDELEMENTS)) {
162
                    $fields['xml_elements'] = $existing->dsParamINCLUDEDELEMENTS;
0 ignored issues
show
Bug introduced by
The property dsParamINCLUDEDELEMENTS does not seem to exist in Datasource.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
163
                } else {
164
                    $fields['xml_elements'] = array();
165
                }
166
167
                $fields['sort'] = isset($existing->dsParamSORT) ? $existing->dsParamSORT : null;
0 ignored issues
show
Bug introduced by
The property dsParamSORT does not seem to exist in Datasource.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
168
                $fields['paginate_results'] = isset($existing->dsParamPAGINATERESULTS) ? $existing->dsParamPAGINATERESULTS : 'yes';
0 ignored issues
show
Bug introduced by
The property dsParamPAGINATERESULTS does not seem to exist in Datasource.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
169
                $fields['page_number'] = isset($existing->dsParamSTARTPAGE) ? $existing->dsParamSTARTPAGE : '1';
0 ignored issues
show
Bug introduced by
The property dsParamSTARTPAGE does not seem to exist in Datasource.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
170
                $fields['group'] = isset($existing->dsParamGROUP) ? $existing->dsParamGROUP : null;
0 ignored issues
show
Bug introduced by
The property dsParamGROUP does not seem to exist in Datasource.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
171
                $fields['html_encode'] = isset($existing->dsParamHTMLENCODE) ? $existing->dsParamHTMLENCODE : 'no';
0 ignored issues
show
Bug introduced by
The property dsParamHTMLENCODE does not seem to exist in Datasource.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
172
                $fields['associated_entry_counts'] = isset($existing->dsParamASSOCIATEDENTRYCOUNTS) ? $existing->dsParamASSOCIATEDENTRYCOUNTS : 'no';
0 ignored issues
show
Bug introduced by
The property dsParamASSOCIATEDENTRYCOUNTS does not seem to exist in Datasource.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
173
                $fields['redirect_on_empty'] = isset($existing->dsParamREDIRECTONEMPTY) ? $existing->dsParamREDIRECTONEMPTY : 'no';
0 ignored issues
show
Bug introduced by
The property dsParamREDIRECTONEMPTY does not seem to exist in Datasource.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
174
                $fields['redirect_on_forbidden'] = isset($existing->dsParamREDIRECTONFORBIDDEN) ? $existing->dsParamREDIRECTONFORBIDDEN : 'no';
0 ignored issues
show
Bug introduced by
The property dsParamREDIRECTONFORBIDDEN does not seem to exist in Datasource.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
175
                $fields['redirect_on_required'] = isset($existing->dsParamREDIRECTONREQUIRED) ? $existing->dsParamREDIRECTONREQUIRED : 'no';
0 ignored issues
show
Bug introduced by
The property dsParamREDIRECTONREQUIRED does not seem to exist in Datasource.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
176
177
                if (!isset($existing->dsParamFILTERS) || !is_array($existing->dsParamFILTERS)) {
178
                    $existing->dsParamFILTERS = array();
0 ignored issues
show
Bug introduced by
The property dsParamFILTERS does not seem to exist in Datasource.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
179
                }
180
181
                if (!empty($existing->dsParamFILTERS)) {
182
                    $existing->dsParamFILTERS = array_map('stripslashes', $existing->dsParamFILTERS);
183
                }
184
185
                $fields['source'] = $existing->getSource();
186
187
                $provided = false;
188
189 View Code Duplication
                if (!empty($providers)) {
190
                    foreach ($providers as $providerClass => $provider) {
191
                        if ($fields['source'] === call_user_func(array($providerClass, 'getClass'))) {
192
                            $fields = array_merge($fields, $existing->settings());
0 ignored issues
show
Bug introduced by
The method settings() does not seem to exist on object<Datasource>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
193
                            $provided = true;
194
                            break;
195
                        }
196
                    }
197
                }
198
199
                if ($provided === false) {
200
                    switch ($fields['source']) {
201
                        case 'authors':
202
                            $fields['filter']['author'] = $existing->dsParamFILTERS;
203
                            break;
204
                        case 'navigation':
205
                            $fields['filter']['navigation'] = $existing->dsParamFILTERS;
206
                            break;
207
                        case 'static_xml':
208
                            // Symphony 2.3+
209
                            if (isset($existing->dsParamSTATIC)) {
210
                                $fields['static_xml'] = trim($existing->dsParamSTATIC);
0 ignored issues
show
Bug introduced by
The property dsParamSTATIC does not seem to exist in Datasource.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
211
212
                                // Handle Symphony 2.2.2 to 2.3 DS's
213
                            } elseif (isset($existing->dsSTATIC)) {
214
                                $fields['static_xml'] = trim($existing->dsSTATIC);
0 ignored issues
show
Bug introduced by
The property dsSTATIC does not seem to exist in Datasource.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
215
216
                                // Handle pre Symphony 2.2.1 Static DS's
217
                            } else {
218
                                $fields['static_xml'] = trim($existing->grab());
0 ignored issues
show
Bug introduced by
The method grab() does not seem to exist on object<Datasource>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
219
                            }
220
                            break;
221
                        default:
222
                            $fields['filter'][$fields['source']] = $existing->dsParamFILTERS;
223
                            $fields['max_records'] = $existing->dsParamLIMIT;
0 ignored issues
show
Bug introduced by
The property dsParamLIMIT does not seem to exist in Datasource.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
224
                            break;
225
                    }
226
                }
227
            } else {
228
                $fields['max_records'] = '20';
229
                $fields['page_number'] = '1';
230
                $fields['order'] = 'desc';
231
            }
232
233
            // Handle name on edited changes, or from reading an edited datasource
234 View Code Duplication
            if (isset($about['name'])) {
235
                $name = $about['name'];
236
            } elseif (isset($fields['name'])) {
237
                $name = $fields['name'];
238
            }
239
240
            $this->setPageType('form');
241
            $this->setTitle(__(($isEditing ? '%1$s &ndash; %2$s &ndash; %3$s' : '%2$s &ndash; %3$s'),
242
                array($name, __('Data Sources'), __('Symphony'))));
0 ignored issues
show
Bug introduced by
The variable $name does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
243
            $this->appendSubheading(($isEditing ? $name : __('Untitled')));
244
            $this->insertBreadcrumbs(array(
245
                Widget::Anchor(__('Data Sources'), SYMPHONY_URL . '/blueprints/datasources/'),
246
            ));
247
248
            // Sources
249
            $sources = new XMLElement('div', null, array('class' => 'apply actions'));
250
            $div = new XMLElement('div');
251
            $label = Widget::Label(__('Source'), null, 'apply-label-left');
252
            $sources->appendChild($label);
253
            $sources->appendChild($div);
254
255
            $sections = SectionManager::fetch(null, 'ASC', 'name');
256
257
            if (!is_array($sections)) {
258
                $sections = array();
259
            }
260
261
            $field_groups = array();
262
263 View Code Duplication
            foreach ($sections as $section) {
264
                $field_groups[$section->get('id')] = array('fields' => $section->fetchFields(), 'section' => $section);
265
            }
266
267
            $options = array(
268
                array(
269
                    'label' => __('System'),
270
                    'data-label' => 'system',
271
                    'options' => array(
272
                        array(
273
                            'authors',
274
                            ($fields['source'] === 'authors'),
275
                            __('Authors'),
276
                            null,
277
                            null,
278
                            array('data-context' => 'authors')
279
                        ),
280
                        array(
281
                            'navigation',
282
                            ($fields['source'] === 'navigation'),
283
                            __('Navigation'),
284
                            null,
285
                            null,
286
                            array('data-context' => 'navigation')
287
                        ),
288
                    )
289
                ),
290
                array(
291
                    'label' => __('Custom XML'),
292
                    'data-label' => 'custom-xml',
293
                    'options' => array(
294
                        array(
295
                            'static_xml',
296
                            ($fields['source'] === 'static_xml'),
297
                            __('Static XML'),
298
                            null,
299
                            null,
300
                            array('data-context' => 'static-xml')
301
                        ),
302
                    )
303
                ),
304
            );
305
306
            // Loop over the datasource providers
307
            if (!empty($providers)) {
308
                $p = array('label' => __('From extensions'), 'data-label' => 'from_extensions', 'options' => array());
309
310
                foreach ($providers as $providerClass => $provider) {
311
                    $p['options'][] = array(
312
                        $providerClass,
313
                        ($fields['source'] === $providerClass),
314
                        $provider,
315
                        null,
316
                        null,
317
                        array('data-context' => Lang::createHandle($provider))
318
                    );
319
                }
320
321
                $options[] = $p;
322
            }
323
324
            // Add Sections
325
            if (is_array($sections) && !empty($sections)) {
326
                array_unshift($options,
327
                    array('label' => __('Sections'), 'data-label' => 'sections', 'options' => array()));
328
329 View Code Duplication
                foreach ($sections as $s) {
330
                    $options[0]['options'][] = array(
331
                        $s->get('id'),
332
                        ($fields['source'] === $s->get('id')),
333
                        General::sanitize($s->get('name'))
334
                    );
335
                }
336
            }
337
338
            $div->appendChild(Widget::Select('source', $options, array('id' => 'ds-context')));
339
            $this->Context->prependChild($sources);
340
341
            $this->Form->appendChild(
342
                Widget::Input('fields[source]', null, 'hidden', array('id' => 'ds-source'))
343
            );
344
345
            // Name
346
            $fieldset = new XMLElement('fieldset');
347
            $fieldset->setAttribute('class', 'settings');
348
            $fieldset->appendChild(new XMLElement('legend', __('Essentials')));
349
350
            $group = new XMLElement('div');
351
352
            $label = Widget::Label(__('Name'));
353
            $label->appendChild(Widget::Input('fields[name]', General::sanitize($fields['name'])));
354
355
            if (isset($this->_errors['name'])) {
356
                $group->appendChild(Widget::Error($label, $this->_errors['name']));
357
            } else {
358
                $group->appendChild($label);
359
            }
360
361
            $fieldset->appendChild($group);
362
            $this->Form->appendChild($fieldset);
363
364
            // Conditions
365
            $fieldset = new XMLElement('fieldset');
366
            $this->setContext($fieldset, array('sections', 'system', 'custom-xml'));
367
            $fieldset->appendChild(new XMLElement('legend', __('Conditions')));
368
            $p = new XMLElement('p', __('Leaving these fields empty will always execute the data source.'));
369
            $p->setAttribute('class', 'help');
370
            $fieldset->appendChild($p);
371
372
            $group = new XMLElement('div');
373
            $group->setAttribute('class', 'two columns');
374
375
            $label = Widget::Label(__('Required Parameter'));
376
            $label->setAttribute('class', 'column ds-param');
377
            $label->appendChild(new XMLElement('i', __('Optional')));
378
            $input = Widget::Input('fields[required_url_param]', trim($fields['required_url_param']), 'text', array(
379
                'placeholder' => __('$param'),
380
                'data-search-types' => 'parameters',
381
                'data-trigger' => '$'
382
            ));
383
            $label->appendChild($input);
384
            $group->appendChild($label);
385
386
            $label = Widget::Label(__('Forbidden Parameter'));
387
            $label->setAttribute('class', 'column ds-param');
388
            $label->appendChild(new XMLElement('i', __('Optional')));
389
            $input = Widget::Input('fields[negate_url_param]', trim($fields['negate_url_param']), 'text', array(
390
                'placeholder' => __('$param'),
391
                'data-search-types' => 'parameters',
392
                'data-trigger' => '$'
393
            ));
394
            $label->appendChild($input);
395
            $group->appendChild($label);
396
397
            $fieldset->appendChild($group);
398
399
            $group = new XMLElement('div');
400
            $group->setAttribute('class', 'two columns ds-param');
401
402
            $label = Widget::Checkbox('fields[redirect_on_required]', $fields['redirect_on_required'],
403
                __('Redirect to 404 page when the required parameter is not present'));
404
            $label->setAttribute('class', 'column');
405
            $group->appendChild($label);
406
407
            $label = Widget::Checkbox('fields[redirect_on_forbidden]', $fields['redirect_on_forbidden'],
408
                __('Redirect to 404 page when the forbidden parameter is present'));
409
            $label->setAttribute('class', 'column');
410
            $group->appendChild($label);
411
412
            $fieldset->appendChild($group);
413
414
            $label = Widget::Checkbox('fields[redirect_on_empty]', $fields['redirect_on_empty'],
415
                __('Redirect to 404 page when no results are found'));
416
            $label->setAttribute('class', 'column');
417
            $fieldset->appendChild($label);
418
419
            $this->Form->appendChild($fieldset);
420
421
            // Filters
422
            $fieldset = new XMLElement('fieldset');
423
            $this->setContext($fieldset, array('sections', 'system'));
424
            $fieldset->appendChild(new XMLElement('legend', __('Filters')));
425
            $p = new XMLElement('p',
426
                __('Use %s syntax to filter by page parameters. A default value can be set using %s.', array(
427
                    '<code>{' . __('$param') . '}</code>',
428
                    '<code>{' . __('$param:default') . '}</code>'
429
                ))
430
            );
431
            $p->setAttribute('class', 'help');
432
            $fieldset->appendChild($p);
433
434
            foreach ($field_groups as $section_id => $section_data) {
435
                $div = new XMLElement('div');
436
                $div->setAttribute('class', 'contextual frame filters-duplicator');
437
                $div->setAttribute('data-context', 'section-' . $section_id);
438
                $div->setAttribute('data-interactive', 'data-interactive');
439
440
                $ol = new XMLElement('ol');
441
                $ol->setAttribute('class', 'suggestable');
442
                $ol->setAttribute('data-interactive', 'data-interactive');
443
                $ol->setAttribute('data-add', __('Add filter'));
444
                $ol->setAttribute('data-remove', __('Remove filter'));
445
446
                // Add system:id filter
447 View Code Duplication
                if (
448
                    isset($fields['filter'][$section_id]['system:id'])
449
                    || isset($fields['filter'][$section_id]['id'])
450
                ) {
451
                    $id = isset($fields['filter'][$section_id]['system:id'])
452
                        ? $fields['filter'][$section_id]['system:id']
453
                        : $fields['filter'][$section_id]['id'];
454
455
                    $li = new XMLElement('li');
456
                    $li->setAttribute('class', 'unique');
457
                    $li->setAttribute('data-type', 'system:id');
458
                    $li->appendChild(new XMLElement('header', '<h4>' . __('System ID') . '</h4>'));
459
                    $label = Widget::Label(__('Value'));
460
                    $input = Widget::Input('fields[filter][' . $section_id . '][system:id]', General::sanitize($id));
461
                    $input->setAttribute('data-search-types', 'parameters');
462
                    $input->setAttribute('data-trigger', '{$');
463
                    $label->appendChild($input);
464
                    $li->appendChild($label);
465
                    $ol->appendChild($li);
466
                }
467
468
                $li = new XMLElement('li');
469
                $li->setAttribute('class', 'unique template');
470
                $li->setAttribute('data-type', 'system:id');
471
                $li->appendChild(new XMLElement('header', '<h4>' . __('System ID') . '</h4>'));
472
                $label = Widget::Label(__('Value'));
473
                $input = Widget::Input('fields[filter][' . $section_id . '][system:id]', General::sanitize($id));
0 ignored issues
show
Bug introduced by
The variable $id does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
474
                $input->setAttribute('data-search-types', 'parameters');
475
                $input->setAttribute('data-trigger', '{$');
476
                $label->appendChild($input);
477
                $li->appendChild($label);
478
                $ol->appendChild($li);
479
480
                // Add system:date filter
481 View Code Duplication
                if (
482
                    isset($fields['filter'][$section_id]['system:creation-date'])
483
                    || isset($fields['filter'][$section_id]['system:date'])
484
                ) {
485
                    $creation_date = isset($fields['filter'][$section_id]['system:creation-date'])
486
                        ? $fields['filter'][$section_id]['system:creation-date']
487
                        : $fields['filter'][$section_id]['system:date'];
488
489
                    $li = new XMLElement('li');
490
                    $li->setAttribute('class', 'unique');
491
                    $li->setAttribute('data-type', 'system:creation-date');
492
                    $li->appendChild(new XMLElement('header', '<h4>' . __('System Creation Date') . '</h4>'));
493
                    $label = Widget::Label(__('Value'));
494
                    $input = Widget::Input('fields[filter][' . $section_id . '][system:creation-date]',
495
                        General::sanitize($creation_date));
496
                    $input->setAttribute('data-search-types', 'parameters');
497
                    $input->setAttribute('data-trigger', '{$');
498
                    $label->appendChild($input);
499
                    $li->appendChild($label);
500
                    $ol->appendChild($li);
501
                }
502
503
                $li = new XMLElement('li');
504
                $li->setAttribute('class', 'unique template');
505
                $li->setAttribute('data-type', 'system:creation-date');
506
                $li->appendChild(new XMLElement('header', '<h4>' . __('System Creation Date') . '</h4>'));
507
                $label = Widget::Label(__('Value'));
508
                $input = Widget::Input('fields[filter][' . $section_id . '][system:creation-date]');
509
                $input->setAttribute('data-search-types', 'parameters');
510
                $input->setAttribute('data-trigger', '{$');
511
                $label->appendChild($input);
512
                $li->appendChild($label);
513
                $ol->appendChild($li);
514
515
                if (isset($fields['filter'][$section_id]['system:modification-date'])) {
516
                    $li = new XMLElement('li');
517
                    $li->setAttribute('class', 'unique');
518
                    $li->setAttribute('data-type', 'system:modification-date');
519
                    $li->appendChild(new XMLElement('header', '<h4>' . __('System Modification Date') . '</h4>'));
520
                    $label = Widget::Label(__('Value'));
521
                    $input = Widget::Input('fields[filter][' . $section_id . '][system:modification-date]',
522
                        General::sanitize($fields['filter'][$section_id]['system:modification-date']));
523
                    $input->setAttribute('data-search-types', 'parameters');
524
                    $input->setAttribute('data-trigger', '{$');
525
                    $label->appendChild($input);
526
                    $li->appendChild($label);
527
                    $ol->appendChild($li);
528
                }
529
530
                $li = new XMLElement('li');
531
                $li->setAttribute('class', 'unique template');
532
                $li->setAttribute('data-type', 'system:modification-date');
533
                $li->appendChild(new XMLElement('header', '<h4>' . __('System Modification Date') . '</h4>'));
534
                $label = Widget::Label(__('Value'));
535
                $input = Widget::Input('fields[filter][' . $section_id . '][system:modification-date]');
536
                $input->setAttribute('data-search-types', 'parameters');
537
                $input->setAttribute('data-trigger', '{$');
538
                $label->appendChild($input);
539
                $li->appendChild($label);
540
                $ol->appendChild($li);
541
542
                if (is_array($section_data['fields']) && !empty($section_data['fields'])) {
543
                    foreach ($section_data['fields'] as $field) {
544
                        if (!$field->canFilter()) {
545
                            continue;
546
                        }
547
548
                        if (isset($fields['filter'][$section_id], $fields['filter'][$section_id][$field->get('id')])) {
549
                            $wrapper = new XMLElement('li');
550
                            $wrapper->setAttribute('class', 'unique');
551
                            $wrapper->setAttribute('data-type', $field->get('element_name'));
552
                            $errors = isset($this->_errors[$field->get('id')])
553
                                ? $this->_errors[$field->get('id')]
554
                                : array();
555
556
                            $field->displayDatasourceFilterPanel($wrapper,
557
                                $fields['filter'][$section_id][$field->get('id')], $errors, $section_id);
558
                            $ol->appendChild($wrapper);
559
                        }
560
561
                        $wrapper = new XMLElement('li');
562
                        $wrapper->setAttribute('class', 'unique template');
563
                        $wrapper->setAttribute('data-type', $field->get('element_name'));
564
                        $field->displayDatasourceFilterPanel($wrapper, null, null, $section_id);
565
                        $ol->appendChild($wrapper);
566
                    }
567
                }
568
569
                $div->appendChild($ol);
570
571
                $fieldset->appendChild($div);
572
            }
573
574
            $div = new XMLElement('div');
575
            $div->setAttribute('class', 'contextual frame filters-duplicator');
576
            $div->setAttribute('data-context', 'authors');
577
            $div->setAttribute('data-interactive', 'data-interactive');
578
579
            $ol = new XMLElement('ol');
580
            $ol->setAttribute('class', 'suggestable');
581
            $ol->setAttribute('data-interactive', 'data-interactive');
582
            $ol->setAttribute('data-add', __('Add filter'));
583
            $ol->setAttribute('data-remove', __('Remove filter'));
584
585
            if (!isset($fields['filter']['author'])) {
586
                $fields['filter']['author'] = array(
587
                    'id' => null,
588
                    'username' => null,
589
                    'first_name' => null,
590
                    'last_name' => null,
591
                    'email' => null,
592
                    'user_type' => null
593
                );
594
            }
595
596
            $this->__appendAuthorFilter($ol, __('ID'), 'id', $fields['filter']['author']['id'],
597
                (!isset($fields['filter']['author']['id'])));
598
            $this->__appendAuthorFilter($ol, __('Username'), 'username', $fields['filter']['author']['username'],
599
                (!isset($fields['filter']['author']['username'])));
600
            $this->__appendAuthorFilter($ol, __('First Name'), 'first_name', $fields['filter']['author']['first_name'],
601
                (!isset($fields['filter']['author']['first_name'])));
602
            $this->__appendAuthorFilter($ol, __('Last Name'), 'last_name', $fields['filter']['author']['last_name'],
603
                (!isset($fields['filter']['author']['last_name'])));
604
            $this->__appendAuthorFilter($ol, __('Email'), 'email', $fields['filter']['author']['email'],
605
                (!isset($fields['filter']['author']['email'])));
606
            $this->__appendAuthorFilter($ol, __('User Type'), 'user_type', $fields['filter']['author']['user_type'],
607
                (!isset($fields['filter']['author']['user_type'])));
608
609
            $div->appendChild($ol);
610
611
            $fieldset->appendChild($div);
612
613
            $div = new XMLElement('div');
614
            $div->setAttribute('class', 'contextual frame filters-duplicator');
615
            $div->setAttribute('data-context', 'navigation');
616
            $div->setAttribute('data-interactive', 'data-interactive');
617
618
            $ol = new XMLElement('ol');
619
            $ol->setAttribute('class', 'suggestable');
620
            $ol->setAttribute('data-interactive', 'data-interactive');
621
            $ol->setAttribute('data-add', __('Add filter'));
622
            $ol->setAttribute('data-remove', __('Remove filter'));
623
624
            $ul = new XMLElement('ul');
625
            $ul->setAttribute('class', 'tags');
626
            $ul->setAttribute('data-interactive', 'data-interactive');
627
628
            $pages = PageManager::fetch(false, array('*'), array(), 'title ASC');
629
630
            foreach ($pages as $page) {
0 ignored issues
show
Bug introduced by
The expression $pages of type array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
631
                $ul->appendChild(new XMLElement('li',
632
                    preg_replace('/\/{2,}/i', '/', '/' . $page['path'] . '/' . $page['handle'])));
633
            }
634
635 View Code Duplication
            if (isset($fields['filter']['navigation']['parent'])) {
636
                $li = new XMLElement('li');
637
                $li->setAttribute('class', 'unique');
638
                $li->setAttribute('data-type', 'parent');
639
                $li->appendChild(new XMLElement('header', '<h4>' . __('Parent Page') . '</h4>'));
640
                $label = Widget::Label(__('Value'));
641
                $label->appendChild(Widget::Input('fields[filter][navigation][parent]',
642
                    General::sanitize($fields['filter']['navigation']['parent'])));
643
                $li->appendChild($label);
644
                $li->appendChild($ul);
645
                $ol->appendChild($li);
646
            }
647
648
            $li = new XMLElement('li');
649
            $li->setAttribute('class', 'unique template');
650
            $li->setAttribute('data-type', 'parent');
651
            $li->appendChild(new XMLElement('header', '<h4>' . __('Parent Page') . '</h4>'));
652
            $label = Widget::Label(__('Value'));
653
            $label->appendChild(Widget::Input('fields[filter][navigation][parent]'));
654
            $li->appendChild($label);
655
            $li->appendChild($ul);
656
            $ol->appendChild($li);
657
658
            $ul = new XMLElement('ul');
659
            $ul->setAttribute('class', 'tags');
660
            $ul->setAttribute('data-interactive', 'data-interactive');
661
662
            if ($types = PageManager::fetchAvailablePageTypes()) {
663
                foreach ($types as $type) {
664
                    $ul->appendChild(new XMLElement('li', $type));
665
                }
666
            }
667
668 View Code Duplication
            if (isset($fields['filter']['navigation']['type'])) {
669
                $li = new XMLElement('li');
670
                $li->setAttribute('class', 'unique');
671
                $li->setAttribute('data-type', 'type');
672
                $li->appendChild(new XMLElement('header', '<h4>' . __('Page Type') . '</h4>'));
673
                $label = Widget::Label(__('Value'));
674
                $label->appendChild(Widget::Input('fields[filter][navigation][type]',
675
                    General::sanitize($fields['filter']['navigation']['type'])));
676
                $li->appendChild($label);
677
                $li->appendChild($ul);
678
                $ol->appendChild($li);
679
            }
680
681
            $li = new XMLElement('li');
682
            $li->setAttribute('class', 'unique template');
683
            $li->appendChild(new XMLElement('header', '<h4>' . __('Page Type') . '</h4>'));
684
            $li->setAttribute('data-type', 'type');
685
            $label = Widget::Label(__('Value'));
686
            $label->appendChild(Widget::Input('fields[filter][navigation][type]'));
687
            $li->appendChild($label);
688
            $li->appendChild($ul);
689
            $ol->appendChild($li);
690
691
            $div->appendChild($ol);
692
693
            $fieldset->appendChild($div);
694
            $this->Form->appendChild($fieldset);
695
696
            // Sorting
697
            $fieldset = new XMLElement('fieldset');
698
            $this->setContext($fieldset, array('sections', 'system'));
699
            $fieldset->appendChild(new XMLElement('legend', __('Sorting')));
700
701
            $p = new XMLElement('p',
702
                __('Use %s syntax to order by page parameters.', array(
703
                    '<code>{' . __('$param') . '}</code>'
704
                ))
705
            );
706
            $p->setAttribute('class', 'help');
707
            $fieldset->appendChild($p);
708
709
            $div = new XMLElement('div');
710
711
            $label = Widget::Label(__('Sort By'));
712
713
            $options = array(
714
                array(
715
                    'label' => __('Authors'),
716
                    'data-label' => 'authors',
717
                    'options' => array(
718
                        array('id', ($fields['source'] === 'authors' && $fields['sort'] === 'id'), __('Author ID')),
719
                        array(
720
                            'username',
721
                            ($fields['source'] === 'authors' && $fields['sort'] === 'username'),
722
                            __('Username')
723
                        ),
724
                        array(
725
                            'first-name',
726
                            ($fields['source'] === 'authors' && $fields['sort'] === 'first-name'),
727
                            __('First Name')
728
                        ),
729
                        array(
730
                            'last-name',
731
                            ($fields['source'] === 'authors' && $fields['sort'] === 'last-name'),
732
                            __('Last Name')
733
                        ),
734
                        array('email', ($fields['source'] === 'authors' && $fields['sort'] === 'email'), __('Email')),
735
                        array(
736
                            'status',
737
                            ($fields['source'] === 'authors' && $fields['sort'] === 'status'),
738
                            __('Status')
739
                        ),
740
                    )
741
                ),
742
743
                array(
744
                    'label' => __('Navigation'),
745
                    'data-label' => 'navigation',
746
                    'options' => array(
747
                        array('id', ($fields['source'] === 'navigation' && $fields['sort'] === 'id'), __('Page ID')),
748
                        array(
749
                            'handle',
750
                            ($fields['source'] === 'navigation' && $fields['sort'] === 'handle'),
751
                            __('Handle')
752
                        ),
753
                        array(
754
                            'sortorder',
755
                            ($fields['source'] === 'navigation' && $fields['sort'] === 'sortorder'),
756
                            __('Sort Order')
757
                        ),
758
                    )
759
                ),
760
            );
761
762
            foreach ($field_groups as $section_id => $section_data) {
763
                $optgroup = array(
764
                    'label' => General::sanitize($section_data['section']->get('name')),
765
                    'data-label' => 'section-' . $section_data['section']->get('id'),
766
                    'options' => array(
767
                        array(
768
                            'system:id',
769
                            ($fields['source'] === $section_id && $fields['sort'] === 'system:id'),
770
                            __('System ID')
771
                        ),
772
                        array(
773
                            'system:creation-date',
774
                            ($fields['source'] === $section_id && ($fields['sort'] === 'system:creation-date' || $fields['sort'] === 'system:date')),
775
                            __('System Creation Date')
776
                        ),
777
                        array(
778
                            'system:modification-date',
779
                            ($fields['source'] === $section_id && $fields['sort'] === 'system:modification-date'),
780
                            __('System Modification Date')
781
                        ),
782
                    )
783
                );
784
785 View Code Duplication
                if (is_array($section_data['fields']) && !empty($section_data['fields'])) {
786
                    foreach ($section_data['fields'] as $input) {
787
                        if (!$input->isSortable()) {
788
                            continue;
789
                        }
790
791
                        $optgroup['options'][] = array(
792
                            $input->get('element_name'),
793
                            ($fields['source'] === $section_id && $input->get('element_name') === $fields['sort']),
794
                            $input->get('label')
795
                        );
796
                    }
797
                }
798
799
                $options[] = $optgroup;
800
            }
801
802
            $label->appendChild(Widget::Select('fields[sort]', $options));
803
            $div->appendChild($label);
804
805
            $label = Widget::Label(__('Sort Order'));
806
            $label->setAttribute('class', 'ds-param');
807
808
            $input = Widget::Input('fields[order]', $fields['order'], 'text', array(
809
                'placeholder' => __('{$param}'),
810
                'data-search-types' => 'parameters',
811
                'data-trigger' => '{$'
812
            ));
813
            $label->appendChild($input);
814
            $div->appendChild($label);
815
816
            $orders = new XMLElement('ul');
817
            $orders->setAttribute('class', 'tags singular');
818
            $orders->setAttribute('data-interactive', 'data-interactive');
819
            $orders->appendChild(new XMLElement('li', 'asc'));
820
            $orders->appendChild(new XMLElement('li', 'desc'));
821
            $orders->appendChild(new XMLElement('li', 'random'));
822
            $div->appendChild($orders);
823
824
            $fieldset->appendChild($div);
825
            $this->Form->appendChild($fieldset);
826
827
            // Grouping
828
            $fieldset = new XMLElement('fieldset');
829
            $this->setContext($fieldset, array('sections', 'authors'));
830
            $fieldset->appendChild(new XMLElement('legend', __('Grouping')));
831
832
            $label = Widget::Label(__('Group By'));
833
            $options = array(
834
                array('', null, __('None')),
835
            );
836
837
            foreach ($field_groups as $section_id => $section_data) {
838
                $optgroup = array(
839
                    'label' => $section_data['section']->get('name'),
840
                    'data-label' => 'section-' . $section_data['section']->get('id'),
841
                    'options' => array()
842
                );
843
844 View Code Duplication
                if (is_array($section_data['fields']) && !empty($section_data['fields'])) {
845
                    foreach ($section_data['fields'] as $input) {
846
                        if (!$input->allowDatasourceOutputGrouping()) {
847
                            continue;
848
                        }
849
850
                        $optgroup['options'][] = array(
851
                            $input->get('id'),
852
                            ($fields['source'] === $section_id && $fields['group'] === $input->get('id')),
853
                            $input->get('label')
854
                        );
855
                    }
856
                }
857
858
                $options[] = $optgroup;
859
            }
860
861
            $label->appendChild(Widget::Select('fields[group]', $options));
862
            $fieldset->appendChild($label);
863
864
            $this->Form->appendChild($fieldset);
865
866
            // Pagination
867
            $fieldset = new XMLElement('fieldset');
868
            $this->setContext($fieldset, array('sections'));
869
            $fieldset->appendChild(new XMLElement('legend', __('Pagination')));
870
871
            $p = new XMLElement('p',
872
                __('Use %s syntax to limit by page parameters.', array(
873
                    '<code>{' . __('$param') . '}</code>'
874
                ))
875
            );
876
            $p->setAttribute('class', 'help');
877
            $fieldset->appendChild($p);
878
879
            $group = new XMLElement('div');
880
            $group->setAttribute('class', 'two columns pagination');
881
882
            $label = Widget::Label(__('Entries per Page'));
883
            $label->setAttribute('class', 'column ds-param');
884
            $input = Widget::Input('fields[max_records]', isset($fields['max_records']) ? $fields['max_records'] : '10',
885
                'text', array(
886
                    'placeholder' => __('{$param}'),
887
                    'data-search-types' => 'parameters',
888
                    'data-trigger' => '{$'
889
                ));
890
            $label->appendChild($input);
891
            $group->appendChild($label);
892
893
            $label = Widget::Label(__('Page Number'));
894
            $label->setAttribute('class', 'column ds-param');
895
            $input = Widget::Input('fields[page_number]', $fields['page_number'], 'text', array(
896
                'placeholder' => __('{$param}'),
897
                'data-search-types' => 'parameters',
898
                'data-trigger' => '{$'
899
            ));
900
            $label->appendChild($input);
901
            $group->appendChild($label);
902
903
            $fieldset->appendChild($group);
904
905
            $label = Widget::Checkbox('fields[paginate_results]', $fields['paginate_results'], __('Enable pagination'));
906
            $fieldset->appendChild($label);
907
            $this->Form->appendChild($fieldset);
908
909
            // Content
910
            $fieldset = new XMLElement('fieldset');
911
            $this->setContext($fieldset, array('sections', 'authors'));
912
            $fieldset->appendChild(new XMLElement('legend', __('Content')));
913
914
            // XML
915
            $group = new XMLElement('div', null, array('class' => 'two columns'));
916
917
            $label = Widget::Label(__('Included Elements'));
918
            $label->setAttribute('class', 'column');
919
920
            $options = array(
921
                array(
922
                    'label' => __('Authors'),
923
                    'data-label' => 'authors',
924
                    'options' => array(
925
                        array(
926
                            'username',
927
                            ($fields['source'] === 'authors' && in_array('username', $fields['xml_elements'])),
928
                            'username'
929
                        ),
930
                        array(
931
                            'name',
932
                            ($fields['source'] === 'authors' && in_array('name', $fields['xml_elements'])),
933
                            'name'
934
                        ),
935
                        array(
936
                            'email',
937
                            ($fields['source'] === 'authors' && in_array('email', $fields['xml_elements'])),
938
                            'email'
939
                        ),
940
                        array(
941
                            'author-token',
942
                            ($fields['source'] === 'authors' && in_array('author-token', $fields['xml_elements'])),
943
                            'author-token'
944
                        ),
945
                        array(
946
                            'default-area',
947
                            ($fields['source'] === 'authors' && in_array('default-area', $fields['xml_elements'])),
948
                            'default-area'
949
                        ),
950
                    )
951
                ),
952
            );
953
954
            foreach ($field_groups as $section_id => $section_data) {
955
                $optgroup = array(
956
                    'label' => General::sanitize($section_data['section']->get('name')),
957
                    'data-label' => 'section-' . $section_data['section']->get('id'),
958
                    'options' => array(
959
                        array(
960
                            'system:pagination',
961
                            ($fields['source'] === $section_id && in_array('system:pagination',
962
                                    $fields['xml_elements'])),
963
                            'system: pagination'
964
                        ),
965
                        array(
966
                            'system:date',
967
                            ($fields['source'] === $section_id && in_array('system:date', $fields['xml_elements'])),
968
                            'system: date'
969
                        )
970
                    )
971
                );
972
973
                if (is_array($section_data['fields']) && !empty($section_data['fields'])) {
974
                    foreach ($section_data['fields'] as $field) {
975
                        $elements = $field->fetchIncludableElements();
976
977
                        if (is_array($elements) && !empty($elements)) {
978
                            foreach ($elements as $name) {
979
                                $selected = false;
980
981
                                if ($fields['source'] === $section_id && in_array($name, $fields['xml_elements'])) {
982
                                    $selected = true;
983
                                }
984
985
                                $optgroup['options'][] = array($name, $selected, $name);
986
                            }
987
                        }
988
                    }
989
                }
990
991
                $options[] = $optgroup;
992
            }
993
994
            $label->appendChild(Widget::Select('fields[xml_elements][]', $options, array('multiple' => 'multiple')));
995
            $group->appendChild($label);
996
997
            // Support multiple parameters
998
            if (!isset($fields['param'])) {
999
                $fields['param'] = array();
1000
            } elseif (!is_array($fields['param'])) {
1001
                $fields['param'] = array($fields['param']);
1002
            }
1003
1004
            $label = Widget::Label(__('Parameters'));
1005
            $label->setAttribute('class', 'column');
1006
            $prefix = '$ds-' . (isset($this->_context['handle']) ? Lang::createHandle($fields['name']) : __('untitled')) . '.';
1007
1008
            $options = array(
1009
                array('label' => __('Authors'), 'data-label' => 'authors', 'options' => array())
1010
            );
1011
1012
            foreach (array('id', 'username', 'name', 'email', 'user_type') as $p) {
1013
                $options[0]['options'][] = array(
1014
                    $p,
1015
                    ($fields['source'] === 'authors' && in_array($p, $fields['param'])),
1016
                    $prefix . $p,
1017
                    null,
1018
                    null,
1019
                    array(
1020
                        'data-handle' => $p
1021
                    )
1022
                );
1023
            }
1024
1025
            foreach ($field_groups as $section_id => $section_data) {
1026
                $optgroup = array(
1027
                    'label' => $section_data['section']->get('name'),
1028
                    'data-label' => 'section-' . $section_data['section']->get('id'),
1029
                    'options' => array()
1030
                );
1031
1032
                foreach (array('id', 'creation-date', 'modification-date', 'author') as $p) {
1033
                    $option = array(
1034
                        'system:' . $p,
1035
                        ($fields['source'] === $section_id && in_array('system:' . $p, $fields['param'])),
1036
                        $prefix . 'system-' . $p,
1037
                        null,
1038
                        null,
1039
                        array(
1040
                            'data-handle' => 'system-' . $p
1041
                        )
1042
                    );
1043
1044
                    // Handle 'system:date' as an output paramater (backwards compatibility)
1045
                    if ($p === 'creation-date') {
1046
                        if ($fields['source'] === $section_id && in_array('system:date', $fields['param'])) {
1047
                            $option[1] = true;
1048
                        }
1049
                    }
1050
1051
                    $optgroup['options'][] = $option;
1052
                }
1053
1054
                if (is_array($section_data['fields']) && !empty($section_data['fields'])) {
1055
                    foreach ($section_data['fields'] as $input) {
1056
                        if (!$input->allowDatasourceParamOutput()) {
1057
                            continue;
1058
                        }
1059
1060
                        $optgroup['options'][] = array(
1061
                            $input->get('element_name'),
1062
                            ($fields['source'] === $section_id && in_array($input->get('element_name'),
1063
                                    $fields['param'])),
1064
                            $prefix . $input->get('element_name'),
1065
                            null,
1066
                            null,
1067
                            array(
1068
                                'data-handle' => $input->get('element_name')
1069
                            )
1070
                        );
1071
                    }
1072
                }
1073
1074
                $options[] = $optgroup;
1075
            }
1076
1077
            $label->appendChild(Widget::Select('fields[param][]', $options, array('multiple' => 'multiple')));
1078
            $group->appendChild($label);
1079
1080
            $fieldset->appendChild($group);
1081
1082
            // Associations
1083
            $label = Widget::Checkbox('fields[associated_entry_counts]', $fields['associated_entry_counts'],
1084
                __('Include a count of entries in associated sections'));
1085
            $this->setContext($label, array('sections'));
1086
            $fieldset->appendChild($label);
1087
1088
            // Encoding
1089
            $label = Widget::Checkbox('fields[html_encode]', $fields['html_encode'], __('HTML-encode text'));
1090
            $this->setContext($label, array('sections'));
1091
            $fieldset->appendChild($label);
1092
1093
            $this->Form->appendChild($fieldset);
1094
1095
            // Static XML
1096
            if (!isset($fields['static_xml'])) {
1097
                $fields['static_xml'] = null;
1098
            }
1099
1100
            $fieldset = new XMLElement('fieldset');
1101
            $this->setContext($fieldset, array('static-xml'));
1102
            $fieldset->appendChild(new XMLElement('legend', __('Static XML')));
1103
            $p = new XMLElement('p', __('Enter valid XML, exclude XML declaration'));
1104
            $p->setAttribute('class', 'help');
1105
            $fieldset->appendChild($p);
1106
1107
            $label = Widget::Label();
1108
            $label->appendChild(Widget::Textarea('fields[static_xml]', 12, 50,
1109
                General::sanitize(stripslashes($fields['static_xml'])),
1110
                array('class' => 'code', 'placeholder' => '<static>content</static>')));
1111
1112
            if (isset($this->_errors['static_xml'])) {
1113
                $fieldset->appendChild(Widget::Error($label, $this->_errors['static_xml']));
1114
            } else {
1115
                $fieldset->appendChild($label);
1116
            }
1117
1118
            $this->Form->appendChild($fieldset);
1119
1120
            // Connections
1121
            $fieldset = new XMLElement('fieldset');
1122
            $fieldset->setAttribute('class', 'settings');
1123
            $fieldset->appendChild(new XMLElement('legend', __('Attach to Pages')));
1124
            $p = new XMLElement('p', __('The data will only be available on the selected pages.'));
1125
            $p->setAttribute('class', 'help');
1126
            $fieldset->appendChild($p);
1127
1128
            $div = new XMLElement('div');
1129
            $label = Widget::Label(__('Pages'));
1130
1131
            $pages = PageManager::fetch();
1132
            $ds_handle = str_replace('-', '_', Lang::createHandle($fields['name']));
1133
            $connections = ResourceManager::getAttachedPages(ResourceManager::RESOURCE_TYPE_DS, $ds_handle);
1134
            $selected = array();
1135
1136
            foreach ($connections as $connection) {
1137
                $selected[] = $connection['id'];
1138
            }
1139
1140
            $options = array();
1141
1142 View Code Duplication
            foreach ($pages as $page) {
0 ignored issues
show
Bug introduced by
The expression $pages of type array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1143
                $options[] = array(
1144
                    $page['id'],
1145
                    in_array($page['id'], $selected),
1146
                    PageManager::resolvePageTitle($page['id'])
1147
                );
1148
            }
1149
1150
            $label->appendChild(Widget::Select('fields[connections][]', $options, array('multiple' => 'multiple')));
1151
            $div->appendChild($label);
1152
1153
            $fieldset->appendChild($div);
1154
            $this->Form->appendChild($fieldset);
1155
1156
1157
            // Call the provided datasources to let them inject their filters
1158
            // @todo Ideally when a new Datasource is chosen an AJAX request will fire
1159
            // to get the HTML from the extension. This is hardcoded for now into
1160
            // creating a 'big' page and then hiding the fields with JS
1161
            if (!empty($providers)) {
1162
                foreach ($providers as $providerClass => $provider) {
1163
                    call_user_func_array(array($providerClass, 'buildEditor'),
1164
                        array($this->Form, &$this->_errors, $fields, $handle));
1165
                }
1166
            }
1167
1168
            $div = new XMLElement('div');
1169
            $div->setAttribute('class', 'actions');
1170
            $div->appendChild(Widget::Input('action[save]',
1171
                ($isEditing ? __('Save Changes') : __('Create Data Source')), 'submit', array('accesskey' => 's')));
1172
1173 View Code Duplication
            if ($isEditing) {
1174
                $button = new XMLElement('button', __('Delete'));
1175
                $button->setAttributeArray(array(
1176
                    'name' => 'action[delete]',
1177
                    'class' => 'button confirm delete',
1178
                    'title' => __('Delete this data source'),
1179
                    'type' => 'submit',
1180
                    'accesskey' => 'd',
1181
                    'data-message' => __('Are you sure you want to delete this data source?')
1182
                ));
1183
                $div->appendChild($button);
1184
            }
1185
1186
            $this->Form->appendChild($div);
1187
        }
1188
1189
        /**
1190
         * Set Data Source context
1191
         *
1192
         * @since Symphony 2.3.3
1193
         * @param XMLElement $element
1194
         * @param array $context
1195
         */
1196
        public function setContext(&$element, $context)
1197
        {
1198
            $element->setAttribute('class', 'settings contextual');
1199
            $element->setAttribute('data-context', implode(' ', (array)$context));
1200
        }
1201
1202
        public function __appendAuthorFilter(&$wrapper, $h4_label, $name, $value = null, $templateOnly = true)
1203
        {
1204
            if (!$templateOnly) {
1205
                $li = new XMLElement('li');
1206
                $li->setAttribute('class', 'unique');
1207
                $li->setAttribute('data-type', $name);
1208
                $li->appendChild(new XMLElement('header', '<h4>' . $h4_label . '</h4>'));
1209
                $label = Widget::Label(__('Value'));
1210
                $label->appendChild(Widget::Input('fields[filter][author][' . $name . ']', General::sanitize($value)));
1211
                $li->appendChild($label);
1212
1213
                $wrapper->appendChild($li);
1214
            }
1215
1216
            $li = new XMLElement('li');
1217
            $li->setAttribute('class', 'unique template');
1218
            $li->setAttribute('data-type', $name);
1219
            $li->appendChild(new XMLElement('header', '<h4>' . $h4_label . '</h4>'));
1220
            $label = Widget::Label(__('Value'));
1221
            $label->appendChild(Widget::Input('fields[filter][author][' . $name . ']'));
1222
            $li->appendChild($label);
1223
1224
            $wrapper->appendChild($li);
1225
        }
1226
1227
        public function __viewEdit()
1228
        {
1229
            $this->__form();
1230
        }
1231
1232
        public function __viewInfo()
1233
        {
1234
            $this->setPageType('form');
1235
1236
            $datasource = DatasourceManager::create($this->_context['handle'], array(), false);
1237
            $about = $datasource->about();
1238
1239
            $this->setTitle(__('%1$s &ndash; %2$s &ndash; %3$s',
1240
                array($about['name'], __('Data Source'), __('Symphony'))));
1241
            $this->appendSubheading((($this->_context['action'] === 'info') ? $about['name'] : __('Untitled')));
1242
            $this->insertBreadcrumbs(array(
1243
                Widget::Anchor(__('Data Sources'), SYMPHONY_URL . '/blueprints/datasources/'),
1244
            ));
1245
            $this->Form->setAttribute('id', 'controller');
1246
1247
            $link = $about['author']['name'];
1248
1249 View Code Duplication
            if (isset($about['author']['website'])) {
1250
                $link = Widget::Anchor($about['author']['name'], General::validateURL($about['author']['website']));
1251
            } elseif (isset($about['author']['email'])) {
1252
                $link = Widget::Anchor($about['author']['name'], 'mailto:' . $about['author']['email']);
1253
            }
1254
1255
            foreach ($about as $key => $value) {
1256
                $fieldset = null;
1257
1258
                switch ($key) {
1259
                    case 'author':
1260
                        if ($link) {
1261
                            $fieldset = new XMLElement('fieldset');
1262
                            $fieldset->appendChild(new XMLElement('legend', __('Author')));
1263
                            $fieldset->appendChild(new XMLElement('p', $link->generate(false)));
1264
                        }
1265
                        break;
1266
                    case 'version':
1267
                        $fieldset = new XMLElement('fieldset');
1268
                        $fieldset->appendChild(new XMLElement('legend', __('Version')));
1269
                        $release_date = array_key_exists('release-date',
1270
                            $about) ? $about['release-date'] : filemtime(DatasourceManager::__getDriverPath($this->_context['handle']));
1271
1272
                        if (preg_match('/^\d+(\.\d+)*$/', $value)) {
1273
                            $fieldset->appendChild(new XMLElement('p', __('%1$s released on %2$s',
1274
                                array($value, DateTimeObj::format($release_date, __SYM_DATE_FORMAT__)))));
1275 View Code Duplication
                        } else {
1276
                            $fieldset->appendChild(new XMLElement('p', __('Created by %1$s at %2$s',
1277
                                array($value, DateTimeObj::format($release_date, __SYM_DATE_FORMAT__)))));
1278
                        }
1279
                        break;
1280
                    case 'description':
1281
                        $fieldset = new XMLElement('fieldset');
1282
                        $fieldset->appendChild(new XMLElement('legend', __('Description')));
1283
                        $fieldset->appendChild((is_object($about['description']) ? $about['description'] : new XMLElement('p',
1284
                            $about['description'])));
1285
                        break;
1286
                    case 'example':
1287
                        if (is_callable(array($datasource, 'example'))) {
1288
                            $fieldset = new XMLElement('fieldset');
1289
                            $fieldset->appendChild(new XMLElement('legend', __('Example XML')));
1290
1291
                            $example = $datasource->example();
0 ignored issues
show
Bug introduced by
The method example() does not seem to exist on object<Datasource>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1292
1293
                            if (is_object($example)) {
1294
                                $fieldset->appendChild($example);
1295
                            } else {
1296
                                $p = new XMLElement('p');
1297
                                $p->appendChild(new XMLElement('pre',
1298
                                    '<code>' . str_replace('<', '&lt;', $example) . '</code>'));
1299
                                $fieldset->appendChild($p);
1300
                            }
1301
                        }
1302
                        break;
1303
                }
1304
1305
                if ($fieldset) {
1306
                    $fieldset->setAttribute('class', 'settings');
1307
                    $this->Form->appendChild($fieldset);
1308
                }
1309
            }
1310
1311
            // Display source
1312
            $file = DatasourceManager::__getClassPath($this->_context['handle']) . '/data.' . $this->_context['handle'] . '.php';
1313
1314
            if (file_exists($file)) {
1315
                $fieldset = new XMLElement('fieldset');
1316
                $fieldset->setAttribute('class', 'settings');
1317
                $fieldset->appendChild(new XMLElement('legend', __('Source')));
1318
1319
                $source = file_get_contents($file);
1320
                $code = new XMLElement('code', htmlspecialchars($source));
1321
                $pre = new XMLElement('pre');
1322
                $pre->appendChild($code);
1323
1324
                $fieldset->appendChild($pre);
1325
                $this->Form->appendChild($fieldset);
1326
            }
1327
        }
1328
1329
        public function __actionIndex($resource_type)
1330
        {
1331
            return parent::__actionIndex(ResourceManager::RESOURCE_TYPE_DS);
1332
        }
1333
1334 View Code Duplication
        public function __actionEdit()
0 ignored issues
show
Coding Style introduced by
__actionEdit uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1335
        {
1336
            if (array_key_exists('save', $_POST['action'])) {
1337
                return $this->__formAction();
1338
            } elseif (array_key_exists('delete', $_POST['action'])) {
1339
                /**
1340
                 * Prior to deleting the Datasource file. Target file path is provided.
1341
                 *
1342
                 * @delegate DatasourcePreDelete
1343
                 * @since Symphony 2.2
1344
                 * @param string $context
1345
                 * '/blueprints/datasources/'
1346
                 * @param string $file
1347
                 *  The path to the Datasource file
1348
                 */
1349
                Symphony::ExtensionManager()->notifyMembers('DatasourcePreDelete', '/blueprints/datasources/',
1350
                    array('file' => DATASOURCES . "/data." . $this->_context['handle'] . ".php"));
1351
1352
                if (!General::deleteFile(DATASOURCES . '/data.' . $this->_context['handle'] . '.php')) {
1353
                    $this->pageAlert(
1354
                        __('Failed to delete %s.', array('<code>' . $this->_context['handle'] . '</code>'))
1355
                        . ' ' . __('Please check permissions on %s.', array('<code>/workspace/data-sources</code>')),
1356
                        Alert::ERROR
1357
                    );
1358
                } else {
1359
                    $pages = ResourceManager::getAttachedPages(ResourceManager::RESOURCE_TYPE_DS,
1360
                        $this->_context['handle']);
1361
1362
                    foreach ($pages as $page) {
1363
                        ResourceManager::detach(ResourceManager::RESOURCE_TYPE_DS, $this->_context['handle'],
1364
                            $page['id']);
1365
                    }
1366
1367
                    redirect(SYMPHONY_URL . '/blueprints/datasources/');
1368
                }
1369
            }
1370
        }
1371
1372
        public function __formAction()
0 ignored issues
show
Coding Style introduced by
__formAction uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1373
        {
1374
            $fields = $_POST['fields'];
1375
            $this->_errors = array();
1376
            $providers = Symphony::ExtensionManager()->getProvidersOf(iProvider::DATASOURCE);
1377
            $providerClass = null;
1378
1379 View Code Duplication
            if (trim($fields['name']) === '') {
1380
                $this->_errors['name'] = __('This is a required field');
1381
            }
1382
1383
            if ($fields['source'] === 'static_xml') {
1384
                if (trim($fields['static_xml']) === '') {
1385
                    $this->_errors['static_xml'] = __('This is a required field');
1386
                } else {
1387
                    $xml_errors = null;
1388
1389
                    include_once TOOLKIT . '/class.xsltprocess.php';
1390
1391
                    General::validateXML($fields['static_xml'], $xml_errors, false, new XsltProcess());
1392
1393
                    if (!empty($xml_errors)) {
1394
                        $this->_errors['static_xml'] = __('XML is invalid.');
1395
                    }
1396
                }
1397
            } elseif (is_numeric($fields['source'])) {
1398 View Code Duplication
                if (strlen(trim($fields['max_records'])) === 0 || (is_numeric($fields['max_records']) && $fields['max_records'] < 1)) {
1399
                    if ($fields['paginate_results'] === 'yes') {
1400
                        $this->_errors['max_records'] = __('A result limit must be set');
1401
                    }
1402
                } elseif (!self::__isValidPageString($fields['max_records'])) {
1403
                    $this->_errors['max_records'] = __('Must be a valid number or parameter');
1404
                }
1405
1406 View Code Duplication
                if (strlen(trim($fields['page_number'])) === 0 || (is_numeric($fields['page_number']) && $fields['page_number'] < 1)) {
1407
                    if ($fields['paginate_results'] === 'yes') {
1408
                        $this->_errors['page_number'] = __('A page number must be set');
1409
                    }
1410
                } elseif (!self::__isValidPageString($fields['page_number'])) {
1411
                    $this->_errors['page_number'] = __('Must be a valid number or parameter');
1412
                }
1413
1414
                // See if a Provided Datasource is saved
1415 View Code Duplication
            } elseif (!empty($providers)) {
1416
                foreach ($providers as $providerClass => $provider) {
1417
                    if ($fields['source'] === call_user_func(array($providerClass, 'getSource'))) {
1418
                        call_user_func_array(array($providerClass, 'validate'), array(&$fields, &$this->_errors));
1419
                        break;
1420
                    }
1421
1422
                    unset($providerClass);
1423
                }
1424
            }
1425
1426
            $classname = Lang::createHandle($fields['name'], 255, '_', false, true,
1427
                array('@^[^a-z\d]+@i' => '', '/[^\w-\.]/i' => ''));
1428
            $rootelement = str_replace('_', '-', $classname);
1429
1430
            // Check to make sure the classname is not empty after handlisation.
1431 View Code Duplication
            if (empty($classname) && !isset($this->_errors['name'])) {
1432
                $this->_errors['name'] = __('Please ensure name contains at least one Latin-based character.',
1433
                    array($classname));
1434
            }
1435
1436
            $file = DATASOURCES . '/data.' . $classname . '.php';
1437
1438
            $isDuplicate = false;
1439
            $queueForDeletion = null;
1440
1441 View Code Duplication
            if ($this->_context['action'] === 'new' && is_file($file)) {
1442
                $isDuplicate = true;
1443
            } elseif ($this->_context['action'] === 'edit') {
1444
                $existing_handle = $this->_context['handle'];
1445
1446
                if ($classname !== $existing_handle && is_file($file)) {
1447
                    $isDuplicate = true;
1448
                } elseif ($classname !== $existing_handle) {
1449
                    $queueForDeletion = DATASOURCES . '/data.' . $existing_handle . '.php';
1450
                }
1451
            }
1452
1453
            // Duplicate
1454 View Code Duplication
            if ($isDuplicate) {
1455
                $this->_errors['name'] = __('A Data source with the name %s already exists',
1456
                    array('<code>' . $classname . '</code>'));
1457
            }
1458
1459
            if (empty($this->_errors)) {
1460
                $filters = array();
1461
                $elements = null;
1462
                $source = $fields['source'];
1463
                $params = array(
1464
                    'rootelement' => $rootelement
1465
                );
1466
1467
                $about = array(
1468
                    'name' => $fields['name'],
1469
                    'version' => 'Symphony ' . Symphony::Configuration()->get('version', 'symphony'),
1470
                    'release date' => DateTimeObj::getGMT('c'),
1471
                    'author name' => Symphony::Author()->getFullName(),
1472
                    'author website' => URL,
1473
                    'author email' => Symphony::Author()->get('email')
1474
                );
1475
1476
                // If there is a provider, get their template
1477
                if ($providerClass) {
1478
                    $dsShell = file_get_contents(call_user_func(array($providerClass, 'getTemplate')));
1479
                } else {
1480
                    $dsShell = file_get_contents($this->getTemplate('blueprints.datasource'));
1481
                }
1482
1483
                // Author metadata
1484
                self::injectAboutInformation($dsShell, $about);
1485
1486
                // Do dependencies, the template file must have <!-- CLASS NAME -->
1487
                $dsShell = str_replace('<!-- CLASS NAME -->', $classname, $dsShell);
1488
1489
                // If there is a provider, let them do the prepartion work
1490
                if ($providerClass) {
1491
                    $dsShell = call_user_func(array($providerClass, 'prepare'), $fields, $params, $dsShell);
1492
                } else {
1493
                    switch ($source) {
1494
                        case 'authors':
1495
                            $extends = 'AuthorDatasource';
1496
                            if (isset($fields['filter']['author'])) {
1497
                                $filters = $fields['filter']['author'];
1498
                            }
1499
1500
                            $elements = $fields['xml_elements'];
1501
1502
                            $params['order'] = $fields['order'];
1503
                            $params['redirectonempty'] = $fields['redirect_on_empty'];
1504
                            $params['redirectonforbidden'] = $fields['redirect_on_forbidden'];
1505
                            $params['redirectonrequired'] = $fields['redirect_on_required'];
1506
                            $params['requiredparam'] = trim($fields['required_url_param']);
1507
                            $params['negateparam'] = trim($fields['negate_url_param']);
1508
                            $params['paramoutput'] = $fields['param'];
1509
                            $params['sort'] = $fields['sort'];
1510
1511
                            break;
1512
                        case 'navigation':
1513
                            $extends = 'NavigationDatasource';
1514
                            if (isset($fields['filter']['navigation'])) {
1515
                                $filters = $fields['filter']['navigation'];
1516
                            }
1517
1518
                            $params['order'] = $fields['order'];
1519
                            $params['redirectonempty'] = $fields['redirect_on_empty'];
1520
                            $params['redirectonforbidden'] = $fields['redirect_on_forbidden'];
1521
                            $params['redirectonrequired'] = $fields['redirect_on_required'];
1522
                            $params['requiredparam'] = trim($fields['required_url_param']);
1523
                            $params['negateparam'] = trim($fields['negate_url_param']);
1524
1525
                            break;
1526
                        case 'static_xml':
1527
                            $extends = 'StaticXMLDatasource';
1528
                            $fields['static_xml'] = trim($fields['static_xml']);
1529
1530
                            if (preg_match('/^<\?xml/i', $fields['static_xml']) === true) {
1531
                                // Need to remove any XML declaration
1532
                                $fields['static_xml'] = preg_replace('/^<\?xml[^>]+>/i', null, $fields['static_xml']);
1533
                            }
1534
1535
                            $params['static'] = sprintf(
1536
                                '%s',
1537
                                trim($fields['static_xml'])
1538
                            );
1539
                            break;
1540
                        default:
1541
                            $extends = 'SectionDatasource';
1542
                            $elements = $fields['xml_elements'];
1543
1544
                            if (is_array($fields['filter']) && !empty($fields['filter'])) {
1545
                                $filters = array();
1546
1547
                                foreach ($fields['filter'] as $f) {
1548
                                    foreach ($f as $key => $val) {
1549
                                        $filters[$key] = $val;
1550
                                    }
1551
                                }
1552
                            }
1553
1554
                            $params['order'] = $fields['order'];
1555
                            $params['group'] = $fields['group'];
1556
                            $params['paginateresults'] = $fields['paginate_results'];
1557
                            $params['limit'] = $fields['max_records'];
1558
                            $params['startpage'] = $fields['page_number'];
1559
                            $params['redirectonempty'] = $fields['redirect_on_empty'];
1560
                            $params['redirectonforbidden'] = $fields['redirect_on_forbidden'];
1561
                            $params['redirectonrequired'] = $fields['redirect_on_required'];
1562
                            $params['requiredparam'] = trim($fields['required_url_param']);
1563
                            $params['negateparam'] = trim($fields['negate_url_param']);
1564
                            $params['paramoutput'] = $fields['param'];
1565
                            $params['sort'] = $fields['sort'];
1566
                            $params['htmlencode'] = $fields['html_encode'];
1567
                            $params['associatedentrycounts'] = $fields['associated_entry_counts'];
1568
1569
                            break;
1570
                    }
1571
1572
                    $this->__injectVarList($dsShell, $params);
1573
                    $this->__injectIncludedElements($dsShell, $elements);
1574
                    self::injectFilters($dsShell, $filters);
1575
1576
                    if (preg_match_all('@(\$ds-[0-9a-z_\.\-]+)@i', $dsShell, $matches)) {
1577
                        $dependencies = General::array_remove_duplicates($matches[1]);
1578
                        $dsShell = str_replace('<!-- DS DEPENDENCY LIST -->',
1579
                            "'" . implode("', '", $dependencies) . "'", $dsShell);
1580
                    }
1581
1582
                    $dsShell = str_replace('<!-- CLASS EXTENDS -->', $extends, $dsShell);
1583
                    $dsShell = str_replace('<!-- SOURCE -->', $source, $dsShell);
1584
                }
1585
1586
                if ($this->_context['action'] === 'new') {
1587
                    /**
1588
                     * Prior to creating the Datasource, the file path where it will be written to
1589
                     * is provided and well as the contents of that file.
1590
                     *
1591
                     * @delegate DatasourcePreCreate
1592
                     * @since Symphony 2.2
1593
                     * @param string $context
1594
                     * '/blueprints/datasources/'
1595
                     * @param string $file
1596
                     *  The path to the Datasource file
1597
                     * @param string $contents
1598
                     *  The contents for this Datasource as a string passed by reference
1599
                     * @param array $params
1600
                     *  An array of all the `$dsParam*` values
1601
                     * @param array $elements
1602
                     *  An array of all the elements included in this datasource
1603
                     * @param array $filters
1604
                     *  An associative array of all the filters for this datasource with the key
1605
                     *  being the `field_id` and the value the filter.
1606
                     * @param array $dependencies
1607
                     *  An array of dependencies that this datasource has
1608
                     */
1609
                    Symphony::ExtensionManager()->notifyMembers('DatasourcePreCreate', '/blueprints/datasources/',
1610
                        array(
1611
                            'file' => $file,
1612
                            'contents' => &$dsShell,
1613
                            'params' => $params,
1614
                            'elements' => $elements,
1615
                            'filters' => $filters,
1616
                            'dependencies' => $dependencies
0 ignored issues
show
Bug introduced by
The variable $dependencies does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1617
                        ));
1618
                } else {
1619
                    /**
1620
                     * Prior to editing a Datasource, the file path where it will be written to
1621
                     * is provided and well as the contents of that file.
1622
                     *
1623
                     * @delegate DatasourcePreEdit
1624
                     * @since Symphony 2.2
1625
                     * @param string $context
1626
                     * '/blueprints/datasources/'
1627
                     * @param string $file
1628
                     *  The path to the Datasource file
1629
                     * @param string $contents
1630
                     *  The contents for this Datasource as a string passed by reference
1631
                     * @param array $dependencies
1632
                     *  An array of dependencies that this datasource has
1633
                     * @param array $params
1634
                     *  An array of all the `$dsParam*` values
1635
                     * @param array $elements
1636
                     *  An array of all the elements included in this datasource
1637
                     * @param array $filters
1638
                     *  An associative array of all the filters for this datasource with the key
1639
                     *  being the `field_id` and the value the filter.
1640
                     */
1641
                    Symphony::ExtensionManager()->notifyMembers('DatasourcePreEdit', '/blueprints/datasources/', array(
1642
                        'file' => $file,
1643
                        'contents' => &$dsShell,
1644
                        'dependencies' => $dependencies,
1645
                        'params' => $params,
1646
                        'elements' => $elements,
1647
                        'filters' => $filters
1648
                    ));
1649
                }
1650
1651
                // Remove left over placeholders
1652
                $dsShell = preg_replace(array('/<!--[\w ]++-->/', '/(\t+[\r\n]){2,}/', '/(\r\n){2,}/'), '$1', $dsShell);
1653
1654
                // Write the file
1655 View Code Duplication
                if (!is_writable(dirname($file)) || !General::writeFile($file, $dsShell,
1656
                        Symphony::Configuration()->get('write_mode', 'file'), 'w', true)
1657
                ) {
1658
                    $this->pageAlert(
1659
                        __('Failed to write Data source to disk.')
1660
                        . ' ' . __('Please check permissions on %s.', array('<code>/workspace/data-sources</code>')),
1661
                        Alert::ERROR
1662
                    );
1663
1664
                    // Write successful
1665
                } else {
1666
                    if (function_exists('opcache_invalidate')) {
1667
                        opcache_invalidate($file, true);
1668
                    }
1669
1670
                    // Attach this datasources to pages
1671
                    $connections = $fields['connections'];
1672
                    ResourceManager::setPages(ResourceManager::RESOURCE_TYPE_DS,
1673
                        is_null($existing_handle) ? $classname : $existing_handle, $connections);
0 ignored issues
show
Bug introduced by
The variable $existing_handle does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1674
1675
                    // If the datasource has been updated and the name changed, then adjust all the existing pages that have the old datasource name
1676
                    if ($queueForDeletion) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $queueForDeletion of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1677
                        General::deleteFile($queueForDeletion);
1678
1679
                        // Update pages that use this DS
1680
                        $pages = PageManager::fetch(false, array('data_sources', 'id'), array(
1681
                            "
1682
                        `data_sources` REGEXP '[[:<:]]" . $existing_handle . "[[:>:]]'
1683
                    "
1684
                        ));
1685
1686
                        if (is_array($pages) && !empty($pages)) {
1687
                            foreach ($pages as $page) {
1688
                                $page['data_sources'] = preg_replace('/\b' . $existing_handle . '\b/i', $classname,
1689
                                    $page['data_sources']);
1690
1691
                                PageManager::edit($page['id'], $page);
1692
                            }
1693
                        }
1694
                    }
1695
1696
                    if ($this->_context['action'] === 'new') {
1697
                        /**
1698
                         * After creating the Datasource, the path to the Datasource file is provided
1699
                         *
1700
                         * @delegate DatasourcePostCreate
1701
                         * @since Symphony 2.2
1702
                         * @param string $context
1703
                         * '/blueprints/datasources/'
1704
                         * @param string $file
1705
                         *  The path to the Datasource file
1706
                         */
1707
                        Symphony::ExtensionManager()->notifyMembers('DatasourcePostCreate', '/blueprints/datasources/',
1708
                            array(
1709
                                'file' => $file
1710
                            ));
1711
                    } else {
1712
                        /**
1713
                         * After editing the Datasource, the path to the Datasource file is provided
1714
                         *
1715
                         * @delegate DatasourcePostEdit
1716
                         * @since Symphony 2.2
1717
                         * @param string $context
1718
                         * '/blueprints/datasources/'
1719
                         * @param string $file
1720
                         *  The path to the Datasource file
1721
                         * @param string $previous_file
1722
                         *  The path of the previous Datasource file in the case where a Datasource may
1723
                         *  have been renamed. To get the handle from this value, see
1724
                         *  `DatasourceManager::__getHandleFromFilename`
1725
                         */
1726
                        Symphony::ExtensionManager()->notifyMembers('DatasourcePostEdit', '/blueprints/datasources/',
1727
                            array(
1728
                                'file' => $file,
1729
                                'previous_file' => ($queueForDeletion) ? $queueForDeletion : null
1730
                            ));
1731
                    }
1732
1733
                    redirect(SYMPHONY_URL . '/blueprints/datasources/edit/' . $classname . '/' . ($this->_context['action'] === 'new' ? 'created' : 'saved') . '/');
1734
                }
1735
            }
1736
        }
1737
1738
        private static function __isValidPageString($string)
1739
        {
1740
            return (bool)preg_match('/^\{\$[\w-]+(.[\w]+(-[\w]+)?){0,1}\}|[\d]+$/', $string);
1741
        }
1742
1743 View Code Duplication
        public static function injectAboutInformation(&$shell, array $details)
1744
        {
1745
            if (empty($details)) {
1746
                return;
1747
            }
1748
1749
            foreach ($details as $key => $val) {
1750
                $shell = str_replace('<!-- ' . strtoupper($key) . ' -->', addslashes($val), $shell);
1751
            }
1752
        }
1753
1754
        public function __injectVarList(&$shell, $vars)
1755
        {
1756
            if (!is_array($vars) || empty($vars)) {
1757
                return;
1758
            }
1759
1760
            $var_list = null;
1761
1762
            foreach ($vars as $key => $val) {
1763
                if (is_array($val)) {
1764
                    $val = "array(" . PHP_EOL . "        '" . implode("'," . PHP_EOL . "        '",
1765
                            $val) . "'" . PHP_EOL . '        );';
1766
                    $var_list .= '    public $dsParam' . strtoupper($key) . ' = ' . $val . PHP_EOL;
1767
                } elseif (trim($val) !== '') {
1768
                    $var_list .= '    public $dsParam' . strtoupper($key) . " = '" . addslashes($val) . "';" . PHP_EOL;
1769
                }
1770
            }
1771
1772
            $placeholder = '<!-- VAR LIST -->';
1773
            $shell = str_replace($placeholder, trim($var_list) . PHP_EOL . "    " . $placeholder, $shell);
1774
        }
1775
1776
        public function __injectIncludedElements(&$shell, $elements)
1777
        {
1778
            if (!is_array($elements) || empty($elements)) {
1779
                return;
1780
            }
1781
1782
            $placeholder = '<!-- INCLUDED ELEMENTS -->';
1783
            $shell = str_replace($placeholder,
1784
                "public \$dsParamINCLUDEDELEMENTS = array(" . PHP_EOL . "        '" . implode("'," . PHP_EOL . "        '",
1785
                    $elements) . "'" . PHP_EOL . '    );' . PHP_EOL . "    " . $placeholder, $shell);
1786
        }
1787
1788
        public static function injectFilters(&$shell, array $filters)
1789
        {
1790
            if (empty($filters)) {
1791
                return;
1792
            }
1793
1794
            $placeholder = '<!-- FILTERS -->';
1795
            $string = 'public $dsParamFILTERS = array(' . PHP_EOL;
1796
1797
            foreach ($filters as $key => $val) {
1798
                if (trim($val) === '') {
1799
                    continue;
1800
                }
1801
1802
                $string .= "        '$key' => '" . addslashes($val) . "'," . PHP_EOL;
1803
            }
1804
1805
            $string .= "    );" . PHP_EOL . "        " . $placeholder;
1806
1807
            $shell = str_replace($placeholder, trim($string), $shell);
1808
        }
1809
1810
        public function __actionNew()
0 ignored issues
show
Coding Style introduced by
__actionNew uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1811
        {
1812
            if (array_key_exists('save', $_POST['action'])) {
1813
                return $this->__formAction();
1814
            }
1815
        }
1816
    }
1817