PageRenderTime 99ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

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

https://gitlab.com/najomie/fit-hippie
PHP | 293 lines | 184 code | 33 blank | 76 comment | 32 complexity | 10e4ed251e3dadccd82b8486e47a1496 MD5 | raw file
  1. <?php
  2. /**
  3. * Handles execution and rendering of HTML admin pages.
  4. */
  5. class Loco_mvc_AdminRouter extends Loco_hooks_Hookable {
  6. /**
  7. * Current admin page controller
  8. * @var Loco_mvc_AdminController
  9. */
  10. private $ctrl;
  11. /**
  12. * admin_menu action callback
  13. */
  14. public function on_admin_menu() {
  15. // lowest capability required to see menu items is "loco_admin"
  16. // currently also the highest (and only) capability
  17. $cap = 'loco_admin';
  18. $user = wp_get_current_user();
  19. $super = $user->has_cap('manage_options');
  20. // avoid admin lockout before permissions can be initialized
  21. if( $super && ! $user->has_cap($cap) ){
  22. $perms = new Loco_data_Permissions;
  23. $perms->reset();
  24. $user->get_role_caps(); // <- rebuild
  25. }
  26. // rendering hook for all menu items
  27. $render = array( $this, 'renderPage' );
  28. // main loco pages, hooking only if has permission
  29. if( $user->has_cap($cap) ){
  30. $label = __('Loco Translate','loco');
  31. // translators: Page title for plugin home screen
  32. $title = __('Loco, Translation Management','loco');
  33. add_menu_page( $title, $label, $cap, 'loco', $render, 'dashicons-translation' );
  34. // alternative label for first menu item which gets repeated from top level
  35. add_submenu_page( 'loco', $title, __('Home','loco'), $cap, 'loco', $render );
  36. $label = __('Themes','loco');
  37. // translators: Page title for theme translations
  38. $title = __('Theme translations &lsaquo; Loco','loco');
  39. add_submenu_page( 'loco', $title, $label, $cap, 'loco-theme', $render );
  40. $label = __('Plugins', 'loco');
  41. // translators: Page title for plugin translations
  42. $title = __('Plugin translations &lsaquo; Loco','loco');
  43. add_submenu_page( 'loco', $title, $label, $cap, 'loco-plugin', $render );
  44. $label = __('WordPress', 'loco');
  45. // translators: Page title for core WordPress translations
  46. $title = __('Core translations &lsaquo; Loco', 'loco');
  47. add_submenu_page( 'loco', $title, $label, $cap, 'loco-core', $render );
  48. }
  49. // settings page only for users with manage_options permission:
  50. if( $super ){
  51. $title = __('Plugin settings','loco');
  52. add_submenu_page( 'loco', $title, __('Settings','loco'), 'manage_options', 'loco-config', $render );
  53. }
  54. // but all users need access to user preferences which live under this privileged page
  55. else if( $user->has_cap($cap) ){
  56. $title = __('User options','loco');
  57. add_submenu_page( 'loco', $title, __('Settings','loco'), $cap, 'loco-config-user', $render );
  58. }
  59. // legacy link redirect from previous slug
  60. if( isset($_GET['page']) && 'loco-translate' === $_GET['page'] ){
  61. if( wp_redirect( self::generate('') ) ){
  62. exit(0); // <- required to avoid page permissions being checked
  63. }
  64. }
  65. }
  66. /**
  67. * Early hook as soon as we know what screen will be rendered
  68. */
  69. public function on_current_screen( WP_Screen $screen ){
  70. $action = isset($_GET['action']) ? $_GET['action'] : null;
  71. $this->initPage( $screen, $action );
  72. }
  73. /**
  74. * Instantiate admin page controller from current screen.
  75. * This is called early (before renderPage) so controller can listen on other hooks.
  76. *
  77. * @return Loco_mvc_AdminController
  78. */
  79. public function initPage( WP_Screen $screen, $action = '' ){
  80. $class = null;
  81. $args = array ();
  82. // suppress error display when establishing Loco page
  83. $page = self::screenToPage($screen);
  84. if( is_string($page) ){
  85. $class = self::pageToClass( $page, $action, $args );
  86. }
  87. if( is_null($class) ){
  88. $this->ctrl = null;
  89. return;
  90. }
  91. // class should exist, so throw fatal if it doesn't
  92. $this->ctrl = new $class;
  93. if( ! $this->ctrl instanceof Loco_mvc_AdminController ){
  94. throw new Exception( $class.' must inherit Loco_mvc_AdminController');
  95. }
  96. // transfer flash messages from session to admin notice buffer
  97. try {
  98. $session = Loco_data_Session::get();
  99. while( $message = $session->flash('success') ){
  100. Loco_error_AdminNotices::success( $message );
  101. }
  102. }
  103. catch( Exception $e ){
  104. Loco_error_AdminNotices::debug( $e->getMessage() );
  105. }
  106. // buffer errors during controller setup
  107. try {
  108. $this->ctrl->_init( $_GET + $args );
  109. do_action('loco_admin_init', $this->ctrl );
  110. }
  111. catch( Loco_error_Exception $e ){
  112. Loco_error_AdminNotices::add( $e );
  113. }
  114. return $this->ctrl;
  115. }
  116. /**
  117. * Convert WordPress internal WPScreen $id into route prefix for an admin page controller
  118. * @return array
  119. */
  120. private static function screenToPage( WP_Screen $screen ){
  121. // Hooked menu slug is either "toplevel_page_loco" or "{title}_page_loco-{page}"
  122. // Sanitized {title} prefix is not reliable as it may be localized. instead just checking for "_page_loco"
  123. // TODO is there a safer WordPress way to resolve this?
  124. $id = $screen->id;
  125. $start = strpos($id,'_page_loco');
  126. // not one of our pages if token not found
  127. if( is_int($start) ){
  128. $page = substr( $id, $start+11 ) or $page = '';
  129. return $page;
  130. }
  131. }
  132. /**
  133. * Get unvalidated controller class for given route parameters
  134. * Abstracted from initPage so we can validate routes in self::generate
  135. * @return string
  136. */
  137. private static function pageToClass( $page, $action, array &$args ){
  138. $routes = array (
  139. '' => 'Root',
  140. 'debug' => 'Debug',
  141. // site-wide plugin configurations
  142. 'config' => 'config_Settings',
  143. 'config-user' => 'config_Prefs',
  144. 'config-version' => 'config_Version',
  145. // bundle type listings
  146. 'theme' => 'list_Themes',
  147. 'plugin' => 'list_Plugins',
  148. 'core' => 'list_Core',
  149. // bundle level views
  150. '{type}-view' => 'bundle_View',
  151. '{type}-conf' => 'bundle_Conf',
  152. '{type}-setup' => 'bundle_Setup',
  153. '{type}-debug' => 'bundle_Debug',
  154. // file initialization
  155. '{type}-msginit' => 'init_InitPo',
  156. '{type}-xgettext' => 'init_InitPot',
  157. // file resource views
  158. '{type}-file-view' => 'file_View',
  159. '{type}-file-edit' => 'file_Edit',
  160. '{type}-file-info' => 'file_Info',
  161. '{type}-file-delete' => 'file_Delete',
  162. );
  163. if( ! $page ){
  164. $page = $action;
  165. }
  166. else if( $action ){
  167. $page .= '-'. $action;
  168. }
  169. $args['_route'] = $page;
  170. // tokenize path arguments
  171. if( preg_match('/^(plugin|theme|core)-/', $page, $r ) ){
  172. $args['type'] = $r[1];
  173. $page = substr_replace( $page, '{type}', 0, strlen($r[1]) );
  174. }
  175. if( isset($routes[$page]) ){
  176. return 'Loco_admin_'.$routes[$page].'Controller';
  177. }
  178. // debug routing failures:
  179. // throw new Exception( sprintf('Failed to get page class from $page=%s',$page) );
  180. }
  181. /**
  182. * Main entry point for admin menu callback, establishes page and hands off to controller
  183. */
  184. public function renderPage(){
  185. try {
  186. // show deferred failure from initPage
  187. if( ! $this->ctrl ){
  188. throw new Loco_error_Exception( __('Page not found','loco') );
  189. }
  190. // display loco admin page
  191. echo $this->ctrl->render();
  192. }
  193. catch( Exception $e ){
  194. $ctrl = new Loco_admin_ErrorController;
  195. $ctrl->_init( array() );
  196. echo $ctrl->renderError($e);
  197. }
  198. // ensure session always shutdown cleanly after render
  199. Loco_data_Session::close();
  200. }
  201. /**
  202. * Generate a routable link to Loco admin page
  203. * @return string
  204. */
  205. public static function generate( $route, array $args = array() ){
  206. $url = null;
  207. $page = null;
  208. $action = null;
  209. // empty action targets plugin root
  210. if( ! $route ){
  211. $route = 'loco';
  212. }
  213. // support direct usage of page hooks
  214. if( $url = menu_page_url( $route, false ) ){
  215. $page = $route;
  216. }
  217. // else split action into admin page (e.g. "loco-themes") and sub-action (e.g. "view-theme")
  218. else {
  219. $page = 'loco';
  220. $path = explode( '-', $route );
  221. if( $sub = array_shift($path) ){
  222. $page .= '-'.$sub;
  223. if( $path ){
  224. $action = implode('-',$path);
  225. }
  226. }
  227. }
  228. // sanitize extended route in debug mode only. useful in tests
  229. if( loco_debugging() ){
  230. $tmp = array();
  231. $class = self::pageToClass( substr($page,5), $action, $tmp );
  232. if( ! $class || ! class_exists($class) ){
  233. throw new InvalidArgumentException( sprintf('Invalid admin route: %s => %s', json_encode($route), json_encode($class) ) );
  234. }
  235. }
  236. // if url found, it should contain the page
  237. if( $url ){
  238. unset( $args['page'] );
  239. }
  240. // else start with base URL
  241. else {
  242. $url = admin_url('admin.php');
  243. $args['page'] = $page;
  244. }
  245. // add action if found
  246. if( $action ){
  247. $args['action'] = $action;
  248. }
  249. // else ensure not set in args, as it's reserved
  250. else {
  251. unset( $args['action'] );
  252. }
  253. // append all arguments to base URL
  254. if( $query = http_build_query($args,null,'&') ){
  255. $sep = false === strpos($url, '?') ? '?' : '&';
  256. $url .= $sep.$query;
  257. }
  258. return $url;
  259. }
  260. }