Completed
Push — master ( 11fa70...bdfca8 )
by Askupa
01:16
created

Manager::enqueue_popup_style()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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