Completed
Push — master ( be1b20...0523f1 )
by Askupa
01:23
created

Manager::get_instance()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace Amarkal\Shortcode;
4
5
class Manager
6
{
7
    /**
8
     * @var Singleton The reference to *Singleton* instance of this class
9
     */
10
    private static $instance;
11
    
12
    /**
13
     * Undocumented variable
14
     *
15
     * @var array The list of registered shortcodes
16
     */
17
    private $shortcodes = array();
18
    
19
    /**
20
     * Returns the *Singleton* instance of this class.
21
     *
22
     * @return Singleton The *Singleton* instance.
23
     */
24
    public static function get_instance()
25
    {
26
        if( null === static::$instance ) 
0 ignored issues
show
Bug introduced by
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...
27
        {
28
            static::$instance = new static();
0 ignored issues
show
Bug introduced by
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\Shortcode\Manager> is incompatible with the declared type object<Amarkal\Shortcode\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...
29
        }
30
        return static::$instance;
0 ignored issues
show
Bug introduced by
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\Shortcode\Manage...kal\Shortcode\Singleton adds the type Amarkal\Shortcode\Manager to the return on line 30 which is incompatible with the return type documented by Amarkal\Shortcode\Manager::get_instance of type Amarkal\Shortcode\Singleton.
Loading history...
31
    }
32
    
33
    /**
34
     * Register a shortcode 
35
     *
36
     * @param [array] $args
0 ignored issues
show
Documentation introduced by
The doc-type [array] 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...
37
     * @return void
38
     */
39
    public function register_shortcode( $args )
40
    {
41
        $config = $this->prepare_config($args);
42
43
        if($this->shortcode_exists($args['id']))
44
        {
45
            throw new \RuntimeException("A shortcode with id '{$args['id']}' has already been registered");
46
        }
47
48
        $this->shortcodes[$args['id']] = $config;
49
50
        if($config['is_shortcode'])
51
        {
52
            $self = $this; // Needed for backward compatibility
53
            \add_shortcode( $args['id'], function($atts, $content = null) use ($args, $self) {
54
                // TODO: merge $atts with defaults using shortcode_atts()
55
                $atts['content'] = $content;
56
                return call_user_func_array($args['render'], array($self->decode_atts($atts)));
57
            });
58
        }
59
    }
60
    
61
    /**
62
     * Enqueue the shortcode script and print the JSON object
63
     *
64
     * @param [array] $plugins_array
0 ignored issues
show
Documentation introduced by
The doc-type [array] 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...
65
     * @return void
66
     */
67
    public function enqueue_script($plugins_array)
68
    {
69
        // Printing the JSON object ensures that it will be available whenever 
70
        // the visual editor is present.
71
        echo "<script id='amarkal-shortcode-json' type='application/json'>{$this->prepare_json_object()}</script>";
72
        
73
        // This script must be included after the JSON object, since it refers
74
        // to it, and so the JSON must be readily available.
75
        $plugins_array['amarkal_shortcode'] = \Amarkal\Core\Utility::path_to_url(__DIR__.'/assets/js/dist/amarkal-shortcode.min.js');
76
        return $plugins_array;
77
    }
78
79
    /**
80
     * Enqueue the necessary styles & scripts
81
     *
82
     * @return void
83
     */
84
    public function enqueue_scripts()
85
    {
86
        \wp_enqueue_script('shortcode'); // Needed for wp.shortcode
87
88
        /**
89
         * Enqueue the popup stylesheet. This needs to be separated from the editor
90
         * stylesheet since it is not part of the editor.
91
         */
92
        \wp_enqueue_style('amarkal-shortcode',\Amarkal\Core\Utility::path_to_url(__DIR__.'/assets/css/dist/amarkal-shortcode-popup.min.css'));
93
    }
94
95
    /**
96
     * Enqueue the editor stylesheet.
97
     *
98
     * @return void
99
     */
100
    public function enqueue_editor_style()
101
    {
102
        \add_editor_style(\Amarkal\Core\Utility::path_to_url(__DIR__.'/assets/css/dist/amarkal-shortcode-editor.min.css'));
103
    }
104
105
    public function decode_atts($atts)
106
    {
107
        $decoded_atts = array();
108
        
109
        // Attributes are JSON encoded and then URL encoded in the shortcode editor, so
110
        // we need to reverse that
111
        foreach($atts as $name => $value)
112
        {
113
            $decoded_atts[$name] = $this->decode_att($value);
114
        }
115
        return $decoded_atts;
116
    }
117
118
    private function decode_att($value)
119
    {
120
        $decoded = \json_decode(\urldecode($value));
121
        
122
        // If the value is null, it is most likely because the attribute is not JSON encoded.
123
        // We return the uncoded value for backward compatibility, where attributes are not JSON encoded.
124
        if(null === $decoded) {
125
            $decoded = $this->decode_non_json_encodede_att($value);
126
        }
127
128
        return $decoded;
129
    }
130
131
    private function decode_non_json_encodede_att($value)
132
    {
133
        if(false !== \strpos($value, ',')) {
134
            return \explode(',', $value);
135
        }
136
        return $value;
137
    }
138
    
139
    /**
140
     * Create a JSON object that will be printed in the admin section
141
     * to be used by the TinyMCE plugin code.
142
     */
143
    private function prepare_json_object()
144
    {
145
        $json = array();
146
        foreach($this->shortcodes as $id => $shortcode)
147
        {
148
            $popup = new Popup($shortcode);
149
            $json[$id] = $shortcode;
150
            $json[$id]['html'] = $popup->render();
151
        }
152
        return json_encode($json);
153
    }
154
    
155
    /**
156
     * Default shortcode arguments
157
     */
158
    private function default_args()
159
    {
160
        return array(
161
            'id'                => null,
162
            'title'             => '',
163
            'template'          => null,
164
            'cmd'               => '',
165
            'width'             => 550,
166
            'height'            => 450,
167
            'render'            => function(){},
168
            'fields'            => array(),
169
            'is_shortcode'      => true,
170
            'show_placeholder'  => true,
171
            'placeholder_class' => null,
172
            'placeholder_icon'  => null,
173
            'placeholder_subtitle' => null
174
        );
175
    }
176
    
177
    /**
178
     * Check if a shortcode with the given ID has already been registered
179
     */
180
    private function shortcode_exists( $id )
181
    {
182
        return array_key_exists($id, $this->shortcodes);
183
    }
184
    
185
    /**
186
     * Validate that the provided arguments have the required arguments as
187
     * specified in self::required_args()
188
     */
189
    private function validate_args( $args )
190
    {
191
        foreach($this->required_args() as $arg)
192
        {
193
            if(!array_key_exists($arg, $args))
194
            {
195
                throw new \RuntimeException("Missing required argument '$arg'");
196
            }
197
        }
198
    }
199
200
    /**
201
     * Prepare a shortcode configuration array based on the given arguments
202
     *
203
     * @param [array] $args
0 ignored issues
show
Documentation introduced by
The doc-type [array] 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...
204
     * @return array
205
     */
206
    private function prepare_config( $args )
207
    {
208
        $this->validate_args($args);
209
        $config = array_merge($this->default_args(), $args);
210
211
        if($config['template'] === null)
212
        {
213
            $config['template'] = $this->generate_template($config['id'],$config['fields']);
214
        }
215
216
        return $config;
217
    }
218
219
    /**
220
     * Genereate a basic shortcode template based on the given set 
221
     * of shortcode fields.
222
     *
223
     * @param [string] $tag
0 ignored issues
show
Documentation introduced by
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...
224
     * @param [array] $fields
0 ignored issues
show
Documentation introduced by
The doc-type [array] 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...
225
     * @return string
226
     */
227
    private function generate_template($tag, $fields)
228
    {
229
        $template = "[$tag";
230
        $self_enclosing = true;
231
232
        foreach($fields as $field)
233
        {
234
            $name = $field['name'];
235
            if('content' !== $name)
236
            {
237
                $template .= " $name=\"{{{$name}}}\"";
238
            }
239
            else $self_enclosing = false;
240
        }
241
242
        if($self_enclosing)
243
        {
244
            $template .= "/]";
245
        }
246
        else {
247
            $template .= "]{{content}}[/$tag]";
248
        }
249
        
250
        return "<p>$template</p>";
251
    }
252
    
253
    /**
254
     * A list of required arguments
255
     */
256
    private function required_args()
257
    {
258
        return array('id','title','fields');
259
    }
260
261
    /**
262
     * Private constructor to prevent instantiation
263
     */
264
    private function __construct() 
265
    {
266
        \add_filter('mce_external_plugins',array($this,'enqueue_script'));
267
        \add_action('admin_init', array($this,'enqueue_editor_style'));
268
        \add_action('admin_enqueue_scripts', array($this,'enqueue_scripts'));
269
        \add_action('wp_enqueue_scripts', array($this,'enqueue_scripts'));
270
    }
271
}