Issues (10)

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.

Manager.php (5 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\Metabox;
4
5
/**
6
 * Metabox Manager adds metaboxes to WordPress posts
7
 */
8
class Manager
9
{
10
    /**
11
     * @var Singleton The reference to *Singleton* instance of this class
12
     */
13
    private static $instance;
14
    
15
    /**
16
     * @var Array Stores all the registered metaboxes
17
     */
18
    private $metaboxes = array();
19
    
20
    /**
21
     * Security nonce action
22
     */
23
    const NONCE_ACTION = 'amarkal_metabox';
24
    
25
    /**
26
     * Returns the *Singleton* instance of this class.
27
     *
28
     * @return Singleton The *Singleton* instance.
29
     */
30
    public static function get_instance()
31
    {
32
        if( null === static::$instance ) 
0 ignored issues
show
Since $instance is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $instance to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
33
        {
34
            static::$instance = new static();
0 ignored issues
show
Since $instance is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $instance to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
Documentation Bug introduced by
It seems like new static() of type this<Amarkal\Metabox\Manager> is incompatible with the declared type object<Amarkal\Metabox\Singleton> of property $instance.

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...
35
        }
36
        return static::$instance;
0 ignored issues
show
Since $instance is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $instance to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
Bug Compatibility introduced by
The expression static::$instance; of type Amarkal\Metabox\Manager|Amarkal\Metabox\Singleton adds the type Amarkal\Metabox\Manager to the return on line 36 which is incompatible with the return type documented by Amarkal\Metabox\Manager::get_instance of type Amarkal\Metabox\Singleton.
Loading history...
37
    }
38
    
39
    /**
40
     * Private constructor to prevent instantiation
41
     */
42
    private function __construct() 
43
    {
44
        $this->init();
45
    }
46
    
47
    /**
48
     * Add a metabox.
49
     * 
50
     * @param string $id
51
     * @param array $args
52
     * @throws \RuntimeException if the given metabox id has already been registered
53
     */
54
    public function add( $id, array $args )
55
    {
56
        if( !in_array($id, $this->metaboxes) )
57
        {
58
            $this->metaboxes[$id] = array_merge($this->default_args(), $args);
59
            $this->metaboxes[$id]['form'] = new \Amarkal\UI\Form($args['fields']);
60
        }
61
        else throw new \RuntimeException("A metabox with id '$id' has already been registered.");
62
    }
63
    
64
    /**
65
     * Render a metabox.
66
     * 
67
     * @param WP_Post $post
68
     * @param array $args
69
     */
70
    public function render( $post, $args )
71
    {
72
        $metabox = $this->metaboxes[$args['id']];
73
        
74
        // Print the errors from the previous processing
75
        $this->print_errors($post->ID, $metabox);
76
        
77
        // Update component values before rendering
78
        $this->update_form($metabox, $post->ID);
79
        
80
        // Render the metabox with a nonce
81
        wp_nonce_field(self::NONCE_ACTION, $args['id'].'_nonce');
82
        include __DIR__.'/Form.phtml';
83
    }
84
    
85
    /**
86
     * Internally used to register metaboxes.
87
     */
88
    public function add_meta_boxes()
89
    {
90
        foreach( $this->metaboxes as $id => $args )
91
        {
92
            \add_meta_box(
93
                $id,
94
                $args['title'],
95
                array($this, 'render'),
96
                $args['screen'],
97
                $args['context'],
98
                $args['priority']
99
            );
100
        }
101
    }
102
    
103
    /**
104
     * Save metaboxes data for a given page.
105
     * 
106
     * @param number $post_id
107
     */
108
    public function save_meta_boxes( $post_id )
109
    {
110
        /**
111
         * A note on security:
112
         * 
113
         * We need to verify this came from the our screen and with proper authorization,
114
         * because save_post can be triggered at other times. since metaboxes can 
115
         * be removed - by having a nonce field in only one metabox there is no 
116
         * guarantee the nonce will be there. By placing a nonce field in each 
117
         * metabox you can check if data from that metabox has been sent 
118
         * (and is actually from where you think it is) prior to processing any data.
119
         * @see http://wordpress.stackexchange.com/a/49460/25959
120
         */
121
 
122
        $post_type = filter_input(INPUT_POST, 'post_type');
123
        
124
        /**
125
         * Bail if this is an autosave, or if the current user does not have 
126
         * sufficient permissions
127
         */
128
        if( (defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE) ||
129
            (null !== $post_type && !current_user_can('edit_'.$post_type, $post_id)) ) 
130
        {
131
            return;
132
        }
133
134
        // Update the meta fields.
135
        foreach( $this->metaboxes as $id => $metabox )
136
        {
137
            $this->save_meta_box( $post_id, $id, $metabox );
138
        }
139
    }
140
    
141
    /**
142
     * Save the data of a single metabox.
143
     * 
144
     * @param number $post_id
145
     * @param string $id
146
     * @param array $metabox
147
     */
148
    public function save_meta_box( $post_id, $id, $metabox )
149
    {
150
        $nonce_name   = $id.'_nonce';
151
        $nonce_value  = filter_input(INPUT_POST, $nonce_name);
152
        $new_instance = filter_input_array(INPUT_POST);
153
        
154
        // Check if our nonce is set and verify it
155
        if( null === $nonce_value || !wp_verify_nonce($nonce_value, self::NONCE_ACTION) ) 
156
        {
157
            return $post_id;
158
        }
159
160
        $this->update_form($metabox, $post_id, $new_instance);
161
    }
162
    
163
    /**
164
     * Get the value of the given field.
165
     * 
166
     * @param string $metabox_id
167
     * @param string $name
168
     * @param number $post_id
169
     * @return mix
170
     */
171
    public function get_meta_box_value( $metabox_id, $name, $post_id )
172
    {
173
        // Check if the meta key exists
174
        if( in_array($name, get_post_custom_keys($post_id)) )
175
        {
176
            return get_post_meta( $post_id, $name, true );
177
        }
178
        
179
        // If no meta key exists in the db, use default value
180
        $component = $this->metaboxes[$metabox_id]['form']->get_component($name);
181
        return $component->default;
182
    }
183
    
184
    /**
185
     * Print custom metabox style.
186
     */
187
    public function print_style() 
188
    {
189
        $cs = get_current_screen();
190
        
191
        foreach( $this->metaboxes as $metabox )
192
        {
193
            if( $metabox['screen'] === $cs->id )
194
            {
195
                echo '<style>';
196
                include 'metabox.css';
197
                echo '</style>';
198
                return;
199
            }
200
        }
201
    }
202
    
203
    /**
204
     * Print all errors stored in a transient for a given post ID.
205
     * 
206
     * @param number $post_id
207
     * @param array $metabox
208
     */
209
    public function print_errors( $post_id, $metabox )
210
    {
211
        $errors  = \get_transient("amarkal_metabox_errors_$post_id");
212
        
213
        if( $errors )
214
        {
215
            foreach( $errors as $name => $error )
216
            {
217
                $component = $metabox['form']->get_component($name);
218
                echo "<div class=\"notice notice-error\"><p><strong>{$component->title}</strong> $error</p></div>";
219
            }
220
        }
221
        
222
        \delete_transient("amarkal_metabox_errors_$post_id");
223
    }
224
    
225
    /**
226
     * Initiate the metaboxes by adding action hooks for printing and saving.
227
     */
228
    private function init()
229
    {
230
        \add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ) );
231
        \add_action( 'save_post', array( $this, 'save_meta_boxes' ) );
232
        \add_action( 'admin_footer', array( $this, 'print_style' ) );
233
    }
234
    
235
    /**
236
     * Update the form data for the given given metabox. If the $new_instance
237
     * contains new data, it will be saved into the db and if there are any 
238
     * validation errors they will be printed.
239
     * 
240
     * @param array $metabox
241
     * @param number $post_id
242
     * @param array $new_instance
243
     */
244
    private function update_form( $metabox, $post_id, array $new_instance = array() )
245
    {
246
        $old_instance   = $this->get_old_instance($metabox, $post_id);
247
        $final_instance = $metabox['form']->update( $new_instance, $old_instance );
248
249
        // Update db if there is new data to be saved
250
        if( array() !== $new_instance )
251
        {
252
            $this->update_post_meta($final_instance, $post_id);
253
            
254
            /**
255
             * We need to store all errors in a transient since WordPress does
256
             * a redirect to post.php and then back to our post, which clears
257
             * the execution thread. See https://www.sitepoint.com/displaying-errors-from-the-save_post-hook-in-wordpress/
258
             */
259
            \set_transient( "amarkal_metabox_errors_$post_id", $metabox['form']->get_errors(), 60 );
260
        }
261
    }
262
    
263
    /**
264
     * Update post meta for the given post id
265
     * 
266
     * @param type $final_instance
267
     * @param type $post_id
268
     */
269
    private function update_post_meta( $final_instance, $post_id )
270
    {
271
        foreach( $final_instance as $name => $value )
272
        {
273
            \update_post_meta( $post_id, $name, $value );
274
        }
275
    }
276
    
277
    /**
278
     * Get existing meta values from the database.
279
     * 
280
     * @param array $metabox
281
     * @param number $post_id
282
     * @return array
283
     */
284
    private function get_old_instance( $metabox, $post_id )
285
    {
286
        $old_instance = array();
287
        
288
        foreach( $metabox['fields'] as $field )
289
        {
290
            if( in_array($field['name'], get_post_custom_keys($post_id)) )
291
            {
292
                $old_instance[$field['name']] = \get_post_meta( $post_id, $field['name'], true );
293
            }
294
        }
295
        
296
        return $old_instance;
297
    }
298
    
299
    /**
300
     * Default arguments for the add() method.
301
     * 
302
     * @return array
303
     */
304
    private function default_args()
305
    {
306
        return array(
307
            'title'    => null,
308
            'screen'   => null,
309
            'context'  => 'advanced',
310
            'priority' => 'default',
311
            'fields'   => array()
312
        );
313
    }
314
}