Completed
Push — master ( 79e699...9f4abe )
by Askupa
05:35
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
            \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
            $decoded_atts[$name] = \json_decode(\urldecode($value));
107
        }
108
        return $decoded_atts;
109
    }
110
    
111
    /**
112
     * Create a JSON object that will be printed in the admin section
113
     * to be used by the TinyMCE plugin code.
114
     */
115
    private function prepare_json_object()
116
    {
117
        $json = array();
118
        foreach($this->shortcodes as $id => $shortcode)
119
        {
120
            $popup = new Popup($shortcode);
121
            $json[$id] = $shortcode;
122
            $json[$id]['html'] = $popup->render();
123
        }
124
        return json_encode($json);
125
    }
126
    
127
    /**
128
     * Default shortcode arguments
129
     */
130
    private function default_args()
131
    {
132
        return array(
133
            'id'                => null,
134
            'title'             => '',
135
            'template'          => null,
136
            'cmd'               => '',
137
            'width'             => 550,
138
            'height'            => 450,
139
            'render'            => function(){},
140
            'fields'            => array(),
141
            'is_shortcode'      => true,
142
            'show_placeholder'  => true,
143
            'placeholder_class' => null,
144
            'placeholder_icon'  => null,
145
            'placeholder_subtitle' => null
146
        );
147
    }
148
    
149
    /**
150
     * Check if a shortcode with the given ID has already been registered
151
     */
152
    private function shortcode_exists( $id )
153
    {
154
        return array_key_exists($id, $this->shortcodes);
155
    }
156
    
157
    /**
158
     * Validate that the provided arguments have the required arguments as
159
     * specified in self::required_args()
160
     */
161
    private function validate_args( $args )
162
    {
163
        foreach($this->required_args() as $arg)
164
        {
165
            if(!array_key_exists($arg, $args))
166
            {
167
                throw new \RuntimeException("Missing required argument '$arg'");
168
            }
169
        }
170
    }
171
172
    /**
173
     * Prepare a shortcode configuration array based on the given arguments
174
     *
175
     * @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...
176
     * @return array
177
     */
178
    private function prepare_config( $args )
179
    {
180
        $this->validate_args($args);
181
        $config = array_merge($this->default_args(), $args);
182
183
        if($config['template'] === null)
184
        {
185
            $config['template'] = $this->generate_template($config['id'],$config['fields']);
186
        }
187
188
        return $config;
189
    }
190
191
    /**
192
     * Genereate a basic shortcode template based on the given set 
193
     * of shortcode fields.
194
     *
195
     * @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...
196
     * @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...
197
     * @return string
198
     */
199
    private function generate_template($tag, $fields)
200
    {
201
        $template = "[$tag";
202
        $self_enclosing = true;
203
204
        foreach($fields as $field)
205
        {
206
            $name = $field['name'];
207
            if('content' !== $name)
208
            {
209
                $template .= " $name=\"{{{$name}}}\"";
210
            }
211
            else $self_enclosing = false;
212
        }
213
214
        if($self_enclosing)
215
        {
216
            $template .= "/]";
217
        }
218
        else {
219
            $template .= "]{{content}}[/$tag]";
220
        }
221
        
222
        return "<p>$template</p>";
223
    }
224
    
225
    /**
226
     * A list of required arguments
227
     */
228
    private function required_args()
229
    {
230
        return array('id','title','fields');
231
    }
232
233
    /**
234
     * Private constructor to prevent instantiation
235
     */
236
    private function __construct() 
237
    {
238
        \add_filter('mce_external_plugins',array($this,'enqueue_script'));
239
        \add_action('admin_init', array($this,'enqueue_editor_style'));
240
        \add_action('admin_enqueue_scripts', array($this,'enqueue_popup_style'));
241
        \add_action('wp_enqueue_scripts', array($this,'enqueue_popup_style'));
242
    }
243
}