Completed
Push — master ( e33ed1...4487f0 )
by Askupa
04:38
created

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
        wp_nonce_field(self::NONCE_ACTION, $args['id'].'_nonce');
74
        foreach( $metabox['fields'] as $field )
75
        {
76
            $field['post_id'] = $post->ID;
77
            $field_template = new Field($field);
78
            echo $field_template->render();
79
        }
80
    }
81
    
82
    /**
83
     * Internally used to register metaboxes.
84
     */
85
    public function add_meta_boxes()
86
    {
87
        foreach( $this->metaboxes as $id => $args )
88
        {
89
            \add_meta_box(
90
                $id,
91
                $args['title'],
92
                array($this, 'render'),
93
                $args['screen'],
94
                $args['context'],
95
                $args['priority']
96
            );
97
        }
98
    }
99
    
100
    /**
101
     * Save metaboxes data for a given page.
102
     * 
103
     * @param number $post_id
104
     */
105
    public function save_meta_boxes( $post_id )
106
    {
107
        /**
108
         * A note on security:
109
         * 
110
         * We need to verify this came from the our screen and with proper authorization,
111
         * because save_post can be triggered at other times. since metaboxes can 
112
         * be removed - by having a nonce field in only one metabox there is no 
113
         * guarantee the nonce will be there. By placing a nonce field in each 
114
         * metabox you can check if data from that metabox has been sent 
115
         * (and is actually from where you think it is) prior to processing any data.
116
         * @see http://wordpress.stackexchange.com/a/49460/25959
117
         */
118
 
119
        /*
120
         * If this is an autosave, our form has not been submitted,
121
         * so we don't want to do anything.
122
         */
123
        if( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) 
124
        {
125
            return $post_id;
126
        }
127
128
        // Check the user's permissions.
129
        $post_type = filter_input(INPUT_POST, 'post_type');
130
        if( null !== $post_type && !current_user_can('edit_'.$post_type, $post_id) )
131
        {
132
            return $post_id;
133
        }
134
135
        // Update the meta fields.
136
        foreach( $this->metaboxes as $id => $metabox )
137
        {
138
            $this->save_meta_box( $post_id, $id, $metabox );
139
        }
140
    }
141
    
142
    /**
143
     * Save the data of a single metabox.
144
     * 
145
     * @param number $post_id
146
     * @param string $id
147
     * @param array $metabox
148
     */
149
    public function save_meta_box( $post_id, $id, $metabox )
150
    {
151
        $nonce_name  = $id.'_nonce';
152
        $nonce_value = filter_input(INPUT_POST, $nonce_name);
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
        foreach( $this->get_form_data($metabox, $post_id) as $name => $value )
161
        {
162
            \update_post_meta( $post_id, $name, $value );
163
        }
164
    }
165
    
166
    /**
167
     * Print custom metabox style.
168
     */
169
    public function print_style() 
170
    {
171
        $cs = get_current_screen();
172
        
173
        foreach( $this->metaboxes as $metabox )
174
        {
175
            if( $metabox['screen'] === $cs->id )
176
            {
177
                echo '<style>';
178
                include 'metabox.css';
179
                echo '</style>';
180
                return;
181
            }
182
        }
183
    }
184
    
185
    /**
186
     * Initiate the metaboxes by adding action hooks for printing and saving.
187
     */
188
    private function init()
189
    {
190
        \add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ) );
191
        \add_action( 'save_post', array( $this, 'save_meta_boxes' ) );
192
        \add_action( 'admin_footer', array( $this, 'print_style' ) );
193
    }
194
    
195
    /**
196
     * Get the updated data from the form of the given metabox.
197
     * 
198
     * @param array $metabox
199
     * @param number $post_id
200
     * @return array
201
     */
202
    private function get_form_data( $metabox, $post_id )
203
    {
204
        $old_instance = array();
205
        $new_instance = filter_input_array(INPUT_POST);
206
        
207
        foreach( $metabox['fields'] as $field )
208
        {
209
            if( in_array($field['name'], get_post_custom_keys($post_id)) )
210
            {
211
                $old_instance[$field['name']] = \get_post_meta( $post_id, $field['name'], true );
212
            }
213
        }
214
        
215
        return $metabox['form']->update( $new_instance, $old_instance );
216
    }
217
    
218
    /**
219
     * Default arguments for the add() method.
220
     * 
221
     * @return array
222
     */
223
    private function default_args()
224
    {
225
        return array(
226
            'title'    => null,
227
            'screen'   => null,
228
            'context'  => 'advanced',
229
            'priority' => 'default',
230
            'fields'   => array()
231
        );
232
    }
233
}