Gestionnaire de fichiers - Editer - /home/wwgoat/public_html/blog/mvc.tar
Arrière
Controller.php 0000644 00000007221 14720714126 0007406 0 ustar 00 <?php /** * */ abstract class Loco_mvc_Controller extends Loco_hooks_Hookable { /** * Execute controller and return renderable output * @return string */ abstract public function render(); /** * Get view parameter * @param string $prop * @return mixed */ abstract public function get( $prop ); /** * Set view parameter * @param string $prop * @param mixed $value * @return Loco_mvc_Controller */ abstract public function set( $prop, $value ); /** * Default authorization check * @return Loco_mvc_Controller|void */ public function auth(){ if( is_user_logged_in() ){ // default capability check. child classes should override if( current_user_can('loco_admin') ){ return $this; } } $this->exitForbidden(); } /** * Emulate permission denied screen as performed in wp-admin/admin.php */ protected function exitForbidden(){ do_action( 'admin_page_access_denied' ); wp_die( __( 'You do not have sufficient permissions to access this page.','default' ), 403 ); } // @codeCoverageIgnore /** * Set a nonce for the current page for when it submits a form * @return Loco_mvc_ViewParams */ public function setNonce( $action ){ $name = 'loco-nonce'; $value = wp_create_nonce( $action ); $nonce = new Loco_mvc_ViewParams( compact('name','value','action') ); $this->set('nonce', $nonce ); return $nonce; } /** * Check if a valid nonce has been sent in current request. * Fails if nonce is invalid, but returns false if not sent so scripts can exit accordingly. * @throws Loco_error_Exception * @param string $action action for passing to wp_verify_nonce * @return bool true if data has been posted and nonce is valid */ public function checkNonce( $action ){ $posted = false; $name = 'loco-nonce'; if( isset($_REQUEST[$name]) ){ $value = $_REQUEST[$name]; if( wp_verify_nonce( $value, $action ) ){ $posted = true; } else { throw new Loco_error_Exception('Failed security check for '.$name); } } return $posted; } /** * Filter callback for `translations_api' * Ensures silent failure of translations_api when network disabled, see $this->getAvailableCore */ public function filter_translations_api( $value = false ){ if( apply_filters('loco_allow_remote', true ) ){ return $value; } // returning error here has the safe effect as returning empty translations list return new WP_Error( -1, 'Translations API blocked by loco_allow_remote filter' ); } /** * Filter callback for `pre_http_request` * Ensures fatal error if we failed to handle offline mode earlier. */ public function filter_pre_http_request( $value = false ){ if( apply_filters('loco_allow_remote', true ) ){ return $value; } // little point returning WP_Error error because WordPress will just show "unexpected error" throw new Loco_error_Exception('HTTP request blocked by loco_allow_remote filter' ); } /** * number_format_i18n filter callback because our admin screens assume number_format_i18n() returns unescaped text, not HTML. * @param string * @return string */ public function filter_number_format_i18n( $formatted = '' ){ return html_entity_decode($formatted,ENT_NOQUOTES,'UTF-8'); } } FileParams.php 0000644 00000012704 14720714126 0007310 0 ustar 00 <?php /** * Abstracts information about a file into a view parameter object. * * @property-read string $name * @property-read string $path * @property-read string $relpath * @property-read string $size * @property-read int $imode * @property-read string $smode * @property-read string $group * @property-read string $owner */ class Loco_mvc_FileParams extends Loco_mvc_ViewParams { /** * File reference from which to take live properties. * @var Loco_fs_File */ private $file; /** * Print property as a number of bytes in larger denominations * @param int $n * @return string */ public static function renderBytes( $n ){ $i = 0; $dp = 0; while( $n >= 1024 ){ $i++; $dp++; $n /= 1024; } $s = number_format( $n, $dp, '.', ',' ); // trim trailing zeros from decimal places $a = explode('.',$s); if( isset($a[1]) ){ $s = $a[0]; $d = trim($a[1],'0') and $s .= '.'.$d; } $units = [ ' bytes', ' KB', ' MB', ' GB', ' TB' ]; $s .= $units[$i]; return $s; } /** * @return Loco_mvc_FileParams */ public static function create( Loco_fs_File $file ) { return new Loco_mvc_FileParams( [], $file ); } /** * Override does lazy property initialization * @param array $props initial extra properties */ public function __construct( array $props, Loco_fs_File $file ){ parent::__construct( [ 'name' => '', 'path' => '', 'relpath' => '', 'reltime' => '', 'bytes' => 0, 'size' => '', 'imode' => '', 'smode' => '', 'owner' => '', 'group' => '', ] + $props ); $this->file = $file; } /** * {@inheritdoc} * Override to get live information from file object */ #[ReturnTypeWillChange] public function offsetGet( $prop ){ $getter = [ $this, '_get_'.$prop ]; if( is_callable($getter) ){ return call_user_func( $getter ); } return parent::offsetGet($prop); } /** * {@inheritdoc} * Override to ensure all properties populated */ #[ReturnTypeWillChange] public function getArrayCopy(){ $a = []; foreach( $this as $prop => $dflt ){ $a[$prop] = $this[$prop]; } return $a; } /** * @internal * @return string */ private function _get_name(){ return $this->file->basename(); } /** * @internal * @return string */ private function _get_path(){ return $this->file->getPath(); } /** * @internal * @return string */ private function _get_relpath(){ return $this->file->getRelativePath( loco_constant('WP_CONTENT_DIR') ); } /** * Using slightly modified version of WordPress's Human time differencing * + Added "Just now" when in the last 30 seconds * @internal * @return string */ private function _get_reltime(){ $time = $this->has('mtime') ? $this['mtime'] : $this->file->modified(); $time_diff = time() - $time; // use same time format as posts listing when in future or more than a day ago if( $time_diff < 0 || $time_diff >= 86400 ){ return Loco_mvc_ViewParams::date_i18n( $time, __('Y/m/d','default') ); } if( $time_diff < 30 ){ // translators: relative time when something happened in the last 30 seconds return __('Just now','loco-translate'); } // translators: %s: Human-readable time difference. return sprintf( __('%s ago','default'), human_time_diff($time) ); } /** * @internal * @return int */ private function _get_bytes(){ return $this->file->size(); } /** * @internal * @return string */ private function _get_size(){ return self::renderBytes( $this->_get_bytes() ); } /** * Get octal file mode * @internal * @return string */ private function _get_imode(){ $mode = new Loco_fs_FileMode( $this->file->mode() ); return (string) $mode; } /** * Get rwx file mode * @internal * @return string */ private function _get_smode(){ $mode = new Loco_fs_FileMode( $this->file->mode() ); return $mode->format(); } /** * Get file owner name * @internal * @return string */ private function _get_owner(){ if( ( $uid = $this->file->uid() ) && function_exists('posix_getpwuid') && ( $a = posix_getpwuid($uid) ) ){ return $a['name']; } return sprintf('%u',$uid); } /** * Get group owner name * @internal * @return string */ private function _get_group(){ if( ( $gid = $this->file->gid() ) && function_exists('posix_getpwuid') && ( $a = posix_getgrgid($gid) ) ){ return $a['name']; } return sprintf('%u',$gid); } /** * Print pseudo console line * @return string; */ public function ls(){ $this->e('smode'); echo ' '; $this->e('owner'); echo ':'; $this->e('group'); echo ' '; $this->e('relpath'); return ''; } } PostParams.php 0000644 00000005154 14720714126 0007357 0 ustar 00 <?php /** * Postdata wrapper */ class Loco_mvc_PostParams extends Loco_mvc_ViewParams { /** * @var Loco_mvc_PostParams */ private static $singleton; /** * Get actual postdata, not hacked postdata WordPress ruined with wp_magic_quotes * @return Loco_mvc_PostParams */ public static function get(){ if( ! self::$singleton ){ self::$singleton = self::create(); } return self::$singleton; } /** * @return void */ public static function destroy(){ self::$singleton = null; } /** * Check if either magic_quotes_gpc or magic_quotes_runtime are enabled. * Note that get_magic_quotes_gpc and get_magic_quotes_runtime are deprecated as of PHP 7.4 and always return false * @return bool */ private static function has_magic_quotes(){ // phpcs:ignore -- PHP version is checked prior to deprecated function call. return version_compare(PHP_VERSION,'7.4','<') && ( get_magic_quotes_gpc() || get_magic_quotes_runtime() ); } /** * Construct clean postdata from current HTTP request * @return self */ public static function create(){ $post = []; if( 'POST' === $_SERVER['REQUEST_METHOD'] ){ // attempt to use clean input if available (without added slashes) $raw = (string) file_get_contents('php://input'); if( '' !== $raw && ! self::has_magic_quotes() ){ parse_str( $raw, $post ); } // else reverse wp_magic_quotes (assumes no other process has hacked the array) else { $post = stripslashes_deep( $_POST ); } } return new Loco_mvc_PostParams( $post ); } /** * Construct postdata from a series of value pairs. * This is used in tests to simulate how a form is serialized and posted * * @return self */ public static function fromSerial( array $serial ){ $pairs = []; foreach( $serial as $pair ){ $pairs[] = rawurlencode($pair[0]).'='.rawurlencode($pair[1]); } parse_str( implode('&',$pairs), $parsed ); return new Loco_mvc_PostParams( $parsed ); } /** * Collapse nested array down to series of scalar forms * @return string[] */ public function getSerial(){ $serial = []; $query = http_build_query( $this->getArrayCopy(), false, '&' ); foreach( explode('&',$query) as $str ){ $serial[] = array_map( 'urldecode', explode( '=', $str, 2 ) ); } return $serial; } } ViewParams.php 0000644 00000011771 14720714126 0007346 0 ustar 00 <?php /** * */ class Loco_mvc_ViewParams extends ArrayObject implements JsonSerializable { /** * Default escape function for view type is HTML * @param string $text * @return string */ public function escape( $text ){ return htmlspecialchars( (string) $text, ENT_COMPAT, 'UTF-8' ); } /** * format integer as string date, including time according to user settings * @param int $u unix timestamp * @param string|null $f date format * @return string */ public static function date_i18n( $u, $f = null ){ static $tf, $df, $tz; if( is_null($f) ){ if( is_null($tf) ){ $tf = get_option('time_format') or $tf = 'g:i A'; $df = get_option('date_format') or $df= 'M jS Y'; } $f = $df.' '.$tf; } // date_i18n was replaced with wp_date in WP 5.3 if( function_exists('wp_date') ){ return wp_date($f,$u); } // date_i18n expects timestamp to include offset if( is_null($tz) ){ try { $wp = get_option('timezone_string') or $wp = date_default_timezone_get(); $tz = new DateTimeZone($wp); } catch( Exception $e ){ $tz = new DateTimeZone('UTC'); } } $d = new DateTime(null,$tz); $d->setTimestamp($u); return date_i18n( $f, $u + $d->getOffset() ); } /** * Wrapper for sprintf so we can handle PHP 8 exceptions * @param string $format * @return string */ public static function format( $format, array $args ){ try { return vsprintf($format,$args); } // Note that PHP8 will throw Error (not Exception), PHP 7 will trigger E_WARNING catch( Error $e ){ Loco_error_AdminNotices::warn( $e->getMessage().' in vsprintf('.var_export($format,true).')' ); return ''; } } /** * @internal * @param string $p property name * @return mixed */ public function __get( $p ){ return $this->offsetExists($p) ? $this->offsetGet($p) : null; } /** * Test if a property exists, even if null * @param string $p property name * @return bool */ public function has( $p ){ return $this->offsetExists($p); } /** * Print escaped property value * @param string $p property key * @return string empty string */ public function e( $p ){ $text = $this->__get($p); echo $this->escape( $text ); return ''; } /** * Print property as string date, including time * @param string $p property name * @param string $f date format * @return string empty string */ public function date( $p, $f = null ){ $u = (int) $this->__get($p); if( $u > 0 ){ echo $this->escape( self::date_i18n($u,$f) ); } return ''; } /** * Print property as a string-formatted number * @param string $p property name * @param int $dp optional decimal places * @return string empty string */ public function n( $p, $dp = 0 ){ // number_format_i18n is pre-escaped for HTML echo number_format_i18n( $this->__get($p), $dp ); return ''; } /** * Print property with passed formatting string * e.g. $params->f('name', 'My name is %s' ); * @param string $p property name * @param string $f formatting string * @return string empty string */ public function f( $p, $f = '%s' ){ echo $this->escape( self::format( $f, [$this->__get($p)] ) ); return ''; } /** * Print property value for JavaScript * @param string $p property name * @return string empty string */ public function j( $p ){ echo json_encode($this->__get($p) ); return ''; } /** * @return array */ #[ReturnTypeWillChange] public function jsonSerialize(){ return $this->getArrayCopy(); } /** * Fetch whole object as JSON * @return string */ public function exportJson(){ return json_encode( $this->jsonSerialize() ); } /** * Merge parameters into ours * @return Loco_mvc_ViewParams */ public function concat( ArrayObject $more ){ foreach( $more as $name => $value ){ $this[$name] = $value; } return $this; } /** * Debugging function * @codeCoverageIgnore */ public function dump(){ echo '<pre>',$this->escape( json_encode( $this->getArrayCopy(),JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE ) ),'</pre>'; } /** * @param callable $callback * @return Loco_mvc_ViewParams */ public function sort( $callback ){ $raw = $this->getArrayCopy(); uasort( $raw, $callback ); $this->exchangeArray( $raw ); return $this; } } AjaxController.php 0000644 00000004125 14720714126 0010212 0 ustar 00 <?php /** * */ abstract class Loco_mvc_AjaxController extends Loco_mvc_Controller { /** * Request arguments injected from Router * @var ArrayObject */ private $input; /** * Data to respond with as JSON * @var ArrayObject */ private $output; /** * Pre-init call invoked by router * @param mixed[] * @return void */ final public function _init( array $args ){ $this->auth(); $this->output = new ArrayObject; $this->input = new ArrayObject( $args ); // avoid fatal error if json extension is missing loco_check_extension('json'); } /** * Get posted data and validate nonce in the process * @return Loco_mvc_PostParams */ protected function validate(){ $route = $this->input['route']; if( ! $this->checkNonce($route) ){ throw new Loco_error_Exception( sprintf('Ajax %s action requires postdata with nonce',$route) ); } return Loco_mvc_PostParams::get(); } /** * {@inheritdoc} */ public function get( $prop ){ return isset($this->input[$prop]) ? $this->input[$prop] : null; } /** * {@inheritdoc} */ public function set( $prop, $value ){ $this->output[$prop] = $value; return $this; } /** * @return string JSON */ public function render(){ $data = [ 'data' => $this->output->getArrayCopy(), ]; // non-fatal notices deliberately not in "error" key $array = Loco_error_AdminNotices::destroy(); if( $array ){ $data['notices'] = $array; } return json_encode( $data ); } /** * Pretty json encode if PHP version allows * protected function json_encode( $data ){ $opts = 0; if( defined('JSON_PRETTY_PRINT') ){ $opts |= JSON_PRETTY_PRINT; } if( defined('JSON_UNESCAPED_SLASHES') ){ $opts |= JSON_UNESCAPED_SLASHES; } return json_encode( $data, $opts ); }*/ } AjaxRouter.php 0000644 00000015465 14720714126 0007360 0 ustar 00 <?php /** * Handles execution of Ajax actions and rendering of JSON */ class Loco_mvc_AjaxRouter extends Loco_hooks_Hookable { /** * Current ajax controller * @var Loco_mvc_AjaxController */ private $ctrl; /** * @var Loco_output_Buffer */ private $buffer; /** * Generate a GET request URL containing required routing parameters * @param string $route * @param array $args * @return string */ public static function generate( $route, array $args = [] ){ // validate route autoload if debugging if( loco_debugging() ){ class_exists( self::routeToClass($route) ); } $args += [ 'route' => $route, 'action' => 'loco_ajax', 'loco-nonce' => wp_create_nonce($route), ]; return admin_url('admin-ajax.php','relative').'?'.http_build_query($args); } /** * Create a new ajax router and starts buffering output immediately */ public function __construct(){ $this->buffer = Loco_output_Buffer::start(); parent::__construct(); } /** * "init" action callback. * early-ish hook that ensures controllers can initialize */ public function on_init(){ try { $class = self::routeToClass( $_REQUEST['route'] ); // autoloader will throw error if controller class doesn't exist $this->ctrl = new $class; $this->ctrl->_init( $_REQUEST ); // hook name compatible with AdminRouter, plus additional action for ajax hooks to set up do_action('loco_admin_init', $this->ctrl ); do_action('loco_ajax_init', $this->ctrl ); } catch( Loco_error_Exception $e ){ $this->ctrl = null; // throw $e; // <- debug } } /** * @param string $route * @return string */ private static function routeToClass( $route ){ $route = explode( '-', $route ); // convert route to class name, e.g. "foo-bar" => "Loco_ajax_foo_BarController" $key = count($route) - 1; $route[$key] = ucfirst( $route[$key] ); return 'Loco_ajax_'.implode('_',$route).'Controller'; } /** * Common ajax hook for all Loco admin JSON requests * Note that tests call renderAjax directly. * @codeCoverageIgnore */ public function on_wp_ajax_loco_json(){ $json = $this->renderAjax(); $this->exitScript( $json, [ 'Content-Type' => 'application/json; charset=UTF-8', ] ); } /** * Additional ajax hook for download actions that won't be JSON * Note that tests call renderDownload directly. * @codeCoverageIgnore */ public function on_wp_ajax_loco_download(){ $file = null; $ext = null; $data = $this->renderDownload(); if( is_string($data) ){ $path = ( $this->ctrl ? $this->ctrl->get('path') : '' ) or $path = 'error.json'; $file = new Loco_fs_File( $path ); $ext = $file->extension(); } else if( $data instanceof Exception ){ $data = sprintf('%s in %s:%u', $data->getMessage(), basename($data->getFile()), $data->getLine() ); } else { $data = (string) $data; } $mimes = [ 'po' => 'application/x-gettext', 'pot' => 'application/x-gettext', 'mo' => 'application/x-gettext-translation', 'php' => 'application/x-httpd-php-source', 'json' => 'application/json', 'zip' => 'application/zip', 'xml' => 'text/xml', ]; $headers = []; if( $file instanceof Loco_fs_File && isset($mimes[$ext]) ){ $headers['Content-Type'] = $mimes[$ext].'; charset=UTF-8'; $headers['Content-Disposition'] = 'attachment; filename='.$file->basename(); } else { $headers['Content-Type'] = 'text/plain; charset=UTF-8'; } $this->exitScript( $data, $headers ); } /** * Exit script before WordPress shutdown, avoids hijacking of exit via wp_die_ajax_handler. * Also gives us a final chance to check for output buffering problems. * @codeCoverageIgnore */ private function exitScript( $str, array $headers ){ try { do_action('loco_admin_shutdown'); Loco_output_Buffer::clear(); $this->buffer = null; Loco_output_Buffer::check(); $headers['Content-Length'] = strlen($str); foreach( $headers as $name => $value ){ header( $name.': '.$value ); } } catch( Exception $e ){ Loco_error_AdminNotices::add( Loco_error_Exception::convert($e) ); $str = $e->getMessage(); } echo $str; exit(0); } /** * Execute Ajax controller to render JSON response body * @return string */ public function renderAjax(){ try { // respond with deferred failure from initAjax if( ! $this->ctrl ){ $route = isset($_REQUEST['route']) ? $_REQUEST['route'] : ''; // translators: Fatal error where %s represents an unexpected value throw new Loco_error_Exception( sprintf( __('Ajax route not found: "%s"','loco-translate'), $route ) ); } // else execute controller to get json output $json = $this->ctrl->render(); if( is_null($json) || '' === $json ){ throw new Loco_error_Exception( __('Ajax controller returned empty JSON','loco-translate') ); } } catch( Loco_error_Exception $e ){ $json = json_encode( [ 'error' => $e->jsonSerialize(), 'notices' => Loco_error_AdminNotices::destroy() ] ); } catch( Exception $e ){ $e = Loco_error_Exception::convert($e); $json = json_encode( [ 'error' => $e->jsonSerialize(), 'notices' => Loco_error_AdminNotices::destroy() ] ); } $this->buffer->discard(); return $json; } /** * Execute ajax controller to render something other than JSON * @return string|Exception */ public function renderDownload(){ try { // respond with deferred failure from initAjax if( ! $this->ctrl ){ throw new Loco_error_Exception( __('Download action not found','loco-translate') ); } // else execute controller to get raw output $data = $this->ctrl->render(); if( is_null($data) || '' === $data ){ throw new Loco_error_Exception( __('Download controller returned empty output','loco-translate') ); } } catch( Exception $e ){ $data = $e; } $this->buffer->discard(); return $data; } } AdminController.php 0000644 00000024377 14720714126 0010372 0 ustar 00 <?php /** * */ abstract class Loco_mvc_AdminController extends Loco_mvc_Controller { /** * @var Loco_mvc_View */ private $view; /** * Debugging timestamp (microseconds) * @var float */ private $bench; /** * Base url to plugin folder for web access * @var string */ private $baseurl; /** * @var string[] */ private $scripts = []; /** * Pre-init call invoked by router * @return static */ final public function _init( array $args ){ if( loco_debugging() ){ $this->bench = microtime( true ); } $this->view = new Loco_mvc_View( $args ); $this->auth(); // check essential extensions on all pages so admin notices are shown loco_check_extension('json'); loco_check_extension('mbstring'); // add contextual help tabs to current screen if there are any if( $screen = get_current_screen() ){ try { $this->view->cd('/admin/help'); $tabs = $this->getHelpTabs(); // always append common help tabs $tabs[ __('Help & support','loco-translate') ] = $this->view->render('tab-support'); // set all tabs and common sidebar $i = 0; foreach( $tabs as $title => $content ){ $id = sprintf('loco-help-%u', $i++ ); $screen->add_help_tab( compact('id','title','content') ); } $screen->set_help_sidebar( $this->view->render('side-bar') ); $this->view->cd('/'); } // avoid critical errors rendering non-critical part of page catch( Loco_error_Exception $e ){ $this->view->cd('/'); Loco_error_AdminNotices::add( $e ); } } // helper properties for loading static resources $this->baseurl = plugins_url( '', loco_plugin_self() ); // add common admin page resources $this->enqueueStyle('admin', ['wp-jquery-ui-dialog'] ); // load colour scheme is user has non-default $skin = get_user_option('admin_color'); if( $skin && 'fresh' !== $skin ){ $this->enqueueStyle( 'skins/'.$skin ); } // core minimized admin.js loaded on all pages before any other Loco scripts $this->enqueueScript('admin', ['jquery-ui-dialog'] ); $this->init(); return $this; } /** * Post-construct initializer that may be overridden by child classes * @return void */ public function init(){ } /** * "admin_title" filter, modifies HTML document title if we've set one */ public function filter_admin_title( $admin_title, $title ){ if( $view_title = $this->get('title') ){ $admin_title = $view_title.' ‹ '.$admin_title; } return $admin_title; } /** * "admin_footer_text" filter, modifies admin footer only on Loco pages */ public function filter_admin_footer_text(){ $url = apply_filters('loco_external', 'https://localise.biz/'); return '<span id="loco-credit">'.sprintf( '<span>%s</span> <a href="%s" target="_blank">Loco</a>', esc_html(__('Loco Translate is powered by','loco-translate')), esc_url($url) ).'</span>'; } /** * "update_footer" filter, prints Loco version number in admin footer */ public function filter_update_footer( /*$text*/ ){ $html = sprintf( '<span>v%s</span>', loco_plugin_version() ); if( $this->bench && ( $info = $this->get('_debug') ) ){ $html .= sprintf('<span>%ss</span>', number_format_i18n($info['time'],2) ); } return $html; } /** * "loco_external" filter callback, adds campaign identifier onto external links */ public function filter_loco_external( $url ){ $u = parse_url( $url ); if( isset($u['host']) && 'localise.biz' === $u['host'] ){ $query = http_build_query( [ 'utm_medium' => 'plugin', 'utm_campaign' => 'wp', 'utm_source' => 'admin', 'utm_content' => $this->get('_route') ] ); $url = 'https://localise.biz'.$u['path']; if( isset($u['query']) ){ $url .= '?'. $u['query'].'&'.$query; } else { $url .= '?'.$query; } if( isset($u['fragment']) ){ $url .= '#'.$u['fragment']; } } return $url; } /** * All admin screens must define help tabs, even if they return empty * @return array */ public function getHelpTabs(){ return []; } /** * {@inheritdoc} */ public function get( $prop ){ return $this->view->__get($prop); } /** * {@inheritdoc} */ public function set( $prop, $value ){ $this->view->set( $prop, $value ); return $this; } /** * Render template for echoing into admin screen * @param string $tpl template name * @param array $args template arguments * @return string */ public function view( $tpl, array $args = [] ){ /*if( ! $this->baseurl ){ throw new Loco_error_Debug('Did you mean to call $this->viewSnippet('.json_encode($tpl,JSON_UNESCAPED_SLASHES).') in '.get_class($this).'?'); }*/ $view = $this->view; foreach( $args as $prop => $value ){ $view->set( $prop, $value ); } // ensure JavaScript config present if any scripts are loaded if( $view->has('js') ) { $jsConf = $view->get( 'js' ); } else if( $this->scripts ){ $jsConf = new Loco_mvc_ViewParams; $this->set('js',$jsConf); } else { $jsConf = null; } if( $jsConf instanceof Loco_mvc_ViewParams ){ // ensure config has access to latest version information // we will use this to ensure scripts are not cached by browser, or hijacked by other plugins $jsConf->offsetSet('$v', [ loco_plugin_version(), $GLOBALS['wp_version']] ); $jsConf->offsetSet('$js', array_keys($this->scripts) ); $jsConf->offsetSet('WP_DEBUG', loco_debugging() ); // localize script if translations in memory if( is_textdomain_loaded('loco-translate') ){ $strings = new Loco_js_Strings; $jsConf->offsetSet('wpl10n',$strings->compile()); $strings->unhook(); unset( $strings ); // add currently loaded locale for passing plural equation into js. // note that plural rules come from our data, because MO is not trusted. $tag = apply_filters( 'plugin_locale', get_locale(), 'loco-translate' ); $jsConf->offsetSet('wplang', Loco_Locale::parse($tag) ); } // localized formatting from core translations global $wp_locale; if( is_object($wp_locale) && property_exists($wp_locale,'number_format') ){ $jsConf->offsetSet('wpnum', array_map([$this,'filter_number_format_i18n'],$wp_locale->number_format) ); } } // take benchmark for debugger to be rendered in footer if( $this->bench ){ $this->set('_debug', new Loco_mvc_ViewParams( [ 'time' => microtime(true) - $this->bench, ] ) ); } return $view->render( $tpl ); } /** * Shortcut to render template without full page arguments as per view * @param string $tpl * @return string */ public function viewSnippet( $tpl ){ return $this->view->render( $tpl ); } /** * Add CSS to head * @param string $name stem name of file, e.g "editor" * @param string[] $deps dependencies of this stylesheet * @return self */ public function enqueueStyle( $name, array $deps = [] ){ $base = $this->baseurl; if( ! $base ){ throw new Loco_error_Exception('Too early to enqueueStyle('.var_export($name,1).')'); } $id = 'loco-translate-'.strtr($name,'/','-'); // css always minified. sass in build env only $href = $base.'/pub/css/'.$name.'.css'; $vers = apply_filters( 'loco_static_version', loco_plugin_version(), $href ); wp_enqueue_style( $id, $href, $deps, $vers, 'all' ); return $this; } /** * Add JavaScript to footer * @param string $name stem name of file, e.g "editor" * @param string[] $deps dependencies of this script * @return string */ public function enqueueScript( $name, array $deps = [] ){ $base = $this->baseurl; if( ! $base ){ throw new Loco_error_Exception('Too early to enqueueScript('.json_encode($name).')'); } // use minimized javascript file. hook into script_loader_src to point at development source $href = $base.'/pub/js/min/'.$name.'.js'; $vers = apply_filters( 'loco_static_version', loco_plugin_version(), $href ); $id = 'loco-translate-'.strtr($name,'/','-'); wp_enqueue_script( $id, $href, $deps, $vers, true ); $this->scripts[$id] = $href; return $id; } /** * @param string $name * @return void */ public function dequeueScript( $name ){ $id = 'loco-translate-'.strtr($name,'/','-'); if( array_key_exists($id,$this->scripts) ){ wp_dequeue_script($id); unset($this->scripts[$id]); } } /** * @internal * @param string $tag * @param string $id * @return string */ public function filter_script_loader_tag( $tag, $id ) { if( array_key_exists($id,$this->scripts) ) { // Add element id for in-dom verification of expected scripts if( '<script ' === substr($tag,0,8) ){ // WordPress has started adding their own ID since v5.5 which simply appends -js to the handle $id .= '-js'; if( false === strpos($tag,$id) ){ $tag = '<script id="'.$id.'" '.substr($tag,8); } } } return $tag; } } View.php 0000644 00000016701 14720714126 0006200 0 ustar 00 <?php /** * View renderer * @property-read string $_content * @property-read string|null $_trash */ class Loco_mvc_View implements IteratorAggregate { /** * @var Loco_mvc_ViewParams */ private $scope; /** * View that is decorating current view * @var self */ private $parent; /** * Current template as full path to PHP file * @var string */ private $template; /** * Current working directory for finding templates by relative path * @var string */ private $cwd; /** * Name of current output buffer * @var string */ private $block; /** * @internal */ public function __construct( array $args = [] ){ $this->scope = new Loco_mvc_ViewParams( $args ); $this->cwd = loco_plugin_root().'/tpl'; } /** * Change base path for template paths * @param string $path relative to current directory * @return Loco_mvc_View */ public function cd( $path ){ if( $path && '/' === substr($path,0,1) ){ $this->cwd = untrailingslashit( loco_plugin_root().'/tpl'.$path ); } else { $this->cwd = untrailingslashit( $this->cwd.'/'.$path ); } return $this; } /** * @internal * Clean up if something abruptly stopped rendering before graceful end */ public function __destruct(){ if( $this->block ){ ob_end_clean(); } } /** * Render error screen HTML * @return string */ public static function renderError( Loco_error_Exception $e ){ $view = new Loco_mvc_View; try { $view->set( 'error', $e ); return $view->render( $e->getTemplate() ); } catch( Exception $e ){ return '<h1>'.esc_html( $e->getMessage() ).'</h1>'; } } /** * Make this view a child of another template. i.e. decorate this with that. * Parent will have access to original argument scope, but separate from now on * @param string $tpl * @return self the parent view */ private function extend( $tpl ){ $this->parent = new Loco_mvc_View; $this->parent->cwd = $this->cwd; $this->parent->setTemplate( $tpl ); return $this->parent; } /** * After start is called any captured output will be placed in the named variable * @param string $name * @return void */ private function start( $name ){ $this->stop(); $this->scope[$name] = null; $this->block = $name; } /** * When stop is called, buffered output is saved into current variable for output by parent template, or at end of script. * @return void */ private function stop(){ $content = ob_get_contents(); ob_clean(); if( $b = $this->block ){ if( isset($this->scope[$b]) ){ $content = $this->scope[$b].$content; } $this->scope[$b] = new _LocoViewBuffer($content); } $this->block = '_trash'; } /** * {@inheritDoc} */ #[ReturnTypeWillChange] public function getIterator(){ return $this->scope; } /** * @internal * @param string $prop * @return mixed */ public function __get( $prop ){ return $this->has($prop) ? $this->get($prop) : null; } /** * @param string $prop * @return bool */ public function has( $prop ){ return $this->scope->offsetExists($prop); } /** * Get property after checking with self::has * @param string $prop * @return mixed */ public function get( $prop ){ return $this->scope[$prop]; } /** * Set a view argument * @param string $prop * @param mixed $value * @return self */ public function set( $prop, $value ){ $this->scope[$prop] = $value; return $this; } /** * Main entry to rendering complete template * @param string $tpl template name excluding extension * @param array|null $args extra arguments to set in view scope * @param self|null $parent parent view rendering this view * @return string */ public function render( $tpl, array $args = null, Loco_mvc_View $parent = null ){ if( $this->block ){ return $this->fork()->render( $tpl, $args, $this ); } $this->setTemplate($tpl); if( $parent && $this->template === $parent->template ){ throw new Loco_error_Exception('Avoiding infinite loop'); } if( is_array($args) ){ foreach( $args as $prop => $value ){ $this->set($prop, $value); } } ob_start(); $content = $this->buffer(); ob_end_clean(); return $content; } /** * Do actual render of currently validated template path * @return string content not captured in sub-blocks */ private function buffer(){ $this->start('_trash'); $this->execTemplate( $this->template ); $this->stop(); $this->block = null; // decorate via parent view if there is one if( $this->parent ){ $this->parent->scope = clone $this->scope; $this->parent->set('_content', $this->_trash ); return $this->parent->buffer(); } // else at the root of view chain return (string) $this->_trash; } /** * Set current template * @param string $tpl Path to template, excluding file extension */ public function setTemplate( $tpl ){ $file = new Loco_fs_File( $tpl.'.php' ); $file->normalize( $this->cwd ); if( ! $file->exists() ){ $debug = str_replace( loco_plugin_root().'/', '', $file->getPath() ); throw new Loco_error_Exception( 'Template not found: '.$debug ); } $this->cwd = $file->dirname(); $this->template = $file->getPath(); } /** * @return Loco_mvc_View */ private function fork(){ $view = new Loco_mvc_View; $view->cwd = $this->cwd; $view->scope = clone $this->scope; return $view; } /** * Do actual runtime template include * @param string $template * @return void */ private function execTemplate( $template ){ $params = $this->scope; extract( $params->getArrayCopy() ); include $template; } /** * Link generator * @param string $route page route, e.g. "config" * @param array $args optional page arguments * @return Loco_mvc_ViewParams */ public function route( $route, array $args = [] ){ return new Loco_mvc_ViewParams( [ 'href' => Loco_mvc_AdminRouter::generate( $route, $args ), ] ); } /** * Shorthand for `echo esc_html( sprintf( ...` * @param string $text * @return string */ private static function e( $text ){ if( 1 < func_num_args() ){ $args = func_get_args(); $text = call_user_func_array( 'sprintf', $args ); } echo htmlspecialchars( $text, ENT_COMPAT, 'UTF-8' ); return ''; } } /** * @internal */ class _LocoViewBuffer { private $s; public function __construct( $s ){ $this->s = $s; } public function __toString(){ return $this->s; } } HiddenFields.php 0000644 00000002621 14720714126 0007604 0 ustar 00 <?php /** * */ class Loco_mvc_HiddenFields extends Loco_mvc_ViewParams { /** * @internal * Echo all hidden fields to output buffer */ public function _e(){ foreach( $this as $name => $value ){ echo '<input type="hidden" name="',$this->escape($name),'" value="',$this->escape($value),'" />'; } } /** * Add a nonce field * @param string action passed to wp_create_nonce * @return Loco_mvc_HiddenFields */ public function setNonce( $action ){ $this['loco-nonce'] = wp_create_nonce( $action ); return $this; } /** * @return string */ public function getNonce() { return $this['loco-nonce']; } /** * Load postdata fields * @param Loco_mvc_PostParams post data * @return Loco_mvc_HiddenFields */ public function addPost( Loco_mvc_PostParams $post ){ foreach( $post->getSerial() as $pair ){ $this[ $pair[0] ] = isset($pair[1]) ? $pair[1] : ''; } return $this; } /** * Append arguments to a URL * @param string optional base url * @return string full URL with query string */ public function getHref( $base = '' ){ $query = http_build_query($this->getArrayCopy()); $sep = false === strpos($base,'?') ? '?' : '&'; return $base.$sep.$query; } } AdminRouter.php 0000644 00000030141 14720714126 0007511 0 ustar 00 <?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 ‹ 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 ‹ 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 ‹ 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 ‹ 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_screen( WP_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 initPage( WP_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 screenToPage( WP_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}', 0, strlen($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 UnexpectedValueException( sprintf('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; } }
| ver. 1.4 |
Github
|
.
| PHP 8.0.30 | Génération de la page: 0.01 |
proxy
|
phpinfo
|
Réglages