/var/www/html_it/wp-content/plugins/loco-translate/src/mvc/AdminRouter.php


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
<?php
/**
 * Handles execution and rendering of HTML admin pages.
 */
class Loco_mvc_AdminRouter extends Loco_hooks_Hookable {
    
    
/**
     * Current admin page controller
     * @var Loco_mvc_AdminController
     */
    
private $ctrl;


    
/**
     * admin_menu action callback
     */
    
public function on_admin_menu() {

        
// lowest capability required to see menu items is "loco_admin"
        // currently also the highest (and only) capability
        
$cap 'loco_admin';
        
$user wp_get_current_user();
        
$super is_super_admin$user->ID );
        
        
// Ensure Loco permissions are set up for the first time, or nobody will have access at all
        
if( ! get_role('translator') || ( $super && ! is_multisite() && ! $user->has_cap($cap) ) ){
            
Loco_data_Permissions::init();
            
$user->get_role_caps(); // <- rebuild
        
}

        
// rendering hook for all menu items
        
$render = [ $this'renderPage' ];
        
        
// main loco pages, hooking only if has permission
        
if( $user->has_cap($cap) ){

            
$label __('Loco Translate','loco-translate');
            
// translators: Page title for plugin home screen
            
$title __('Loco, Translation Management','loco-translate');
            
add_menu_page$title$label$cap'loco'$render'dashicons-translation' );
            
// alternative label for first menu item which gets repeated from top level 
            
add_submenu_page'loco'$title__('Home','loco-translate'), $cap'loco'$render );

            
$label __('Themes','loco-translate');
            
// translators: Page title for theme translations
            
$title __('Theme translations &lsaquo; Loco','loco-translate');
            
add_submenu_page'loco'$title$label$cap'loco-theme'$render );

            
$label __('Plugins''loco-translate');
            
// translators: Page title for plugin translations
            
$title __('Plugin translations &lsaquo; Loco','loco-translate');
            
add_submenu_page'loco'$title$label$cap'loco-plugin'$render );

            
$label __('WordPress''loco-translate');
            
// translators: Page title for core WordPress translations
            
$title __('Core translations &lsaquo; Loco''loco-translate');
            
add_submenu_page'loco'$title$label$cap'loco-core'$render );

            
$label __('Languages''loco-translate');
            
// translators: Page title for installed languages page
            
$title __('Languages &lsaquo; Loco''loco-translate');
            
add_submenu_page'loco'$title$label$cap'loco-lang'$render );

            
// settings page only for users with manage_options permission in addition to Loco access:
            
if( $user->has_cap('manage_options') ){
                
$title __('Plugin settings','loco-translate');
                
add_submenu_page'loco'$title__('Settings','loco-translate'), 'manage_options''loco-config'$render );
            }
            
// but all users need access to user preferences which require standard Loco access permission
            
else {
                
$title __('User options','loco-translate');
                
add_submenu_page'loco'$title__('Settings','loco-translate'), $cap'loco-config-user'$render );
            }

            
// string translation simulator
            
if( loco_debugging() ){
                
$label __('Debug''loco-translate');
                
add_submenu_page'loco'$label$label$cap'loco-debug'$render );
            }
        }
    }


    
/**
     * Early hook as soon as we know what screen will be rendered
     * @return void
     */
    
public function on_current_screenWP_Screen $screen ){
        
$action = isset($_GET['action']) ? $_GET['action'] : null;
        
$this->initPage$screen$action );
    }


    
/**
     * Instantiate admin page controller from current screen.
     * This is called early (before renderPage) so controller can listen on other hooks.
     * 
     * @param string $action
     * @return Loco_mvc_AdminController|null
     */
    
public function initPageWP_Screen $screen$action '' ){
        
$class null;
        
$args =  [];
        
// suppress error display when establishing Loco page
        
$page self::screenToPage($screen);
        if( 
is_string($page) ){
            
$class self::pageToClass$page$action$args );
        }
        if( 
is_null($class) ){
            
$this->ctrl null;
            return 
null;
        }
        
// class should exist, so throw fatal if it doesn't
        
$this->ctrl = new $class;
        if( ! 
$this->ctrl instanceof Loco_mvc_AdminController ){
            throw new 
Exception$class.' must inherit Loco_mvc_AdminController');
        }
        
// transfer flash messages from session to admin notice buffer
        
try {
            
$session Loco_data_Session::get();
            while( 
$message $session->flash('success') ){
                
Loco_error_AdminNotices::success$message );
            }
        }
        catch( 
Exception $e ){
            
Loco_error_AdminNotices::debug$e->getMessage() );
        }
        
// Initialise controller with query string + route arguments
        // note that $_GET is not being stripped of slashes added by WordPress.
        
try {
            
$this->ctrl->_init($_GET+$args);
            
do_action('loco_admin_init'$this->ctrl );
        }
        
// catch errors during controller setup
        
catch( Loco_error_Exception $e ){
            
$this->ctrl = new Loco_admin_ErrorController;
            
// can't afford an error during an error
            
try {
                
$this->ctrl->_init( [ 'error' => $e ] );
            }
            catch( 
Exception $_e ){
                
Loco_error_AdminNotices::debug$_e->getMessage() );
                
Loco_error_AdminNotices::add($e);
            }
        }
        
// WP emoji replacement doesn't inherit .wp-exclude-emoji so we'd have to add it to hundreds of elements.
        
remove_action'admin_print_scripts''print_emoji_detection_script' );

        return 
$this->ctrl;
    }


    
/**
     * Convert WordPress internal WPScreen $id into route prefix for an admin page controller
     * @return string|null
     */
    
private static function screenToPageWP_Screen $screen ){
        
// Hooked menu slug is either "toplevel_page_loco" or "{title}_page_loco-{page}"
        // Sanitized {title} prefix is not reliable as it may be localized. instead just checking for "_page_loco"
        
$id $screen->id;
        
$start strpos($id,'_page_loco');
        
// not one of our pages if token not found
        
if( is_int($start) ){
            
$page substr$id$start+11 ) or $page '';
            return 
$page;
        }
        return 
null;
    }


    
/**
     * Get unvalidated controller class for given route parameters
     * Abstracted from initPage so we can validate routes in self::generate
     * @param string $page
     * @param string $action
     * @return string|null
     */
    
private static function pageToClass$page$action, array &$args ){
        
$routes =  [
            
'' => 'Root',
            
'debug' => 'Debug',
            
// site-wide plugin configurations
            
'config' => 'config_Settings',
            
'config-apis' => 'config_Apis',
            
'config-user' => 'config_Prefs',
            
'config-debug' => 'config_Debug',
            
'config-version' => 'config_Version',
            
// bundle type listings
            
'theme'  => 'list_Themes',
            
'plugin' => 'list_Plugins',
            
'core'   => 'list_Core',
            
'lang'   => 'list_Locales',
            
// bundle level views
            
'{type}-view' => 'bundle_View',
            
'{type}-conf' => 'bundle_Conf',
            
'{type}-setup' => 'bundle_Setup',
            
'lang-view' => 'bundle_Locale',
            
// file initialization
            
'{type}-msginit' => 'init_InitPo',
            
'{type}-xgettext' => 'init_InitPot',
            
'{type}-upload' => 'init_Upload',
            
// file resource views
            
'{type}-file-view' => 'file_View',
            
'{type}-file-edit' => 'file_Edit',
            
'{type}-file-info' => 'file_Info',
            
'{type}-file-head' => 'file_Head',
            
'{type}-file-diff' => 'file_Diff',
            
'{type}-file-move' => 'file_Move',
            
'{type}-file-delete' => 'file_Delete',
            
// test routes that don't actually exist
            
'test-no-class' => 'test_NonExistentClass',
        ];
        if( ! 
$page ){
            
$page $action;
        }
        else if( 
$action ){
            
$page .= '-'$action;
        }
        
$args['_route'] = $page;
        
// tokenize path arguments
        
if( $page && preg_match('/^(plugin|theme|core)-/'$page$r ) ){
            
$args['type'] = $r[1];
            
$page substr_replace$page'{type}'0strlen($r[1]) );
        }
        if( isset(
$routes[$page]) ){
            return 
'Loco_admin_'.$routes[$page].'Controller';
        }
        
// debug routing failures:
        // throw new Exception( sprintf('Failed to get page class from $page=%s',$page) );
        
return null;
    }



    
/**
     * Main entry point for admin menu callback, establishes page and hands off to controller
     * @return void
     */
    
public function renderPage(){
        try {
            
// show deferred failure from initPage
            
if( ! $this->ctrl ){
                throw new 
Loco_error_Exception__('Page not found','loco-translate') );
            }
            
// display loco admin page
            
echo $this->ctrl->render();
        }
        catch( 
Exception $e ){
            
$ctrl = new Loco_admin_ErrorController;
            try {
                
$ctrl->_init( [] );
            }
            catch( 
Exception $_e ){
                
// avoid errors during error rendering
                
Loco_error_AdminNotices::debug$_e->getMessage() );
            }
            echo 
$ctrl->renderError($e);
        }
        
// ensure session always shutdown cleanly after render
        
Loco_data_Session::close();
        
do_action('loco_admin_shutdown');
    }


    
/**
     * Generate a routable link to Loco admin page
     * @param string $route
     * @return string
     */
    
public static function generate$route, array $args = [] ){
        
$url null;
        
$page null;
        
$action null;
        
// empty action targets plugin root
        
if( ! $route || 'loco' === $route ){
            
$page 'loco';
        }
        
// support direct usage of page hooks
        
else if( 'loco-' === substr($route,0,5) && menu_page_url($route,false) ){
            
$page $route;
        }
        
// else split action into admin page (e.g. "loco-themes") and sub-action (e.g. "view-theme")
        
else {
            
$page 'loco';
            
$path explode'-'$route );
            if( 
$sub array_shift($path) ){
                
$page .= '-'.$sub;
                if( 
$path ){
                    
$action implode('-',$path);
                }
            }
        }
        
// sanitize extended route in debug mode only. useful in tests
        
if( loco_debugging() ){
            
$tmp = [];
            
$class self::pageToClass( (string) substr($page,5), $action$tmp );
            if( ! 
$class ){
                throw new 
UnexpectedValueExceptionsprintf('Invalid admin route: %s'json_encode($route) ) );
            }
            else {
                
class_exists($class,true); // <- autoloader will throw if not class found
            
}
        }
        
// if url found, it should contain the page
        
if( $url ){
            unset( 
$args['page'] );
        }
        
// else start with base URL
        
else {
            
$url admin_url('admin.php');
            
$args['page'] = $page;
        }
        
// add action if found
        
if( $action ){
            
$args['action'] = $action;
        }
        
// else ensure not set in args, as it's reserved
        
else {
            unset( 
$args['action'] );
        }
        
// append all arguments to base URL
        
if( $query http_build_query($args) ){
            
$sep false === strpos($url'?') ? '?' '&';
            
$url .= $sep.$query;
        }
        return 
$url;
    }

}