Test Failed
Push — develop ( c6b569...7a7654 )
by Paul
09:40
created

Application::shortcode()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 11
ccs 0
cts 1
cp 0
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 1
crap 12
1
<?php
2
3
namespace GeminiLabs\SiteReviews;
4
5
use GeminiLabs\SiteReviews\Addons\Updater;
6
use GeminiLabs\SiteReviews\Contracts\PluginContract;
7
use GeminiLabs\SiteReviews\Contracts\ShortcodeContract;
8
use GeminiLabs\SiteReviews\Database\OptionManager;
9
use GeminiLabs\SiteReviews\Defaults\PermissionDefaults;
10
use GeminiLabs\SiteReviews\Helpers\Arr;
11
use GeminiLabs\SiteReviews\Modules\Queue;
12
13
/**
14
 * @property array     $addons
15
 * @property string    $basename
16
 * @property string    $capability
17
 * @property string    $cron_event
18
 * @property array     $db_version
19
 * @property array     $defaults
20
 * @property string    $export_key
21
 * @property string    $file
22
 * @property string    $id
23
 * @property string    $languages
24
 * @property string    $name
25
 * @property string    $paged_handle
26
 * @property string    $paged_query_var
27
 * @property string    $post_type
28
 * @property string    $prefix
29
 * @property array     $session
30
 * @property array     $settings
31
 * @property Arguments $storage
32
 * @property string    $taxonomy
33
 * @property string    $version
34
 * @property string    $testedTo;
35
 */
36
final class Application extends Container implements PluginContract
37
{
38
    use Plugin;
39
    use Session;
40
    use Storage;
41
42
    public const DB_VERSION = '1.4';
43
    public const EXPORT_KEY = '_glsr_export';
44
    public const ID = 'site-reviews';
45
    public const PAGED_HANDLE = 'pagination_request';
46
    public const PAGED_QUERY_VAR = 'reviews-page'; // filtered
47
    public const POST_TYPE = 'site-review';
48
    public const PREFIX = 'glsr_';
49
    public const TAXONOMY = 'site-review-category';
50
51
    protected array $addons = [];
52
    protected array $defaults;
53
    protected string $name;
54
    protected array $settings;
55
56 44
    public function addon(string $addonId)
57
    {
58 44
        return $this->addons[$addonId] ?? null;
59
    }
60
61
    /**
62
     * @param mixed ...$args
63
     */
64 2
    public function can(string $capability, ...$args): bool
65
    {
66 2
        return $this->make(Role::class)->can($capability, ...$args);
67
    }
68
69
    /**
70
     * The default plugin settings.
71
     * This is first triggered on "init:5" in MainController::onInit.
72
     */
73 44
    public function defaults(): array
74
    {
75 44
        if (empty($this->defaults)) {
76
            $settings = $this->settings();
77
            $defaults = array_combine(array_keys($settings), wp_list_pluck($settings, 'default'));
78
            $defaults = wp_parse_args($defaults, [
79
                'version' => '',
80
                'version_upgraded_from' => '0.0.0',
81
            ]);
82
            $defaults = Arr::unflatten($defaults);
83
            $defaults = $this->filterArray('settings/defaults', $defaults);
84
            $this->defaults = $defaults;
85
        }
86 44
        return $this->defaults;
87
    }
88
89
    public function getPermission(string $page = '', string $tab = 'index'): string
90
    {
91
        $fallback = 'edit_posts';
92
        $permissions = $this->make(PermissionDefaults::class)->defaults();
93
        $permission = Arr::get($permissions, $page, $fallback);
94
        if (is_array($permission)) {
95
            $permission = Arr::get($permission, $tab, $fallback);
96
        }
97
        $capability = empty($permission) || !is_string($permission)
98
            ? $fallback
99
            : $permission;
100
        return $this->make(Role::class)->capability($capability);
101
    }
102
103
    public function hasPermission(string $page = '', string $tab = 'index'): bool
104
    {
105
        $isAdminScreen = is_admin() || is_network_admin();
106
        return !$isAdminScreen || $this->can($this->getPermission($page, $tab));
107
    }
108
109
    /**
110
     * This is the entry point to the plugin, it runs before "plugins_loaded".
111
     * If this is a new major version, settings are copied over here and a migration is run.
112
     */
113
    public function init(): void
114
    {
115
        $args = [];
116
        // Ensure the custom database tables exist, this is needed in cases
117
        // where the plugin has been updated instead of activated.
118
        if (empty(get_option(static::PREFIX.'db_version'))) {
119
            $this->make(Install::class)->run();
120
        }
121
        // If this is a new major version, copy over the previous version settings
122
        if (empty(get_option(OptionManager::databaseKey()))) {
123
            $previous = $this->make(OptionManager::class)->previous();
124
            if (!empty($previous)) {
125
                update_option(OptionManager::databaseKey(), $previous, true);
126
                $args['settings'] = true;
127
            }
128
        }
129
        // Force a plugin migration on database version upgrades
130
        if (static::DB_VERSION !== get_option(static::PREFIX.'db_version')) {
131
            $args['database'] = true;
132
        }
133
        if (!empty($args)) {
134
            add_action('init', function () use ($args) {
135 29
                glsr(Queue::class)->once(time() + 15, 'queue/migration', $args, true);
136
            });
137 29
        }
138 29
        $this->make(Hooks::class)->run();
139
    }
140
141
    public function isAdmin(): bool
142
    {
143
        $isAdminScreen = is_admin() || is_network_admin();
144
        return $isAdminScreen && !wp_doing_ajax();
145
    }
146
147
    /**
148
     * This is triggered on init:5 by $this->settings().
149
     *
150
     * @param PluginContract|string $addon
151
     */
152
    public function license($addon): void
153
    {
154
        try {
155
            $settings = $this->settings();
156
            $reflection = new \ReflectionClass($addon);
157
            $id = $reflection->getConstant('ID');
158
            $licensed = $reflection->getConstant('LICENSED');
159
            $name = $reflection->getConstant('NAME');
160
            if (true !== $licensed || 2 !== count(array_filter([$id, $name]))) {
161
                return;
162
            }
163
            $license = [
164
                "settings.licenses.{$id}" => [
165
                    'default' => '',
166
                    'label' => $name,
167
                    'sanitizer' => 'text',
168
                    'tooltip' => sprintf(_x('Enter the license key here. Your license can be found on the %s page of your Nifty Plugins account.', 'License Keys (admin-text)', 'site-reviews'),
169
                        sprintf('<a href="https://niftyplugins.com/account/license-keys/" target="_blank">%s</a>', _x('License Keys', 'admin-text', 'site-reviews'))
170
                    ),
171
                    'type' => 'secret',
172
                ],
173
            ];
174
            $this->settings = array_merge($license, $settings);
175
        } catch (\ReflectionException $e) {
176
            // Fail silently
177
        }
178
    }
179
180
    /**
181
     * This is triggered on "plugins_loaded" by "site-reviews/addon/register".
182
     */
183
    public function register(string $addon, bool $isAuthorized = true): void
184
    {
185
        $retired = [ // @compat these addons have been retired
186
            'site-reviews-gamipress',
187
            'site-reviews-woocommerce',
188
        ];
189
        $premium = glsr()->filterArray('site-reviews-premium', []);
190
        try {
191
            $reflection = new \ReflectionClass($addon); // make sure that the class exists
192
        } catch (\ReflectionException $e) {
193
            glsr_log()->error("Attempted to register an invalid addon [$addon]");
194
            return;
195
        }
196
        $addonId = $reflection->getConstant('ID');
197
        $file = dirname(dirname($reflection->getFileName()));
198
        $file = trailingslashit($file).$addonId.'.php';
199
        if (!file_exists($file)) {
200
            glsr_log()->error("Attempted to register an invalid addon [$addonId].");
201
            return;
202
        }
203
        if (in_array($addonId, $retired)) {
204
            $this->append('retired', $addon);
205
            return;
206
        }
207
        if (in_array($addonId, $premium)
208
            && !str_starts_with($reflection->getNamespaceName(), 'GeminiLabs\SiteReviews\Premium')) {
209
            $this->append('site-reviews-premium', $addon);
210
            return;
211
        }
212 83
        $pluginData = get_file_data($file, ['update_url' => 'Update URI'], 'plugin');
213
        if (empty($pluginData['update_url'])) {
214 83
            $this->append('compat', $file, $addonId); // this addon needs updating in compatibility mode.
215
        }
216
        if (true === $reflection->getConstant('LICENSED')) {
217
            $this->append('licensed', $addon, $addonId);
218
        }
219
        if (true === $isAuthorized) {
220
            $this->addons[$addonId] = $addon;
221
            $this->singleton($addon); // this goes first!
222
            $this->alias($addonId, $this->make($addon)); // @todo for some reason we have to link an alias to an instantiated class
223
            $instance = $this->make($addon)->init();
224
            $this->append('addons', $instance->version, $instance->id);
225 83
        }
226
    }
227
228 83
    /**
229 83
     * The plugin settings configuration.
230
     * This is first triggered on "init:5" by MainController::onInit.
231
     *
232
     * @return mixed
233
     */
234
    public function settings(string $path = '')
235
    {
236
        if (empty($this->settings)) {
237
            $settings = $this->config('settings');
238
            $settings = $this->filterArray('settings', $settings);
239
            array_walk($settings, function (&$setting) {
240
                $setting = wp_parse_args($setting, [
241
                    'default' => '',
242
                    'sanitizer' => 'text',
243
                ]);
244
            });
245
            $this->settings = $settings; // do this before adding license settings!
246
            $licensedAddons = $this->retrieveAs('array', 'licensed', []);
247
            array_walk($licensedAddons, fn ($addon) => $this->license($addon));
248
        }
249
        if (empty($path)) {
250
            return $this->settings;
251
        }
252
        $settings = Arr::unflatten($this->settings);
253
        return Arr::get($settings, $path);
254
    }
255
256
    public function shortcode(string $shortcode): ?ShortcodeContract
257
    {
258
        $shortcodes = glsr()->retrieveAs('array', 'shortcodes');
259
        $className = $shortcodes[$shortcode] ?? '';
260
        if (!class_exists($className)) {
261
            return null;
262
        }
263
        if (!(new \ReflectionClass($className))->implementsInterface(ShortcodeContract::class)) {
264
            return null;
265
        }
266
        return $this->make($className);
1 ignored issue
show
Bug Best Practice introduced by
The expression return $this->make($className) could return the type callable which is incompatible with the type-hinted return GeminiLabs\SiteReviews\C...\ShortcodeContract|null. Consider adding an additional type-check to rule them out.
Loading history...
267
    }
268
}
269