Issues (72)

Security Analysis    no request data  

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

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

SettingsPage.php (6 issues)

Upgrade to new PHP Analysis Engine

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

1
<?php
2
3
namespace Amarkal\Settings;
4
5
/**
6
 * Implements a settings page.
7
 */
8
class SettingsPage
9
{   
0 ignored issues
show
The opening class brace should be on a newline by itself.
Loading history...
10
    /**
11
     * Configuration array 
12
     * 
13
     * @var array 
14
     */
15
    private $config;
16
    
17
    /**
18
     * The UI form instance
19
     * 
20
     * @var Amarkal\UI\Form 
21
     */
22
    private $form;
23
24
    /**
25
     * The list of arguments for each section 
26
     *
27
     * @var array
28
     */
29
    private $sections;
30
31
    /**
32
     * The list of fields for the entire settings page
33
     *
34
     * @var Amarkal\UI\ComponentList
35
     */
36
    private $fields;
37
38
    /**
39
     * An instance of the current field values (either the from the database, or the defaults)
40
     *
41
     * @var array
42
     */
43
    private $values;
44
    
45
    /**
46
     * Set the config, create a form instance and add actions.
47
     * 
48
     * @param array $config
49
     */
50
    public function __construct( array $config = array() ) 
51
    {
52
        $this->config   = array_merge($this->default_args(), $config);
53
        $this->fields   = new \Amarkal\UI\ComponentList();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Amarkal\UI\ComponentList() of type object<Amarkal\UI\ComponentList> is incompatible with the declared type object<Amarkal\Settings\Amarkal\UI\ComponentList> of property $fields.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
54
        $this->sections = array();
55
        $this->form     = new \Amarkal\UI\Form($this->fields);
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Amarkal\UI\Form($this->fields) of type object<Amarkal\UI\Form> is incompatible with the declared type object<Amarkal\Settings\Amarkal\UI\Form> of property $form.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
56
        
57
        \add_action('admin_menu', array($this,'add_submenu_page'));
58
        \add_action('admin_enqueue_scripts', array($this,'enqueue_scripts'));
59
    }
60
61
    /**
62
     * Add a section to this settings page
63
     *
64
     * @param array $args
65
     * @return void
66
     */
67
    public function add_section( array $args )
68
    {
69
        $args = \array_merge($this->default_section_args(), $args);
70
        $slug = $args['slug'];
71
        if(array_key_exists($slug, $this->sections))
72
        {
73
            throw new \RuntimeException("A section with '$slug' has already been created for this page.");  
74
        }
75
        $this->sections[$slug] = $args;
76
    }
77
78
    /**
79
     * Add a settings field to this settings page
80
     *
81
     * @param array $args
82
     * @return void
83
     */
84
    public function add_field( array $args )
85
    {
86
        $this->fields->add_component($args);
87
    }
88
89
    /**
90
     * Get the current values for all fields from the database, or the default values if none exists
91
     *
92
     * @return array
93
     */
94
    public function get_field_values()
95
    {
96
        if(!isset($this->values))
97
        {
98
            $this->values = array_merge($this->form->reset(), $this->get_old_instance());
99
        }
100
        return $this->values;
101
    }
102
103
    /**
104
     * Get the value of the given field from the database, or the default value if none exists
105
     *
106
     * @param string $name
107
     * @return any
108
     */
109
    public function get_field_value( $name )
110
    {
111
        $values = $this->get_field_values();
112
        return $values[$name];
113
    }
114
    
115
    /**
116
     * Internally used to add a submenu page for this settings page
117
     */
118
    public function add_submenu_page()
119
    {
120
        \add_submenu_page(
121
            $this->config['parent_slug'], 
122
            $this->config['title'], 
123
            $this->config['menu_title'], 
124
            $this->config['capability'],
125
            $this->config['slug'],
126
            array($this, 'render')
127
        );
128
    }
129
    
130
    /**
131
     * Conditionally enqueue settings scripts and styles if the calling page is
132
     * a settings page.
133
     */
134
    public function enqueue_scripts()
135
    {
136
        // Only enqueue styles & scripts if this is a settings page
137
        if($this->config['slug'] === filter_input(INPUT_GET, 'page'))
138
        {
139
            \wp_enqueue_style('amarkal-settings');
140
            \wp_enqueue_script('amarkal-settings');
141
        }
142
    }
143
    
144
    /**
145
     * Render the settings page
146
     */
147
    public function render()
148
    {
149
        $this->form->update($this->get_old_instance());
150
        include __DIR__.'/SettingsPage.phtml';
151
        \add_filter('admin_footer_text', array($this, 'footer_credits'));
152
    }
153
    
154
    /**
155
     * Ajax callback internally used to update options values for the given 
156
     * settings page.
157
     * 
158
     * @param array $new_instance
159
     * @return array
160
     */
161 View Code Duplication
    public function update( $new_instance )
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
162
    {
163
        if($this->can_update())
164
        {
165
            $old_instance = $this->get_old_instance();
166
            $this->values = $this->form->update($new_instance, $old_instance);
167
            
168
            \update_option($this->config['slug'], $this->values);
169
170
            return $this->results_array(
171
                $this->get_errors(),
172
                $this->values
173
            );
174
        }
175
        return $this->results_array(
176
            array("You don't have permission to manage options on this site")
177
        );
178
    }
179
    
180
    /**
181
     * Ajax callback internally used to reset all component values to their 
182
     * defaults for the given settings page.
183
     * 
184
     * @return type
185
     */
186
    public function reset()
187
    {
188
        if($this->can_update())
189
        {
190
            \delete_option($this->config['slug']);
191
            $this->values = $this->form->reset();
192
193
            return $this->results_array(
194
                array(),
195
                $this->values
196
            );
197
        }
198
        return $this->results_array(
199
            array("You don't have permission to manage options on this site")
200
        );
201
    }
202
203
    /**
204
     * Ajax callback internally used to reset all component values to their 
205
     * defaults for the given settings section.
206
     *
207
     * @param string $slug
208
     * @return void
209
     */
210 View Code Duplication
    public function reset_section( $slug ) 
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
211
    {
212
        if($this->can_update())
213
        {
214
            $old_instance = $this->get_old_instance();
215
            $final_instance = $this->form->reset_components($this->get_section_fields($slug));
216
            
217
            // Array merge is needed in order not to delete fields from other sections
218
            // since the $final_instance only contains the fields that were reset
219
            $this->values = array_merge($old_instance, $final_instance);
220
            \update_option($this->config['slug'], $this->values);
221
222
            return $this->results_array(
223
                array(),
224
                $this->values
225
            );
226
        }
227
        return $this->results_array(
228
            array("You don't have permission to manage options on this site")
229
        );
230
    }
231
    
232
    /**
233
     * Renders Amarkal's credits on the page's footer.
234
     */
235
    public function footer_credits()
236
    {
237
        echo '<span id="footer-thankyou">Created with <a href="https://github.com/askupasoftware/amarkal-settings">amarkal-settings</a>, a module within the <a href="https://github.com/askupasoftware/amarkal">Amarkal Framework</a></span>';
238
    }
239
240
    /**
241
     * Get the component corresponding to the given name
242
     *
243
     * @param [string] $name
0 ignored issues
show
The doc-type [string] could not be parsed: Unknown type name "" at position 0. [(view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
244
     * @throws RuntimeException when the component cannot be found
245
     * @return void
246
     */
247
    public function get_component($name)
248
    {
249
        return $this->form->get_component_list()->get_by_name($name);
250
    }
251
252
    /**
253
     * Get all the fields for the given section
254
     *
255
     * @param string $slug
256
     * @return array
257
     */
258
    private function get_section_fields($slug)
259
    {
260
        $fields = array();
261
        foreach($this->fields->get_all() as $c)
262
        {
263
            if($c->section === $slug)
264
            {
265
                $fields[] = $c;
266
            }
267
        }
268
        return $fields;
269
    }
270
    
271
    /**
272
     * Get all errors from the form instance.
273
     * 
274
     * @return array
275
     */
276
    private function get_errors()
277
    {
278
        $errors = array();
279
        foreach($this->form->get_errors() as $name => $error)
280
        {
281
            $errors[$name] = $error;
282
        }
283
        return $errors;
284
    }
285
    
286
    /**
287
     * Generates a results array to be returned when an Ajax request is made.
288
     * 
289
     * @param array $errors The list of errors
290
     * @param array $values The list of values
291
     * @return array
292
     */
293
    private function results_array( $errors = array(), $values = '' )
294
    {
295
        return array(
296
            'values' => $values,
297
            'errors' => $errors
298
        );
299
    }
300
    
301
    /**
302
     * Check if the current user has the required privileges to update the 
303
     * settings values.
304
     * 
305
     * @return boolean
306
     */
307
    private function can_update()
308
    {
309
        return \current_user_can($this->config['capability']);
310
    }
311
    
312
    /**
313
     * Get the old instance from the database.
314
     * 
315
     * @return array
316
     */
317
    private function get_old_instance()
318
    {
319
        return \get_option($this->config['slug'], array());
320
    }
321
    
322
    /**
323
     * The default config arguments array for a page.
324
     * 
325
     * @return array
326
     */
327
    private function default_args()
328
    {
329
        return array(
330
            'parent_slug'    => '',
331
            'slug'           => '',
332
            'title'          => '',
333
            'subtitle'       => '',
334
            'menu_title'     => '',
335
            'capability'     => 'manage_options',
336
            'footer_html'    => '',
337
            'subfooter_html' => ''
338
        );
339
    }
340
341
    /**
342
     * The default config arguments array for a section.
343
     *
344
     * @return void
345
     */
346
    private function default_section_args()
347
    {
348
        return array(
349
            'slug'           => '',
350
            'title'          => '',
351
            'subtitle'       => ''
352
        );
353
    }
354
}