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
Push — integration ( 45cc9f...98bc42 )
by Brendan
05:52
created

contentBlueprintsDatasources   F

Complexity

Total Complexity 249

Size/Duplication

Total Lines 1610
Duplicated Lines 19.57 %

Coupling/Cohesion

Components 0
Dependencies 18

Importance

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

17 Methods

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

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
 * The Datasource Editor page allows a developer to create new datasources
8
 * from the four Symphony types, Section, Authors, Navigation and Static XML
9
 */
10
11
class contentBlueprintsDatasources extends ResourcesPage
12
{
13
    public $_errors = array();
14
15 View Code Duplication
    public function __viewIndex($resource_type)
16
    {
17
        parent::__viewIndex(ResourceManager::RESOURCE_TYPE_DS);
18
19
        $this->setTitle(__('%1$s &ndash; %2$s', array(__('Data Sources'), __('Symphony'))));
20
        $this->appendSubheading(__('Data Sources'), Widget::Anchor(__('Create New'), Administration::instance()->getCurrentPageURL().'new/', __('Create a new data source'), 'create button', null, array('accesskey' => 'c')));
21
    }
22
23
    // Both the Edit and New pages need the same form
24
    public function __viewNew()
25
    {
26
        $this->__form();
27
    }
28
29
    public function __viewEdit()
30
    {
31
        $this->__form();
32
    }
33
34
    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...
35
    {
36
        $formHasErrors = (is_array($this->_errors) && !empty($this->_errors));
37
38 View Code Duplication
        if ($formHasErrors) {
39
            $this->pageAlert(
40
                __('An error occurred while processing this form. See below for details.'),
41
                Alert::ERROR
42
            );
43
44
            // These alerts are only valid if the form doesn't have errors
45
        } elseif (isset($this->_context['flag'])) {
46
            $time = Widget::Time();
47
48
            switch ($this->_context['flag']) {
49
                case 'saved':
50
                    $message = __('Data Source updated at %s.', array($time->generate()));
51
                    break;
52
                case 'created':
53
                    $message = __('Data Source created at %s.', array($time->generate()));
54
            }
55
56
            $this->pageAlert(
57
                $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...
58
                . ' <a href="' . SYMPHONY_URL . '/blueprints/datasources/new/" accesskey="c">'
59
                . __('Create another?')
60
                . '</a> <a href="' . SYMPHONY_URL . '/blueprints/datasources/" accesskey="a">'
61
                . __('View all Data Sources')
62
                . '</a>',
63
                Alert::SUCCESS
64
            );
65
        }
66
67
        $providers = Symphony::ExtensionManager()->getProvidersOf(iProvider::DATASOURCE);
68
        $isEditing = $this->_context['action'] === 'edit';
69
        $about = $handle = null;
70
        $fields = array('name'=>null, 'source'=>null, 'filter'=>null, 'required_url_param'=>null, 'negate_url_param'=>null, 'param'=>null);
71
72
        if (isset($_POST['fields'])) {
73
            $fields = $_POST['fields'];
74
75
            if (
76
                !in_array($fields['source'], array('authors', 'navigation', 'static_xml'))
77
                && !empty($fields['filter']) && is_array($fields['filter'])
78
            ) {
79
                $filters = array();
80
                foreach ($fields['filter'] as $f) {
81
                    foreach ($f as $key => $val) {
82
                        $filters[$key] = $val;
83
                    }
84
                }
85
86
                $fields['filter'][$fields['source']] = $filters;
87
            }
88
89
            if (!isset($fields['xml_elements']) || !is_array($fields['xml_elements'])) {
90
                $fields['xml_elements'] = array();
91
            }
92
        } elseif ($isEditing) {
93
            $handle = $this->_context['handle'];
94
            $existing = DatasourceManager::create($handle, array(), false);
95
            $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...
96
97
            if (!$existing->allowEditorToParse()) {
98
                redirect(SYMPHONY_URL . '/blueprints/datasources/info/' . $handle . '/');
99
            }
100
101
            $about = $existing->about();
102
            $fields['name'] = $about['name'];
103
104
            $fields['order'] = ($order === 'rand') ? 'random' : $order;
105
            $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...
106
            $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...
107
            $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...
108
109
            if (isset($existing->dsParamINCLUDEDELEMENTS) && is_array($existing->dsParamINCLUDEDELEMENTS)) {
110
                $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...
111
            } else {
112
                $fields['xml_elements'] = array();
113
            }
114
115
            $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...
116
            $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...
117
            $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...
118
            $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...
119
            $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...
120
            $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...
121
            $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...
122
            $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...
123
            $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...
124
125
            if (!isset($existing->dsParamFILTERS) || !is_array($existing->dsParamFILTERS)) {
126
                $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...
127
            }
128
129
            if (!empty($existing->dsParamFILTERS)) {
130
                $existing->dsParamFILTERS = array_map('stripslashes', $existing->dsParamFILTERS);
131
            }
132
133
            $fields['source'] = $existing->getSource();
134
135
            $provided = false;
136
137 View Code Duplication
            if (!empty($providers)) {
138
                foreach ($providers as $providerClass => $provider) {
139
                    if ($fields['source'] === call_user_func(array($providerClass, 'getClass'))) {
140
                        $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...
141
                        $provided = true;
142
                        break;
143
                    }
144
                }
145
            }
146
147
            if ($provided === false) {
148
                switch ($fields['source']) {
149
                    case 'authors':
150
                        $fields['filter']['author'] = $existing->dsParamFILTERS;
151
                        break;
152
                    case 'navigation':
153
                        $fields['filter']['navigation'] = $existing->dsParamFILTERS;
154
                        break;
155
                    case 'static_xml':
156
                        // Symphony 2.3+
157
                        if (isset($existing->dsParamSTATIC)) {
158
                            $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...
159
160
                            // Handle Symphony 2.2.2 to 2.3 DS's
161
                        } elseif (isset($existing->dsSTATIC)) {
162
                            $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...
163
164
                            // Handle pre Symphony 2.2.1 Static DS's
165
                        } else {
166
                            $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...
167
                        }
168
                        break;
169
                    default:
170
                        $fields['filter'][$fields['source']] = $existing->dsParamFILTERS;
171
                        $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...
172
                        break;
173
                }
174
            }
175
        } else {
176
            $fields['max_records'] = '20';
177
            $fields['page_number'] = '1';
178
            $fields['order'] = 'desc';
179
        }
180
181
        // Handle name on edited changes, or from reading an edited datasource
182 View Code Duplication
        if (isset($about['name'])) {
183
            $name = $about['name'];
184
        } elseif (isset($fields['name'])) {
185
            $name = $fields['name'];
186
        }
187
188
        $this->setPageType('form');
189
        $this->setTitle(__(($isEditing ? '%1$s &ndash; %2$s &ndash; %3$s' : '%2$s &ndash; %3$s'), 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...
190
        $this->appendSubheading(($isEditing ? $name : __('Untitled')));
191
        $this->insertBreadcrumbs(array(
192
            Widget::Anchor(__('Data Sources'), SYMPHONY_URL . '/blueprints/datasources/'),
193
        ));
194
195
        // Sources
196
        $sources = new XMLElement('div', null, array('class' => 'apply actions'));
197
        $div = new XMLElement('div');
198
        $label = Widget::Label(__('Source'), null, 'apply-label-left');
199
        $sources->appendChild($label);
200
        $sources->appendChild($div);
201
202
        $sections = SectionManager::fetch(null, 'ASC', 'name');
203
204
        if (!is_array($sections)) {
205
            $sections = array();
206
        }
207
208
        $field_groups = array();
209
210 View Code Duplication
        foreach ($sections as $section) {
211
            $field_groups[$section->get('id')] = array('fields' => $section->fetchFields(), 'section' => $section);
212
        }
213
214
        $options = array(
215
            array('label' => __('System'), 'data-label' => 'system', 'options' => array(
216
                    array('authors', ($fields['source'] === 'authors'), __('Authors'), null, null, array('data-context' => 'authors')),
217
                    array('navigation', ($fields['source'] === 'navigation'), __('Navigation'), null, null, array('data-context' => 'navigation')),
218
            )),
219
            array('label' => __('Custom XML'), 'data-label' => 'custom-xml', 'options' => array(
220
                    array('static_xml', ($fields['source'] === 'static_xml'), __('Static XML'), null, null, array('data-context' => 'static-xml')),
221
            )),
222
        );
223
224
        // Loop over the datasource providers
225
        if (!empty($providers)) {
226
            $p = array('label' => __('From extensions'), 'data-label' => 'from_extensions', 'options' => array());
227
228
            foreach ($providers as $providerClass => $provider) {
229
                $p['options'][] = array(
230
                    $providerClass, ($fields['source'] === $providerClass), $provider, null, null, array('data-context' => Lang::createHandle($provider))
231
                );
232
            }
233
234
            $options[] = $p;
235
        }
236
237
        // Add Sections
238
        if (is_array($sections) && !empty($sections)) {
239
            array_unshift($options, array('label' => __('Sections'), 'data-label' => 'sections', 'options' => array()));
240
241 View Code Duplication
            foreach ($sections as $s) {
242
                $options[0]['options'][] = array($s->get('id'), ($fields['source'] === $s->get('id')), General::sanitize($s->get('name')));
243
            }
244
        }
245
246
        $div->appendChild(Widget::Select('source', $options, array('id' => 'ds-context')));
247
        $this->Context->prependChild($sources);
248
249
        $this->Form->appendChild(
250
            Widget::Input('fields[source]', null, 'hidden', array('id' => 'ds-source'))
251
        );
252
253
        // Name
254
        $fieldset = new XMLElement('fieldset');
255
        $fieldset->setAttribute('class', 'settings');
256
        $fieldset->appendChild(new XMLElement('legend', __('Essentials')));
257
258
        $group = new XMLElement('div');
259
260
        $label = Widget::Label(__('Name'));
261
        $label->appendChild(Widget::Input('fields[name]', General::sanitize($fields['name'])));
262
263
        if (isset($this->_errors['name'])) {
264
            $group->appendChild(Widget::Error($label, $this->_errors['name']));
265
        } else {
266
            $group->appendChild($label);
267
        }
268
269
        $fieldset->appendChild($group);
270
        $this->Form->appendChild($fieldset);
271
272
        // Conditions
273
        $fieldset = new XMLElement('fieldset');
274
        $this->setContext($fieldset, array('sections', 'system', 'custom-xml'));
275
        $fieldset->appendChild(new XMLElement('legend', __('Conditions')));
276
        $p = new XMLElement('p', __('Leaving these fields empty will always execute the data source.'));
277
        $p->setAttribute('class', 'help');
278
        $fieldset->appendChild($p);
279
280
        $group = new XMLElement('div');
281
        $group->setAttribute('class', 'two columns');
282
283
        $label = Widget::Label(__('Required Parameter'));
284
        $label->setAttribute('class', 'column ds-param');
285
        $label->appendChild(new XMLElement('i', __('Optional')));
286
        $input = Widget::Input('fields[required_url_param]', trim($fields['required_url_param']), 'text', array(
287
            'placeholder' => __('$param'),
288
            'data-search-types' => 'parameters',
289
            'data-trigger' => '$'
290
        ));
291
        $label->appendChild($input);
292
        $group->appendChild($label);
293
294
        $label = Widget::Label(__('Forbidden Parameter'));
295
        $label->setAttribute('class', 'column ds-param');
296
        $label->appendChild(new XMLElement('i', __('Optional')));
297
        $input = Widget::Input('fields[negate_url_param]', trim($fields['negate_url_param']), 'text', array(
298
            'placeholder' => __('$param'),
299
            'data-search-types' => 'parameters',
300
            'data-trigger' => '$'
301
        ));
302
        $label->appendChild($input);
303
        $group->appendChild($label);
304
305
        $fieldset->appendChild($group);
306
307
        $group = new XMLElement('div');
308
        $group->setAttribute('class', 'two columns ds-param');
309
310
        $label = Widget::Checkbox('fields[redirect_on_required]', $fields['redirect_on_required'], __('Redirect to 404 page when the required parameter is not present'));
311
        $label->setAttribute('class', 'column');
312
        $group->appendChild($label);
313
314
        $label = Widget::Checkbox('fields[redirect_on_forbidden]', $fields['redirect_on_forbidden'], __('Redirect to 404 page when the forbidden parameter is present'));
315
        $label->setAttribute('class', 'column');
316
        $group->appendChild($label);
317
318
        $fieldset->appendChild($group);
319
320
        $label = Widget::Checkbox('fields[redirect_on_empty]', $fields['redirect_on_empty'], __('Redirect to 404 page when no results are found'));
321
        $label->setAttribute('class', 'column');
322
        $fieldset->appendChild($label);
323
324
        $this->Form->appendChild($fieldset);
325
326
        // Filters
327
        $fieldset = new XMLElement('fieldset');
328
        $this->setContext($fieldset, array('sections', 'system'));
329
        $fieldset->appendChild(new XMLElement('legend', __('Filters')));
330
        $p = new XMLElement('p',
331
            __('Use %s syntax to filter by page parameters. A default value can be set using %s.', array(
332
                '<code>{' . __('$param') . '}</code>',
333
                '<code>{' . __('$param:default') . '}</code>'
334
            ))
335
        );
336
        $p->setAttribute('class', 'help');
337
        $fieldset->appendChild($p);
338
339
        foreach ($field_groups as $section_id => $section_data) {
340
            $div = new XMLElement('div');
341
            $div->setAttribute('class', 'contextual frame filters-duplicator');
342
            $div->setAttribute('data-context', 'section-' . $section_id);
343
            $div->setAttribute('data-interactive', 'data-interactive');
344
345
            $ol = new XMLElement('ol');
346
            $ol->setAttribute('class', 'suggestable');
347
            $ol->setAttribute('data-interactive', 'data-interactive');
348
            $ol->setAttribute('data-add', __('Add filter'));
349
            $ol->setAttribute('data-remove', __('Remove filter'));
350
351
            // Add system:id filter
352 View Code Duplication
            if (
353
                isset($fields['filter'][$section_id]['system:id'])
354
                || isset($fields['filter'][$section_id]['id'])
355
            ) {
356
                $id = isset($fields['filter'][$section_id]['system:id'])
357
                    ? $fields['filter'][$section_id]['system:id']
358
                    : $fields['filter'][$section_id]['id'];
359
360
                $li = new XMLElement('li');
361
                $li->setAttribute('class', 'unique');
362
                $li->setAttribute('data-type', 'system:id');
363
                $li->appendChild(new XMLElement('header', '<h4>' . __('System ID') . '</h4>'));
364
                $label = Widget::Label(__('Value'));
365
                $input = Widget::Input('fields[filter]['.$section_id.'][system:id]', General::sanitize($id));
366
                $input->setAttribute('data-search-types', 'parameters');
367
                $input->setAttribute('data-trigger', '{$');
368
                $label->appendChild($input);
369
                $li->appendChild($label);
370
                $ol->appendChild($li);
371
            }
372
373
            $li = new XMLElement('li');
374
            $li->setAttribute('class', 'unique template');
375
            $li->setAttribute('data-type', 'system:id');
376
            $li->appendChild(new XMLElement('header', '<h4>' . __('System ID') . '</h4>'));
377
            $label = Widget::Label(__('Value'));
378
            $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...
379
            $input->setAttribute('data-search-types', 'parameters');
380
            $input->setAttribute('data-trigger', '{$');
381
            $label->appendChild($input);
382
            $li->appendChild($label);
383
            $ol->appendChild($li);
384
385
            // Add system:date filter
386 View Code Duplication
            if (
387
                isset($fields['filter'][$section_id]['system:creation-date'])
388
                || isset($fields['filter'][$section_id]['system:date'])
389
            ) {
390
                $creation_date = isset($fields['filter'][$section_id]['system:creation-date'])
391
                    ? $fields['filter'][$section_id]['system:creation-date']
392
                    : $fields['filter'][$section_id]['system:date'];
393
394
                $li = new XMLElement('li');
395
                $li->setAttribute('class', 'unique');
396
                $li->setAttribute('data-type', 'system:creation-date');
397
                $li->appendChild(new XMLElement('header', '<h4>' . __('System Creation Date') . '</h4>'));
398
                $label = Widget::Label(__('Value'));
399
                $input = Widget::Input('fields[filter]['.$section_id.'][system:creation-date]', General::sanitize($creation_date));
400
                $input->setAttribute('data-search-types', 'parameters');
401
                $input->setAttribute('data-trigger', '{$');
402
                $label->appendChild($input);
403
                $li->appendChild($label);
404
                $ol->appendChild($li);
405
            }
406
407
            $li = new XMLElement('li');
408
            $li->setAttribute('class', 'unique template');
409
            $li->setAttribute('data-type', 'system:creation-date');
410
            $li->appendChild(new XMLElement('header', '<h4>' . __('System Creation Date') . '</h4>'));
411
            $label = Widget::Label(__('Value'));
412
            $input = Widget::Input('fields[filter]['.$section_id.'][system:creation-date]');
413
            $input->setAttribute('data-search-types', 'parameters');
414
            $input->setAttribute('data-trigger', '{$');
415
            $label->appendChild($input);
416
            $li->appendChild($label);
417
            $ol->appendChild($li);
418
419
            if (isset($fields['filter'][$section_id]['system:modification-date'])) {
420
                $li = new XMLElement('li');
421
                $li->setAttribute('class', 'unique');
422
                $li->setAttribute('data-type', 'system:modification-date');
423
                $li->appendChild(new XMLElement('header', '<h4>' . __('System Modification Date') . '</h4>'));
424
                $label = Widget::Label(__('Value'));
425
                $input = Widget::Input('fields[filter]['.$section_id.'][system:modification-date]', General::sanitize($fields['filter'][$section_id]['system:modification-date']));
426
                $input->setAttribute('data-search-types', 'parameters');
427
                $input->setAttribute('data-trigger', '{$');
428
                $label->appendChild($input);
429
                $li->appendChild($label);
430
                $ol->appendChild($li);
431
            }
432
433
            $li = new XMLElement('li');
434
            $li->setAttribute('class', 'unique template');
435
            $li->setAttribute('data-type', 'system:modification-date');
436
            $li->appendChild(new XMLElement('header', '<h4>' . __('System Modification Date') . '</h4>'));
437
            $label = Widget::Label(__('Value'));
438
            $input = Widget::Input('fields[filter]['.$section_id.'][system:modification-date]');
439
            $input->setAttribute('data-search-types', 'parameters');
440
            $input->setAttribute('data-trigger', '{$');
441
            $label->appendChild($input);
442
            $li->appendChild($label);
443
            $ol->appendChild($li);
444
445
            if (is_array($section_data['fields']) && !empty($section_data['fields'])) {
446
                foreach ($section_data['fields'] as $field) {
447
                    if (!$field->canFilter()) {
448
                        continue;
449
                    }
450
451
                    if (isset($fields['filter'][$section_id], $fields['filter'][$section_id][$field->get('id')])) {
452
                        $wrapper = new XMLElement('li');
453
                        $wrapper->setAttribute('class', 'unique');
454
                        $wrapper->setAttribute('data-type', $field->get('element_name'));
455
                        $errors = isset($this->_errors[$field->get('id')])
456
                            ? $this->_errors[$field->get('id')]
457
                            : array();
458
459
                        $field->displayDatasourceFilterPanel($wrapper, $fields['filter'][$section_id][$field->get('id')], $errors, $section_id);
460
                        $ol->appendChild($wrapper);
461
                    }
462
463
                    $wrapper = new XMLElement('li');
464
                    $wrapper->setAttribute('class', 'unique template');
465
                    $wrapper->setAttribute('data-type', $field->get('element_name'));
466
                    $field->displayDatasourceFilterPanel($wrapper, null, null, $section_id);
467
                    $ol->appendChild($wrapper);
468
                }
469
            }
470
471
            $div->appendChild($ol);
472
473
            $fieldset->appendChild($div);
474
        }
475
476
        $div = new XMLElement('div');
477
        $div->setAttribute('class', 'contextual frame filters-duplicator');
478
        $div->setAttribute('data-context', 'authors');
479
        $div->setAttribute('data-interactive', 'data-interactive');
480
481
        $ol = new XMLElement('ol');
482
        $ol->setAttribute('class', 'suggestable');
483
        $ol->setAttribute('data-interactive', 'data-interactive');
484
        $ol->setAttribute('data-add', __('Add filter'));
485
        $ol->setAttribute('data-remove', __('Remove filter'));
486
487
        if (!isset($fields['filter']['author'])) {
488
            $fields['filter']['author'] = array(
489
                'id' => null,
490
                'username' => null,
491
                'first_name' => null,
492
                'last_name' => null,
493
                'email' => null,
494
                'user_type' => null
495
            );
496
        }
497
498
        $this->__appendAuthorFilter($ol, __('ID'), 'id', $fields['filter']['author']['id'], (!isset($fields['filter']['author']['id'])));
499
        $this->__appendAuthorFilter($ol, __('Username'), 'username', $fields['filter']['author']['username'], (!isset($fields['filter']['author']['username'])));
500
        $this->__appendAuthorFilter($ol, __('First Name'), 'first_name', $fields['filter']['author']['first_name'], (!isset($fields['filter']['author']['first_name'])));
501
        $this->__appendAuthorFilter($ol, __('Last Name'), 'last_name', $fields['filter']['author']['last_name'], (!isset($fields['filter']['author']['last_name'])));
502
        $this->__appendAuthorFilter($ol, __('Email'), 'email', $fields['filter']['author']['email'], (!isset($fields['filter']['author']['email'])));
503
        $this->__appendAuthorFilter($ol, __('User Type'), 'user_type', $fields['filter']['author']['user_type'], (!isset($fields['filter']['author']['user_type'])));
504
505
        $div->appendChild($ol);
506
507
        $fieldset->appendChild($div);
508
509
        $div = new XMLElement('div');
510
        $div->setAttribute('class', 'contextual frame filters-duplicator');
511
        $div->setAttribute('data-context', 'navigation');
512
        $div->setAttribute('data-interactive', 'data-interactive');
513
514
        $ol = new XMLElement('ol');
515
        $ol->setAttribute('class', 'suggestable');
516
        $ol->setAttribute('data-interactive', 'data-interactive');
517
        $ol->setAttribute('data-add', __('Add filter'));
518
        $ol->setAttribute('data-remove', __('Remove filter'));
519
520
        $ul = new XMLElement('ul');
521
        $ul->setAttribute('class', 'tags');
522
        $ul->setAttribute('data-interactive', 'data-interactive');
523
524
        $pages = PageManager::fetch(false, array('*'), array(), 'title ASC');
525
526
        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...
527
            $ul->appendChild(new XMLElement('li', preg_replace('/\/{2,}/i', '/', '/' . $page['path'] . '/' . $page['handle'])));
528
        }
529
530 View Code Duplication
        if (isset($fields['filter']['navigation']['parent'])) {
531
            $li = new XMLElement('li');
532
            $li->setAttribute('class', 'unique');
533
            $li->setAttribute('data-type', 'parent');
534
            $li->appendChild(new XMLElement('header', '<h4>' . __('Parent Page') . '</h4>'));
535
            $label = Widget::Label(__('Value'));
536
            $label->appendChild(Widget::Input('fields[filter][navigation][parent]', General::sanitize($fields['filter']['navigation']['parent'])));
537
            $li->appendChild($label);
538
            $li->appendChild($ul);
539
            $ol->appendChild($li);
540
        }
541
542
        $li = new XMLElement('li');
543
        $li->setAttribute('class', 'unique template');
544
        $li->setAttribute('data-type', 'parent');
545
        $li->appendChild(new XMLElement('header', '<h4>' . __('Parent Page') . '</h4>'));
546
        $label = Widget::Label(__('Value'));
547
        $label->appendChild(Widget::Input('fields[filter][navigation][parent]'));
548
        $li->appendChild($label);
549
        $li->appendChild($ul);
550
        $ol->appendChild($li);
551
552
        $ul = new XMLElement('ul');
553
        $ul->setAttribute('class', 'tags');
554
        $ul->setAttribute('data-interactive', 'data-interactive');
555
556
        if ($types = PageManager::fetchAvailablePageTypes()) {
557
            foreach ($types as $type) {
558
                $ul->appendChild(new XMLElement('li', $type));
559
            }
560
        }
561
562 View Code Duplication
        if (isset($fields['filter']['navigation']['type'])) {
563
            $li = new XMLElement('li');
564
            $li->setAttribute('class', 'unique');
565
            $li->setAttribute('data-type', 'type');
566
            $li->appendChild(new XMLElement('header', '<h4>' . __('Page Type') . '</h4>'));
567
            $label = Widget::Label(__('Value'));
568
            $label->appendChild(Widget::Input('fields[filter][navigation][type]', General::sanitize($fields['filter']['navigation']['type'])));
569
            $li->appendChild($label);
570
            $li->appendChild($ul);
571
            $ol->appendChild($li);
572
        }
573
574
        $li = new XMLElement('li');
575
        $li->setAttribute('class', 'unique template');
576
        $li->appendChild(new XMLElement('header', '<h4>' . __('Page Type') . '</h4>'));
577
        $li->setAttribute('data-type', 'type');
578
        $label = Widget::Label(__('Value'));
579
        $label->appendChild(Widget::Input('fields[filter][navigation][type]'));
580
        $li->appendChild($label);
581
        $li->appendChild($ul);
582
        $ol->appendChild($li);
583
584
        $div->appendChild($ol);
585
586
        $fieldset->appendChild($div);
587
        $this->Form->appendChild($fieldset);
588
589
        // Sorting
590
        $fieldset = new XMLElement('fieldset');
591
        $this->setContext($fieldset, array('sections', 'system'));
592
        $fieldset->appendChild(new XMLElement('legend', __('Sorting')));
593
594
        $p = new XMLElement('p',
595
            __('Use %s syntax to order by page parameters.', array(
596
                '<code>{' . __('$param') . '}</code>'
597
            ))
598
        );
599
        $p->setAttribute('class', 'help');
600
        $fieldset->appendChild($p);
601
602
        $div = new XMLElement('div');
603
604
        $label = Widget::Label(__('Sort By'));
605
606
        $options = array(
607
            array('label' => __('Authors'), 'data-label' => 'authors', 'options' => array(
608
                    array('id', ($fields['source'] === 'authors' && $fields['sort'] === 'id'), __('Author ID')),
609
                    array('username', ($fields['source'] === 'authors' && $fields['sort'] === 'username'), __('Username')),
610
                    array('first-name', ($fields['source'] === 'authors' && $fields['sort'] === 'first-name'), __('First Name')),
611
                    array('last-name', ($fields['source'] === 'authors' && $fields['sort'] === 'last-name'), __('Last Name')),
612
                    array('email', ($fields['source'] === 'authors' && $fields['sort'] === 'email'), __('Email')),
613
                    array('status', ($fields['source'] === 'authors' && $fields['sort'] === 'status'), __('Status')),
614
                )
615
            ),
616
617
            array('label' => __('Navigation'), 'data-label' => 'navigation', 'options' => array(
618
                    array('id', ($fields['source'] === 'navigation' && $fields['sort'] === 'id'), __('Page ID')),
619
                    array('handle', ($fields['source'] === 'navigation' && $fields['sort'] === 'handle'), __('Handle')),
620
                    array('sortorder', ($fields['source'] === 'navigation' && $fields['sort'] === 'sortorder'), __('Sort Order')),
621
                )
622
            ),
623
        );
624
625
        foreach ($field_groups as $section_id => $section_data) {
626
            $optgroup = array('label' => General::sanitize($section_data['section']->get('name')), 'data-label' => 'section-' . $section_data['section']->get('id'), 'options' => array(
627
                array('system:id', ($fields['source'] === $section_id && $fields['sort'] === 'system:id'), __('System ID')),
628
                array('system:creation-date', ($fields['source'] === $section_id && ($fields['sort'] === 'system:creation-date' || $fields['sort'] === 'system:date')), __('System Creation Date')),
629
                array('system:modification-date', ($fields['source'] === $section_id && $fields['sort'] === 'system:modification-date'), __('System Modification Date')),
630
            ));
631
632 View Code Duplication
            if (is_array($section_data['fields']) && !empty($section_data['fields'])) {
633
                foreach ($section_data['fields'] as $input) {
634
                    if (!$input->isSortable()) {
635
                        continue;
636
                    }
637
638
                    $optgroup['options'][] = array(
639
                        $input->get('element_name'),
640
                        ($fields['source'] === $section_id && $input->get('element_name') === $fields['sort']),
641
                        $input->get('label')
642
                    );
643
                }
644
            }
645
646
            $options[] = $optgroup;
647
        }
648
649
        $label->appendChild(Widget::Select('fields[sort]', $options));
650
        $div->appendChild($label);
651
652
        $label = Widget::Label(__('Sort Order'));
653
        $label->setAttribute('class', 'ds-param');
654
655
        $input = Widget::Input('fields[order]', $fields['order'], 'text', array(
656
            'placeholder' => __('{$param}'),
657
            'data-search-types' => 'parameters',
658
            'data-trigger' => '{$'
659
        ));
660
        $label->appendChild($input);
661
        $div->appendChild($label);
662
663
        $orders = new XMLElement('ul');
664
        $orders->setAttribute('class', 'tags singular');
665
        $orders->setAttribute('data-interactive', 'data-interactive');
666
        $orders->appendChild(new XMLElement('li', 'asc'));
667
        $orders->appendChild(new XMLElement('li', 'desc'));
668
        $orders->appendChild(new XMLElement('li', 'random'));
669
        $div->appendChild($orders);
670
671
        $fieldset->appendChild($div);
672
        $this->Form->appendChild($fieldset);
673
674
        // Grouping
675
        $fieldset = new XMLElement('fieldset');
676
        $this->setContext($fieldset, array('sections', 'authors'));
677
        $fieldset->appendChild(new XMLElement('legend', __('Grouping')));
678
679
        $label = Widget::Label(__('Group By'));
680
        $options = array(
681
            array('', null, __('None')),
682
        );
683
684
        foreach ($field_groups as $section_id => $section_data) {
685
            $optgroup = array('label' => $section_data['section']->get('name'), 'data-label' => 'section-' . $section_data['section']->get('id'), 'options' => array());
686
687 View Code Duplication
            if (is_array($section_data['fields']) && !empty($section_data['fields'])) {
688
                foreach ($section_data['fields'] as $input) {
689
                    if (!$input->allowDatasourceOutputGrouping()) {
690
                        continue;
691
                    }
692
693
                    $optgroup['options'][] = array($input->get('id'), ($fields['source'] === $section_id && $fields['group'] === $input->get('id')), $input->get('label'));
694
                }
695
            }
696
697
            $options[] = $optgroup;
698
        }
699
700
        $label->appendChild(Widget::Select('fields[group]', $options));
701
        $fieldset->appendChild($label);
702
703
        $this->Form->appendChild($fieldset);
704
705
        // Pagination
706
        $fieldset = new XMLElement('fieldset');
707
        $this->setContext($fieldset, array('sections'));
708
        $fieldset->appendChild(new XMLElement('legend', __('Pagination')));
709
710
        $p = new XMLElement('p',
711
            __('Use %s syntax to limit by page parameters.', array(
712
                '<code>{' . __('$param') . '}</code>'
713
            ))
714
        );
715
        $p->setAttribute('class', 'help');
716
        $fieldset->appendChild($p);
717
718
        $group = new XMLElement('div');
719
        $group->setAttribute('class', 'two columns pagination');
720
721
        $label = Widget::Label(__('Entries per Page'));
722
        $label->setAttribute('class', 'column ds-param');
723
        $input = Widget::Input('fields[max_records]', isset($fields['max_records']) ? $fields['max_records'] : '10', 'text', array(
724
            'placeholder' => __('{$param}'),
725
            'data-search-types' => 'parameters',
726
            'data-trigger' => '{$'
727
        ));
728
        $label->appendChild($input);
729
        $group->appendChild($label);
730
731
        $label = Widget::Label(__('Page Number'));
732
        $label->setAttribute('class', 'column ds-param');
733
        $input = Widget::Input('fields[page_number]', $fields['page_number'], 'text', array(
734
            'placeholder' => __('{$param}'),
735
            'data-search-types' => 'parameters',
736
            'data-trigger' => '{$'
737
        ));
738
        $label->appendChild($input);
739
        $group->appendChild($label);
740
741
        $fieldset->appendChild($group);
742
743
        $label = Widget::Checkbox('fields[paginate_results]', $fields['paginate_results'], __('Enable pagination'));
744
        $fieldset->appendChild($label);
745
        $this->Form->appendChild($fieldset);
746
747
        // Content
748
        $fieldset = new XMLElement('fieldset');
749
        $this->setContext($fieldset, array('sections', 'authors'));
750
        $fieldset->appendChild(new XMLElement('legend', __('Content')));
751
752
        // XML
753
        $group = new XMLElement('div', null, array('class' => 'two columns'));
754
755
        $label = Widget::Label(__('Included Elements'));
756
        $label->setAttribute('class', 'column');
757
758
        $options = array(
759
            array('label' => __('Authors'), 'data-label' => 'authors', 'options' => array(
760
                    array('username', ($fields['source'] === 'authors' && in_array('username', $fields['xml_elements'])), 'username'),
761
                    array('name', ($fields['source'] === 'authors' && in_array('name', $fields['xml_elements'])), 'name'),
762
                    array('email', ($fields['source'] === 'authors' && in_array('email', $fields['xml_elements'])), 'email'),
763
                    array('author-token', ($fields['source'] === 'authors' && in_array('author-token', $fields['xml_elements'])), 'author-token'),
764
                    array('default-area', ($fields['source'] === 'authors' && in_array('default-area', $fields['xml_elements'])), 'default-area'),
765
            )),
766
        );
767
768
        foreach ($field_groups as $section_id => $section_data) {
769
            $optgroup = array(
770
                'label' => General::sanitize($section_data['section']->get('name')),
771
                'data-label' => 'section-' . $section_data['section']->get('id'),
772
                'options' => array(
773
                    array(
774
                        'system:pagination',
775
                        ($fields['source'] === $section_id && in_array('system:pagination', $fields['xml_elements'])),
776
                        'system: pagination'
777
                    ),
778
                    array(
779
                        'system:date',
780
                        ($fields['source'] === $section_id && in_array('system:date', $fields['xml_elements'])),
781
                        'system: date'
782
                    )
783
                )
784
            );
785
786
            if (is_array($section_data['fields']) && !empty($section_data['fields'])) {
787
                foreach ($section_data['fields'] as $field) {
788
                    $elements = $field->fetchIncludableElements();
789
790
                    if (is_array($elements) && !empty($elements)) {
791
                        foreach ($elements as $name) {
792
                            $selected = false;
793
794
                            if ($fields['source'] === $section_id && in_array($name, $fields['xml_elements'])) {
795
                                $selected = true;
796
                            }
797
798
                            $optgroup['options'][] = array($name, $selected, $name);
799
                        }
800
                    }
801
                }
802
            }
803
804
            $options[] = $optgroup;
805
        }
806
807
        $label->appendChild(Widget::Select('fields[xml_elements][]', $options, array('multiple' => 'multiple')));
808
        $group->appendChild($label);
809
810
        // Support multiple parameters
811
        if (!isset($fields['param'])) {
812
            $fields['param'] = array();
813
        } elseif (!is_array($fields['param'])) {
814
            $fields['param'] = array($fields['param']);
815
        }
816
817
        $label = Widget::Label(__('Parameters'));
818
        $label->setAttribute('class', 'column');
819
        $prefix = '$ds-' . (isset($this->_context['handle']) ? Lang::createHandle($fields['name']) : __('untitled')) . '.';
820
821
        $options = array(
822
            array('label' => __('Authors'), 'data-label' => 'authors', 'options' => array())
823
        );
824
825
        foreach (array('id', 'username', 'name', 'email', 'user_type') as $p) {
826
            $options[0]['options'][] = array(
827
                $p,
828
                ($fields['source'] === 'authors' && in_array($p, $fields['param'])),
829
                $prefix . $p,
830
                null,
831
                null,
832
                array(
833
                    'data-handle' => $p
834
                )
835
            );
836
        }
837
838
        foreach ($field_groups as $section_id => $section_data) {
839
            $optgroup = array('label' => $section_data['section']->get('name'), 'data-label' => 'section-' . $section_data['section']->get('id'), 'options' => array());
840
841
            foreach (array('id', 'creation-date', 'modification-date', 'author') as $p) {
842
                $option = array(
843
                    'system:' . $p,
844
                    ($fields['source'] === $section_id && in_array('system:' . $p, $fields['param'])),
845
                    $prefix . 'system-' . $p,
846
                    null,
847
                    null,
848
                    array(
849
                        'data-handle' => 'system-' . $p
850
                    )
851
                );
852
853
                // Handle 'system:date' as an output paramater (backwards compatibility)
854
                if ($p === 'creation-date') {
855
                    if ($fields['source'] === $section_id && in_array('system:date', $fields['param'])) {
856
                        $option[1] = true;
857
                    }
858
                }
859
860
                $optgroup['options'][] = $option;
861
            }
862
863
            if (is_array($section_data['fields']) && !empty($section_data['fields'])) {
864
                foreach ($section_data['fields'] as $input) {
865
                    if (!$input->allowDatasourceParamOutput()) {
866
                        continue;
867
                    }
868
869
                    $optgroup['options'][] = array(
870
                        $input->get('element_name'),
871
                        ($fields['source'] === $section_id && in_array($input->get('element_name'), $fields['param'])),
872
                        $prefix . $input->get('element_name'),
873
                        null,
874
                        null,
875
                        array(
876
                            'data-handle' => $input->get('element_name')
877
                        )
878
                    );
879
                }
880
            }
881
882
            $options[] = $optgroup;
883
        }
884
885
        $label->appendChild(Widget::Select('fields[param][]', $options, array('multiple' => 'multiple')));
886
        $group->appendChild($label);
887
888
        $fieldset->appendChild($group);
889
890
        // Associations
891
        $label = Widget::Checkbox('fields[associated_entry_counts]', $fields['associated_entry_counts'], __('Include a count of entries in associated sections'));
892
        $this->setContext($label, array('sections'));
893
        $fieldset->appendChild($label);
894
895
        // Encoding
896
        $label = Widget::Checkbox('fields[html_encode]', $fields['html_encode'], __('HTML-encode text'));
897
        $this->setContext($label, array('sections'));
898
        $fieldset->appendChild($label);
899
900
        $this->Form->appendChild($fieldset);
901
902
        // Static XML
903
        if (!isset($fields['static_xml'])) {
904
            $fields['static_xml'] = null;
905
        }
906
907
        $fieldset = new XMLElement('fieldset');
908
        $this->setContext($fieldset, array('static-xml'));
909
        $fieldset->appendChild(new XMLElement('legend', __('Static XML')));
910
        $p = new XMLElement('p', __('Enter valid XML, exclude XML declaration'));
911
        $p->setAttribute('class', 'help');
912
        $fieldset->appendChild($p);
913
914
        $label = Widget::Label();
915
        $label->appendChild(Widget::Textarea('fields[static_xml]', 12, 50, General::sanitize(stripslashes($fields['static_xml'])), array('class' => 'code', 'placeholder' => '<static>content</static>')));
916
917
        if (isset($this->_errors['static_xml'])) {
918
            $fieldset->appendChild(Widget::Error($label, $this->_errors['static_xml']));
919
        } else {
920
            $fieldset->appendChild($label);
921
        }
922
923
        $this->Form->appendChild($fieldset);
924
925
        // Connections
926
        $fieldset = new XMLElement('fieldset');
927
        $fieldset->setAttribute('class', 'settings');
928
        $fieldset->appendChild(new XMLElement('legend', __('Attach to Pages')));
929
        $p = new XMLElement('p', __('The data will only be available on the selected pages.'));
930
        $p->setAttribute('class', 'help');
931
        $fieldset->appendChild($p);
932
933
        $div = new XMLElement('div');
934
        $label = Widget::Label(__('Pages'));
935
936
        $pages = PageManager::fetch();
937
        $ds_handle = str_replace('-', '_', Lang::createHandle($fields['name']));
938
        $connections = ResourceManager::getAttachedPages(ResourceManager::RESOURCE_TYPE_DS, $ds_handle);
939
        $selected = array();
940
941
        foreach ($connections as $connection) {
942
            $selected[] = $connection['id'];
943
        }
944
945
        $options = array();
946
947 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...
948
            $options[] = array($page['id'], in_array($page['id'], $selected), PageManager::resolvePageTitle($page['id']));
949
        }
950
951
        $label->appendChild(Widget::Select('fields[connections][]', $options, array('multiple' => 'multiple')));
952
        $div->appendChild($label);
953
954
        $fieldset->appendChild($div);
955
        $this->Form->appendChild($fieldset);
956
957
958
        // Call the provided datasources to let them inject their filters
959
        // @todo Ideally when a new Datasource is chosen an AJAX request will fire
960
        // to get the HTML from the extension. This is hardcoded for now into
961
        // creating a 'big' page and then hiding the fields with JS
962
        if (!empty($providers)) {
963
            foreach ($providers as $providerClass => $provider) {
964
                call_user_func_array(array($providerClass, 'buildEditor'), array($this->Form, &$this->_errors, $fields, $handle));
965
            }
966
        }
967
968
        $div = new XMLElement('div');
969
        $div->setAttribute('class', 'actions');
970
        $div->appendChild(Widget::Input('action[save]', ($isEditing ? __('Save Changes') : __('Create Data Source')), 'submit', array('accesskey' => 's')));
971
972 View Code Duplication
        if ($isEditing) {
973
            $button = new XMLElement('button', __('Delete'));
974
            $button->setAttributeArray(array('name' => 'action[delete]', 'class' => 'button confirm delete', 'title' => __('Delete this data source'), 'type' => 'submit', 'accesskey' => 'd', 'data-message' => __('Are you sure you want to delete this data source?')));
975
            $div->appendChild($button);
976
        }
977
978
        $this->Form->appendChild($div);
979
    }
980
981
    public function __viewInfo()
982
    {
983
        $this->setPageType('form');
984
985
        $datasource = DatasourceManager::create($this->_context['handle'], array(), false);
986
        $about = $datasource->about();
987
988
        $this->setTitle(__('%1$s &ndash; %2$s &ndash; %3$s', array($about['name'], __('Data Source'), __('Symphony'))));
989
        $this->appendSubheading((($this->_context['action'] === 'info') ? $about['name'] : __('Untitled')));
990
        $this->insertBreadcrumbs(array(
991
            Widget::Anchor(__('Data Sources'), SYMPHONY_URL . '/blueprints/datasources/'),
992
        ));
993
        $this->Form->setAttribute('id', 'controller');
994
995
        $link = $about['author']['name'];
996
997 View Code Duplication
        if (isset($about['author']['website'])) {
998
            $link = Widget::Anchor($about['author']['name'], General::validateURL($about['author']['website']));
999
        } elseif (isset($about['author']['email'])) {
1000
            $link = Widget::Anchor($about['author']['name'], 'mailto:' . $about['author']['email']);
1001
        }
1002
1003
        foreach ($about as $key => $value) {
1004
            $fieldset = null;
1005
1006
            switch ($key) {
1007
                case 'author':
1008
                    if ($link) {
1009
                        $fieldset = new XMLElement('fieldset');
1010
                        $fieldset->appendChild(new XMLElement('legend', __('Author')));
1011
                        $fieldset->appendChild(new XMLElement('p', $link->generate(false)));
1012
                    }
1013
                    break;
1014
                case 'version':
1015
                    $fieldset = new XMLElement('fieldset');
1016
                    $fieldset->appendChild(new XMLElement('legend', __('Version')));
1017
                    $release_date = array_key_exists('release-date', $about) ? $about['release-date'] : filemtime(DatasourceManager::__getDriverPath($this->_context['handle']));
1018
1019
                    if (preg_match('/^\d+(\.\d+)*$/', $value)) {
1020
                        $fieldset->appendChild(new XMLElement('p', __('%1$s released on %2$s', array($value, DateTimeObj::format($release_date, __SYM_DATE_FORMAT__)))));
1021 View Code Duplication
                    } else {
1022
                        $fieldset->appendChild(new XMLElement('p', __('Created by %1$s at %2$s', array($value, DateTimeObj::format($release_date, __SYM_DATE_FORMAT__)))));
1023
                    }
1024
                    break;
1025
                case 'description':
1026
                    $fieldset = new XMLElement('fieldset');
1027
                    $fieldset->appendChild(new XMLElement('legend', __('Description')));
1028
                    $fieldset->appendChild((is_object($about['description']) ? $about['description'] : new XMLElement('p', $about['description'])));
1029
                    break;
1030
                case 'example':
1031
                    if (is_callable(array($datasource, 'example'))) {
1032
                        $fieldset = new XMLElement('fieldset');
1033
                        $fieldset->appendChild(new XMLElement('legend', __('Example XML')));
1034
1035
                        $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...
1036
1037
                        if (is_object($example)) {
1038
                            $fieldset->appendChild($example);
1039
                        } else {
1040
                            $p = new XMLElement('p');
1041
                            $p->appendChild(new XMLElement('pre', '<code>' . str_replace('<', '&lt;', $example) . '</code>'));
1042
                            $fieldset->appendChild($p);
1043
                        }
1044
                    }
1045
                    break;
1046
            }
1047
1048
            if ($fieldset) {
1049
                $fieldset->setAttribute('class', 'settings');
1050
                $this->Form->appendChild($fieldset);
1051
            }
1052
        }
1053
1054
        // Display source
1055
        $file = DatasourceManager::__getClassPath($this->_context['handle']) . '/data.' . $this->_context['handle'] . '.php';
1056
1057
        if (file_exists($file)) {
1058
            $fieldset = new XMLElement('fieldset');
1059
            $fieldset->setAttribute('class', 'settings');
1060
            $fieldset->appendChild(new XMLElement('legend', __('Source')));
1061
1062
            $source = file_get_contents($file);
1063
            $code = new XMLElement('code', htmlspecialchars($source));
1064
            $pre = new XMLElement('pre');
1065
            $pre->appendChild($code);
1066
1067
            $fieldset->appendChild($pre);
1068
            $this->Form->appendChild($fieldset);
1069
        }
1070
    }
1071
1072
    public function __actionIndex($resource_type)
1073
    {
1074
        return parent::__actionIndex(ResourceManager::RESOURCE_TYPE_DS);
1075
    }
1076
1077 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...
1078
    {
1079
        if (array_key_exists('save', $_POST['action'])) {
1080
            return $this->__formAction();
1081
        } elseif (array_key_exists('delete', $_POST['action'])) {
1082
            /**
1083
             * Prior to deleting the Datasource file. Target file path is provided.
1084
             *
1085
             * @delegate DatasourcePreDelete
1086
             * @since Symphony 2.2
1087
             * @param string $context
1088
             * '/blueprints/datasources/'
1089
             * @param string $file
1090
             *  The path to the Datasource file
1091
             */
1092
            Symphony::ExtensionManager()->notifyMembers('DatasourcePreDelete', '/blueprints/datasources/', array('file' => DATASOURCES . "/data." . $this->_context['handle'] . ".php"));
1093
1094
            if (!General::deleteFile(DATASOURCES . '/data.' . $this->_context['handle'] . '.php')) {
1095
                $this->pageAlert(
1096
                    __('Failed to delete %s.', array('<code>' . $this->_context['handle'] . '</code>'))
1097
                    . ' ' . __('Please check permissions on %s.', array('<code>/workspace/data-sources</code>')),
1098
                    Alert::ERROR
1099
                );
1100
            } else {
1101
                $pages = ResourceManager::getAttachedPages(ResourceManager::RESOURCE_TYPE_DS, $this->_context['handle']);
1102
1103
                foreach ($pages as $page) {
1104
                    ResourceManager::detach(ResourceManager::RESOURCE_TYPE_DS, $this->_context['handle'], $page['id']);
1105
                }
1106
1107
                redirect(SYMPHONY_URL . '/blueprints/datasources/');
1108
            }
1109
        }
1110
    }
1111
1112
    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...
1113
    {
1114
        if (array_key_exists('save', $_POST['action'])) {
1115
            return $this->__formAction();
1116
        }
1117
    }
1118
1119
    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...
1120
    {
1121
        $fields = $_POST['fields'];
1122
        $this->_errors = array();
1123
        $providers = Symphony::ExtensionManager()->getProvidersOf(iProvider::DATASOURCE);
1124
        $providerClass = null;
1125
1126 View Code Duplication
        if (trim($fields['name']) === '') {
1127
            $this->_errors['name'] = __('This is a required field');
1128
        }
1129
1130
        if ($fields['source'] === 'static_xml') {
1131
            if (trim($fields['static_xml']) === '') {
1132
                $this->_errors['static_xml'] = __('This is a required field');
1133
            } else {
1134
                $xml_errors = null;
1135
1136
                General::validateXML($fields['static_xml'], $xml_errors, false, new XsltProcess());
1137
1138
                if (!empty($xml_errors)) {
1139
                    $this->_errors['static_xml'] = __('XML is invalid.');
1140
                }
1141
            }
1142
        } elseif (is_numeric($fields['source'])) {
1143 View Code Duplication
            if (strlen(trim($fields['max_records'])) === 0 || (is_numeric($fields['max_records']) && $fields['max_records'] < 1)) {
1144
                if ($fields['paginate_results'] === 'yes') {
1145
                    $this->_errors['max_records'] = __('A result limit must be set');
1146
                }
1147
            } elseif (!self::__isValidPageString($fields['max_records'])) {
1148
                $this->_errors['max_records'] = __('Must be a valid number or parameter');
1149
            }
1150
1151 View Code Duplication
            if (strlen(trim($fields['page_number'])) === 0 || (is_numeric($fields['page_number']) && $fields['page_number'] < 1)) {
1152
                if ($fields['paginate_results'] === 'yes') {
1153
                    $this->_errors['page_number'] = __('A page number must be set');
1154
                }
1155
            } elseif (!self::__isValidPageString($fields['page_number'])) {
1156
                $this->_errors['page_number'] = __('Must be a valid number or parameter');
1157
            }
1158
1159
            // See if a Provided Datasource is saved
1160 View Code Duplication
        } elseif (!empty($providers)) {
1161
            foreach ($providers as $providerClass => $provider) {
1162
                if ($fields['source'] === call_user_func(array($providerClass, 'getSource'))) {
1163
                    call_user_func_array(array($providerClass, 'validate'), array(&$fields, &$this->_errors));
1164
                    break;
1165
                }
1166
1167
                unset($providerClass);
1168
            }
1169
        }
1170
1171
        $classname = Lang::createHandle($fields['name'], 255, '_', false, true, array('@^[^a-z\d]+@i' => '', '/[^\w-\.]/i' => ''));
1172
        $rootelement = str_replace('_', '-', $classname);
1173
1174
        // Check to make sure the classname is not empty after handlisation.
1175 View Code Duplication
        if (empty($classname) && !isset($this->_errors['name'])) {
1176
            $this->_errors['name'] = __('Please ensure name contains at least one Latin-based character.', array($classname));
1177
        }
1178
1179
        $file = DATASOURCES . '/data.' . $classname . '.php';
1180
1181
        $isDuplicate = false;
1182
        $queueForDeletion = null;
1183
1184 View Code Duplication
        if ($this->_context['action'] === 'new' && is_file($file)) {
1185
            $isDuplicate = true;
1186
        } elseif ($this->_context['action'] === 'edit') {
1187
            $existing_handle = $this->_context['handle'];
1188
1189
            if ($classname !== $existing_handle && is_file($file)) {
1190
                $isDuplicate = true;
1191
            } elseif ($classname !== $existing_handle) {
1192
                $queueForDeletion = DATASOURCES . '/data.' . $existing_handle . '.php';
1193
            }
1194
        }
1195
1196
        // Duplicate
1197 View Code Duplication
        if ($isDuplicate) {
1198
            $this->_errors['name'] = __('A Data source with the name %s already exists', array('<code>' . $classname . '</code>'));
1199
        }
1200
1201
        if (empty($this->_errors)) {
1202
            $filters = array();
1203
            $elements = null;
1204
            $source = $fields['source'];
1205
            $params = array(
1206
                'rootelement' => $rootelement
1207
            );
1208
1209
            $about = array(
1210
                'name' => $fields['name'],
1211
                'version' => 'Symphony ' . Symphony::Configuration()->get('version', 'symphony'),
1212
                'release date' => DateTimeObj::getGMT('c'),
1213
                'author name' => Symphony::Author()->getFullName(),
1214
                'author website' => URL,
1215
                'author email' => Symphony::Author()->get('email')
1216
            );
1217
1218
            // If there is a provider, get their template
1219
            if ($providerClass) {
1220
                $dsShell = file_get_contents(call_user_func(array($providerClass, 'getTemplate')));
1221
            } else {
1222
                $dsShell = file_get_contents($this->getTemplate('blueprints.datasource'));
1223
            }
1224
1225
            // Author metadata
1226
            self::injectAboutInformation($dsShell, $about);
1227
1228
            // Do dependencies, the template file must have <!-- CLASS NAME -->
1229
            $dsShell = str_replace('<!-- CLASS NAME -->', $classname, $dsShell);
1230
1231
            // If there is a provider, let them do the prepartion work
1232
            if ($providerClass) {
1233
                $dsShell = call_user_func(array($providerClass, 'prepare'), $fields, $params, $dsShell);
1234
            } else {
1235
                switch ($source) {
1236
                    case 'authors':
1237
                        $extends = 'AuthorDatasource';
1238
                        if (isset($fields['filter']['author'])) {
1239
                            $filters = $fields['filter']['author'];
1240
                        }
1241
1242
                        $elements = $fields['xml_elements'];
1243
1244
                        $params['order'] = $fields['order'];
1245
                        $params['redirectonempty'] = $fields['redirect_on_empty'];
1246
                        $params['redirectonforbidden'] = $fields['redirect_on_forbidden'];
1247
                        $params['redirectonrequired'] = $fields['redirect_on_required'];
1248
                        $params['requiredparam'] = trim($fields['required_url_param']);
1249
                        $params['negateparam'] = trim($fields['negate_url_param']);
1250
                        $params['paramoutput'] = $fields['param'];
1251
                        $params['sort'] = $fields['sort'];
1252
1253
                        break;
1254
                    case 'navigation':
1255
                        $extends = 'NavigationDatasource';
1256
                        if (isset($fields['filter']['navigation'])) {
1257
                            $filters = $fields['filter']['navigation'];
1258
                        }
1259
1260
                        $params['order'] = $fields['order'];
1261
                        $params['redirectonempty'] = $fields['redirect_on_empty'];
1262
                        $params['redirectonforbidden'] = $fields['redirect_on_forbidden'];
1263
                        $params['redirectonrequired'] = $fields['redirect_on_required'];
1264
                        $params['requiredparam'] = trim($fields['required_url_param']);
1265
                        $params['negateparam'] = trim($fields['negate_url_param']);
1266
1267
                        break;
1268
                    case 'static_xml':
1269
                        $extends = 'StaticXMLDatasource';
1270
                        $fields['static_xml'] = trim($fields['static_xml']);
1271
1272
                        if (preg_match('/^<\?xml/i', $fields['static_xml']) === true) {
1273
                            // Need to remove any XML declaration
1274
                            $fields['static_xml'] = preg_replace('/^<\?xml[^>]+>/i', null, $fields['static_xml']);
1275
                        }
1276
1277
                        $params['static'] = sprintf(
1278
                            '%s',
1279
                            trim($fields['static_xml'])
1280
                        );
1281
                        break;
1282
                    default:
1283
                        $extends = 'SectionDatasource';
1284
                        $elements = $fields['xml_elements'];
1285
1286
                        if (is_array($fields['filter']) && !empty($fields['filter'])) {
1287
                            $filters = array();
1288
1289
                            foreach ($fields['filter'] as $f) {
1290
                                foreach ($f as $key => $val) {
1291
                                    $filters[$key] = $val;
1292
                                }
1293
                            }
1294
                        }
1295
1296
                        $params['order'] = $fields['order'];
1297
                        $params['group'] = $fields['group'];
1298
                        $params['paginateresults'] = $fields['paginate_results'];
1299
                        $params['limit'] = $fields['max_records'];
1300
                        $params['startpage'] = $fields['page_number'];
1301
                        $params['redirectonempty'] = $fields['redirect_on_empty'];
1302
                        $params['redirectonforbidden'] = $fields['redirect_on_forbidden'];
1303
                        $params['redirectonrequired'] = $fields['redirect_on_required'];
1304
                        $params['requiredparam'] = trim($fields['required_url_param']);
1305
                        $params['negateparam'] = trim($fields['negate_url_param']);
1306
                        $params['paramoutput'] = $fields['param'];
1307
                        $params['sort'] = $fields['sort'];
1308
                        $params['htmlencode'] = $fields['html_encode'];
1309
                        $params['associatedentrycounts'] = $fields['associated_entry_counts'];
1310
1311
                        break;
1312
                }
1313
1314
                $this->__injectVarList($dsShell, $params);
1315
                $this->__injectIncludedElements($dsShell, $elements);
1316
                self::injectFilters($dsShell, $filters);
1317
1318
                if (preg_match_all('@(\$ds-[0-9a-z_\.\-]+)@i', $dsShell, $matches)) {
1319
                    $dependencies = General::array_remove_duplicates($matches[1]);
1320
                    $dsShell = str_replace('<!-- DS DEPENDENCY LIST -->', "'" . implode("', '", $dependencies) . "'", $dsShell);
1321
                }
1322
1323
                $dsShell = str_replace('<!-- CLASS EXTENDS -->', $extends, $dsShell);
1324
                $dsShell = str_replace('<!-- SOURCE -->', $source, $dsShell);
1325
            }
1326
1327
            if ($this->_context['action'] === 'new') {
1328
                /**
1329
                 * Prior to creating the Datasource, the file path where it will be written to
1330
                 * is provided and well as the contents of that file.
1331
                 *
1332
                 * @delegate DatasourcePreCreate
1333
                 * @since Symphony 2.2
1334
                 * @param string $context
1335
                 * '/blueprints/datasources/'
1336
                 * @param string $file
1337
                 *  The path to the Datasource file
1338
                 * @param string $contents
1339
                 *  The contents for this Datasource as a string passed by reference
1340
                 * @param array $params
1341
                 *  An array of all the `$dsParam*` values
1342
                 * @param array $elements
1343
                 *  An array of all the elements included in this datasource
1344
                 * @param array $filters
1345
                 *  An associative array of all the filters for this datasource with the key
1346
                 *  being the `field_id` and the value the filter.
1347
                 * @param array $dependencies
1348
                 *  An array of dependencies that this datasource has
1349
                 */
1350
                Symphony::ExtensionManager()->notifyMembers('DatasourcePreCreate', '/blueprints/datasources/', array(
1351
                    'file' => $file,
1352
                    'contents' => &$dsShell,
1353
                    'params' => $params,
1354
                    'elements' => $elements,
1355
                    'filters' => $filters,
1356
                    '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...
1357
                ));
1358
            } else {
1359
                /**
1360
                 * Prior to editing a Datasource, the file path where it will be written to
1361
                 * is provided and well as the contents of that file.
1362
                 *
1363
                 * @delegate DatasourcePreEdit
1364
                 * @since Symphony 2.2
1365
                 * @param string $context
1366
                 * '/blueprints/datasources/'
1367
                 * @param string $file
1368
                 *  The path to the Datasource file
1369
                 * @param string $contents
1370
                 *  The contents for this Datasource as a string passed by reference
1371
                 * @param array $dependencies
1372
                 *  An array of dependencies that this datasource has
1373
                 * @param array $params
1374
                 *  An array of all the `$dsParam*` values
1375
                 * @param array $elements
1376
                 *  An array of all the elements included in this datasource
1377
                 * @param array $filters
1378
                 *  An associative array of all the filters for this datasource with the key
1379
                 *  being the `field_id` and the value the filter.
1380
                 */
1381
                Symphony::ExtensionManager()->notifyMembers('DatasourcePreEdit', '/blueprints/datasources/', array(
1382
                    'file' => $file,
1383
                    'contents' => &$dsShell,
1384
                    'dependencies' => $dependencies,
1385
                    'params' => $params,
1386
                    'elements' => $elements,
1387
                    'filters' => $filters
1388
                ));
1389
            }
1390
1391
            // Remove left over placeholders
1392
            $dsShell = preg_replace(array('/<!--[\w ]++-->/', '/(\t+[\r\n]){2,}/', '/(\r\n){2,}/'), '$1', $dsShell);
1393
1394
            // Write the file
1395 View Code Duplication
            if (!is_writable(dirname($file)) || !General::writeFile($file, $dsShell, Symphony::Configuration()->get('write_mode', 'file'), 'w', true)) {
0 ignored issues
show
Documentation introduced by
\Symphony::Configuration...t('write_mode', 'file') is of type array|string, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1396
                $this->pageAlert(
1397
                    __('Failed to write Data source to disk.')
1398
                    . ' ' . __('Please check permissions on %s.', array('<code>/workspace/data-sources</code>')),
1399
                    Alert::ERROR
1400
                );
1401
1402
                // Write successful
1403
            } else {
1404
                if (function_exists('opcache_invalidate')) {
1405
                    opcache_invalidate($file, true);
1406
                }
1407
1408
                // Attach this datasources to pages
1409
                $connections = $fields['connections'];
1410
                ResourceManager::setPages(ResourceManager::RESOURCE_TYPE_DS, 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...
1411
1412
                // If the datasource has been updated and the name changed, then adjust all the existing pages that have the old datasource name
1413
                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...
1414
                    General::deleteFile($queueForDeletion);
1415
1416
                    // Update pages that use this DS
1417
                    $pages = PageManager::fetch(false, array('data_sources', 'id'), array("
1418
                        `data_sources` REGEXP '[[:<:]]" . $existing_handle . "[[:>:]]'
1419
                    "));
1420
1421
                    if (is_array($pages) && !empty($pages)) {
1422
                        foreach ($pages as $page) {
1423
                            $page['data_sources'] = preg_replace('/\b'.$existing_handle.'\b/i', $classname, $page['data_sources']);
1424
1425
                            PageManager::edit($page['id'], $page);
1426
                        }
1427
                    }
1428
                }
1429
1430
                if ($this->_context['action'] === 'new') {
1431
                    /**
1432
                     * After creating the Datasource, the path to the Datasource file is provided
1433
                     *
1434
                     * @delegate DatasourcePostCreate
1435
                     * @since Symphony 2.2
1436
                     * @param string $context
1437
                     * '/blueprints/datasources/'
1438
                     * @param string $file
1439
                     *  The path to the Datasource file
1440
                     */
1441
                    Symphony::ExtensionManager()->notifyMembers('DatasourcePostCreate', '/blueprints/datasources/', array(
1442
                        'file' => $file
1443
                    ));
1444
                } else {
1445
                    /**
1446
                     * After editing the Datasource, the path to the Datasource file is provided
1447
                     *
1448
                     * @delegate DatasourcePostEdit
1449
                     * @since Symphony 2.2
1450
                     * @param string $context
1451
                     * '/blueprints/datasources/'
1452
                     * @param string $file
1453
                     *  The path to the Datasource file
1454
                     * @param string $previous_file
1455
                     *  The path of the previous Datasource file in the case where a Datasource may
1456
                     *  have been renamed. To get the handle from this value, see
1457
                     *  `DatasourceManager::__getHandleFromFilename`
1458
                     */
1459
                    Symphony::ExtensionManager()->notifyMembers('DatasourcePostEdit', '/blueprints/datasources/', array(
1460
                        'file' => $file,
1461
                        'previous_file' => ($queueForDeletion) ? $queueForDeletion : null
1462
                    ));
1463
                }
1464
1465
                redirect(SYMPHONY_URL . '/blueprints/datasources/edit/'.$classname.'/'.($this->_context['action'] === 'new' ? 'created' : 'saved') . '/');
1466
            }
1467
        }
1468
    }
1469
1470
    public static function injectFilters(&$shell, array $filters)
1471
    {
1472
        if (empty($filters)) {
1473
            return;
1474
        }
1475
1476
        $placeholder = '<!-- FILTERS -->';
1477
        $string = 'public $dsParamFILTERS = array(' . PHP_EOL;
1478
1479
        foreach ($filters as $key => $val) {
1480
            if (trim($val) === '') {
1481
                continue;
1482
            }
1483
1484
            $string .= "        '$key' => '" . addslashes($val) . "'," . PHP_EOL;
1485
        }
1486
1487
        $string .= "    );" . PHP_EOL . "        " . $placeholder;
1488
1489
        $shell = str_replace($placeholder, trim($string), $shell);
1490
    }
1491
1492 View Code Duplication
    public static function injectAboutInformation(&$shell, array $details)
1493
    {
1494
        if (empty($details)) {
1495
            return;
1496
        }
1497
1498
        foreach ($details as $key => $val) {
1499
            $shell = str_replace('<!-- ' . strtoupper($key) . ' -->', addslashes($val), $shell);
1500
        }
1501
    }
1502
1503
    public function __injectIncludedElements(&$shell, $elements)
1504
    {
1505
        if (!is_array($elements) || empty($elements)) {
1506
            return;
1507
        }
1508
1509
        $placeholder = '<!-- INCLUDED ELEMENTS -->';
1510
        $shell = str_replace($placeholder, "public \$dsParamINCLUDEDELEMENTS = array(" . PHP_EOL . "        '" . implode("'," . PHP_EOL . "        '", $elements) . "'" . PHP_EOL . '    );' . PHP_EOL . "    " . $placeholder, $shell);
1511
    }
1512
1513
    public function __injectVarList(&$shell, $vars)
1514
    {
1515
        if (!is_array($vars) || empty($vars)) {
1516
            return;
1517
        }
1518
1519
        $var_list = null;
1520
1521
        foreach ($vars as $key => $val) {
1522
            if (is_array($val)) {
1523
                $val = "array(" . PHP_EOL . "        '" . implode("'," . PHP_EOL . "        '", $val) . "'" . PHP_EOL . '        );';
1524
                $var_list .= '    public $dsParam' . strtoupper($key) . ' = ' . $val . PHP_EOL;
1525
            } elseif (trim($val) !== '') {
1526
                $var_list .= '    public $dsParam' . strtoupper($key) . " = '" . addslashes($val) . "';" . PHP_EOL;
1527
            }
1528
        }
1529
1530
        $placeholder = '<!-- VAR LIST -->';
1531
        $shell = str_replace($placeholder, trim($var_list) . PHP_EOL . "    " . $placeholder, $shell);
1532
    }
1533
1534
    public function __appendAuthorFilter(&$wrapper, $h4_label, $name, $value = null, $templateOnly = true)
1535
    {
1536
        if (!$templateOnly) {
1537
            $li = new XMLElement('li');
1538
            $li->setAttribute('class', 'unique');
1539
            $li->setAttribute('data-type', $name);
1540
            $li->appendChild(new XMLElement('header', '<h4>' . $h4_label . '</h4>'));
1541
            $label = Widget::Label(__('Value'));
1542
            $label->appendChild(Widget::Input('fields[filter][author]['.$name.']', General::sanitize($value)));
1543
            $li->appendChild($label);
1544
1545
            $wrapper->appendChild($li);
1546
        }
1547
1548
        $li = new XMLElement('li');
1549
        $li->setAttribute('class', 'unique template');
1550
        $li->setAttribute('data-type', $name);
1551
        $li->appendChild(new XMLElement('header', '<h4>' . $h4_label . '</h4>'));
1552
        $label = Widget::Label(__('Value'));
1553
        $label->appendChild(Widget::Input('fields[filter][author]['.$name.']'));
1554
        $li->appendChild($label);
1555
1556
        $wrapper->appendChild($li);
1557
    }
1558
1559
    private static function __isValidPageString($string)
1560
    {
1561
        return (bool)preg_match('/^\{\$[\w-]+(.[\w]+(-[\w]+)?){0,1}\}|[\d]+$/', $string);
1562
    }
1563
1564
    /**
1565
     * Given a `$url` and `$timeout`, this function will use the `Gateway`
1566
     * class to determine that it is a valid URL and returns successfully
1567
     * before the `$timeout`. If it does not, an error message will be
1568
     * returned, otherwise true.
1569
     *
1570
     * @since Symphony 2.3
1571
     * @param string $url
1572
     * @param integer $timeout
1573
     *  If not provided, this will default to 6 seconds
1574
     * @param string $error
1575
     *  If this function returns false, this variable will be populated with the
1576
     *  error message.
1577
     * @return array|boolean
1578
     *  Returns an array with the 'data' if it is a valid URL, otherwise a string
1579
     *  containing an error message.
1580
     */
1581
    public static function __isValidURL($url, $timeout = 6, &$error)
1582
    {
1583
        if (!filter_var($url, FILTER_VALIDATE_URL)) {
1584
            $error = __('Invalid URL');
1585
            return false;
1586
        }
1587
1588
        // Check that URL was provided
1589
        $gateway = new Gateway;
1590
        $gateway->init($url);
1591
        $gateway->setopt('TIMEOUT', $timeout);
1592
        $data = $gateway->exec();
1593
1594
        $info = $gateway->getInfoLast();
1595
1596
        // 28 is CURLE_OPERATION_TIMEDOUT
1597
        if ($info['curl_error'] === 28) {
1598
            $error = __('Request timed out. %d second limit reached.', array($timeout));
1599
            return false;
1600
        } elseif ($data === false || $info['http_code'] !== 200) {
1601
            $error = __('Failed to load URL, status code %d was returned.', array($info['http_code']));
1602
            return false;
1603
        }
1604
1605
        return array('data' => $data);
1606
    }
1607
1608
    /**
1609
     * Set Data Source context
1610
     *
1611
     * @since Symphony 2.3.3
1612
     * @param XMLElement $element
1613
     * @param array $context
1614
     */
1615
    public function setContext(&$element, $context)
1616
    {
1617
        $element->setAttribute('class', 'settings contextual');
1618
        $element->setAttribute('data-context', implode(' ', (array)$context));
1619
    }
1620
}
1621