FsConnectController.php000066600000013364152140441670011222 0ustar00expand() as $file ){ if( ! $this->api->authorizeDelete($file) ){ return false; } } // else no dependants failed deletable test return true; } /** * @param Loco_fs_File file being moved (must exist) * @param Loco_fs_File target path (should not exist) * @return bool */ private function authorizeMove( Loco_fs_File $source, Loco_fs_File $target = null ){ return $this->api->authorizeMove($source,$target); } /** * @param Loco_fs_File new file path (should not exist) * @return bool */ private function authorizeCreate( Loco_fs_File $file ){ return $this->api->authorizeCreate($file); } /** * @return bool */ private function authorizeUpdate( Loco_fs_File $file ){ if( ! $this->api->authorizeUpdate($file) ){ return false; } // if backups are enabled, we need to be able to create new files too (i.e. update parent directory) if( Loco_data_Settings::get()->num_backups && ! $this->api->authorizeCopy($file) ){ return false; } // updating file may also recompile binary, which may or may not exist $files = new Loco_fs_Siblings( $file ); if( $file = $files->getBinary() ){ return $this->api->authorizeSave($file); } // else no dependants to update return true; } /** * {@inheritdoc} */ public function render(){ // establish operation being authorized (create,delete,etc..) $post = $this->validate(); $type = $post->auth; $func = 'authorize'.ucfirst($type); $auth = array( $this, $func ); if( ! is_callable($auth) ){ throw new Loco_error_Exception('Unexpected file operation'); } // all auth methods require at least one file argument $file = new Loco_fs_File( $post->path ); $base = loco_constant('WP_CONTENT_DIR'); $file->normalize($base); $args = array($file); // some auth methods also require a destination/target (move,copy,etc..) if( $dest = $post->dest ){ $file = new Loco_fs_File($dest); $file->normalize($base); $args[] = $file; } // call auth method and respond with status and prompt HTML if connect required try { $this->api = new Loco_api_WordPressFileSystem; if( call_user_func_array($auth,$args) ){ $this->set( 'authed', true ); $this->set( 'valid', $this->api->getOutputCredentials() ); $this->set( 'creds', $this->api->getInputCredentials() ); $this->set( 'method', $this->api->getFileSystem()->method ); $this->set( 'success', __('Connected to remote file system','loco-translate') ); // warning when writing to this location is risky (overwrites during wp update) if( Loco_data_Settings::get()->fs_protect && $file->getUpdateType() ){ if( 'create' === $type ){ $message = __('This file may be overwritten or deleted when you update WordPress','loco-translate'); } else if( 'delete' === $type ){ $message = __('This directory is managed by WordPress, be careful what you delete','loco-translate'); } else if( 'move' === $type ){ $message = __('This directory is managed by WordPress. Removed files may be restored during updates','loco-translate'); } else { $message = __('Changes to this file may be overwritten or deleted when you update WordPress','loco-translate'); } $this->set('warning',$message); } } else if( $html = $this->api->getForm() ){ $this->set( 'authed', false ); $this->set( 'prompt', $html ); // supporting text based on file operation type explains why auth is required if( 'create' === $type ){ $message = __('Creating this file requires permission','loco-translate'); } else if( 'delete' === $type ){ $message = __('Deleting this file requires permission','loco-translate'); } else if( 'move' === $type ){ $message = __('This move operation requires permission','loco-translate'); } else { $message = __('Saving this file requires permission','loco-translate'); } // message is printed before default text, so needs delimiting. $this->set('message',$message.'.'); } else { throw new Loco_error_Exception('Failed to get credentials form'); } } catch( Loco_error_WriteException $e ){ $this->set('authed', false ); $this->set('reason', $e->getMessage() ); } return parent::render(); } }DiffController.php000066600000003572152140441670010210 0ustar00validate(); // require x2 valid files for diffing if( ! $post->lhs || ! $post->rhs ){ throw new InvalidArgumentException('Path parameters required'); } $dir = loco_constant('WP_CONTENT_DIR'); $lhs = new Loco_fs_File( $post->lhs ); $lhs->normalize($dir); $rhs = new Loco_fs_File( $post->rhs ); $rhs->normalize($dir); // avoid diffing non Gettext source files $exts = array_flip( array( 'pot', 'pot~', 'po', 'po~' ) ); /* @var $file Loco_fs_File */ foreach( array($lhs,$rhs) as $file ){ if( ! $file->exists() ){ throw new InvalidArgumentException('File paths must exist'); } if( ! $file->underContentDirectory() ){ throw new InvalidArgumentException('Files must be under '.basename($dir) ); } $ext = $file->extension(); if( ! isset($exts[$ext]) ){ throw new InvalidArgumentException('Disallowed file extension'); } } // OK to diff files as HTML table $renderer = new Loco_output_DiffRenderer; $emptysrc = $renderer->_startDiff().$renderer->_endDiff(); $tablesrc = $renderer->renderFiles( $rhs, $lhs ); if( $tablesrc === $emptysrc ){ // translators: Where %s is a file name $message = __('Revisions are identical, you can delete %s','loco-translate'); $this->set( 'error', sprintf( $message, $rhs->basename() ) ); } else { $this->set( 'html', $tablesrc ); } return parent::render(); } } SyncController.php000066600000006211152140441670010245 0ustar00validate(); $bundle = Loco_package_Bundle::fromId( $post->bundle ); $project = $bundle->getProjectById( $post->domain ); if( ! $project instanceof Loco_package_Project ){ throw new Loco_error_Exception('No such project '.$post->domain); } $file = new Loco_fs_File( $post->path ); $base = loco_constant('WP_CONTENT_DIR'); $file->normalize( $base ); // POT file always synced with source code (even if a PO being used as POT) if( 'pot' === $post->type ){ $potfile = null; } // allow post data to force a template file path else if( $path = $post->sync ){ $potfile = new Loco_fs_File($path); $potfile->normalize( $base ); } // else use project-configured template if one is defined else { $potfile = $project->getPot(); } // sync with POT if it exists if( $potfile && $potfile->exists() ){ $this->set('pot', $potfile->basename() ); try { $data = Loco_gettext_Data::load($potfile); } catch( Exception $e ){ // translators: Where %s is the name of the invalid POT file throw new Loco_error_ParseException( sprintf( __('Translation template is invalid (%s)','loco-translate'), $potfile->basename() ) ); } } // else sync with source code else { $this->set('pot', '' ); $domain = (string) $project->getDomain(); $extr = new Loco_gettext_Extraction($bundle); $extr->addProject($project); // bail if any files were skipped if( $list = $extr->getSkipped() ){ $n = count($list); $maximum = Loco_mvc_FileParams::renderBytes( wp_convert_hr_to_bytes( Loco_data_Settings::get()->max_php_size ) ); $largest = Loco_mvc_FileParams::renderBytes( $extr->getMaxPhpSize() ); // Translators: Where %2$s is the maximum size of a file that will be included and %3$s is the largest encountered $text = _n('One file has been skipped because it\'s %3$s. (Max is %2$s). Check all strings are present before saving.','%s files over %2$s have been skipped. (Largest is %3$s). Check all strings are present before saving.',$n,'loco-translate'); $text = sprintf( $text, number_format($n), $maximum, $largest ); // not failing, just warning. Nothing will be saved until user saves editor state Loco_error_AdminNotices::warn( $text ); } // OK to return available strings $data = $extr->includeMeta()->getTemplate($domain); } $this->set( 'po', $data->jsonSerialize() ); return parent::render(); } }DownloadConfController.php000066600000001661152140441670011712 0ustar00validate(); $bundle = $this->getBundle(); $file = new Loco_fs_File( $this->get('path') ); // TODO should we download axtual loco.xml file if bundle is configured from it? //$file->normalize( $bundle->getDirectoryPath() ); //if( $file->exists() ){} $writer = new Loco_config_BundleWriter($bundle); switch( $file->extension() ){ case 'xml': return $writer->toXml(); case 'json': return json_encode( $writer->jsonSerialize() ); } // @codeCoverageIgnoreStart throw new Loco_error_Exception('Specify either XML or JSON file path'); } }MsginitController.php000066600000011701152140441670010743 0ustar00get('use-selector') ){ $tag = $this->get('select-locale'); } else { $tag = $this->get('custom-locale'); } $locale = Loco_Locale::parse($tag); if( ! $locale->isValid() ){ throw new Loco_error_LocaleException('Invalid locale'); } return $locale; } /** * {@inheritdoc} */ public function render(){ $post = $this->validate(); $bundle = $this->getBundle(); $project = $this->getProject( $bundle ); $domain = (string) $project->getDomain(); $locale = $this->getLocale(); $suffix = (string) $locale; // The front end posts a template path, so we must replace the actual locale code $base = loco_constant('WP_CONTENT_DIR'); $path = $post->path[ $post['select-path'] ]; // The request_filesystem_credentials function will try to access the "path" field later $_POST['path'] = $path; $pofile = new Loco_fs_LocaleFile( $path ); if( $suffix !== $pofile->getSuffix() ){ $pofile = $pofile->cloneLocale( $locale ); if( $suffix !== $pofile->getSuffix() ){ throw new Loco_error_Exception('Failed to suffix file path with locale code'); } } // target PO should not exist yet $pofile->normalize( $base ); $api = new Loco_api_WordPressFileSystem; $api->authorizeCreate( $pofile ); // Target MO probably doesn't exist, but we don't want to overwrite it without asking $mofile = $pofile->cloneExtension('mo'); if( $mofile->exists() ){ throw new Loco_error_Exception( __('MO file exists for this language already. Delete it first','loco-translate') ); } /*/ Same for JSON file, but WordPress >= only 5 $jsfile = function_exists('wp_set_script_translations') ? $pofile->cloneExtension('json') : null; if( $jsfile && $jsfile->exists() ){ throw new Loco_error_Exception( __('JSON file exists for this language already. Delete it first','loco-translate') ); }*/ // Permit forcing of any parsable file as strings template if( $source = $post->source ){ $potfile = new Loco_fs_File( $source ); $potfile->normalize( $base ); $data = Loco_gettext_Data::load($potfile); // Remove target strings when copying PO if( $post->strip ){ $data->strip(); } } // else parse POT file if project defines one that exists else if( ( $potfile = $project->getPot() ) && $potfile->exists() ){ $data = Loco_gettext_Data::load($potfile); } // else extract directly from source code, assuming domain passed though from front end else { $extr = new Loco_gettext_Extraction( $bundle ); $data = $extr->addProject($project)->includeMeta()->getTemplate($domain); $potfile = null; } // Let template define Project-Id-Version, else set header to current project name $headers = array(); $vers = $data->getHeaders()->{'Project-Id-Version'}; if( ! $vers || 'PACKAGE VERSION' === $vers ){ $headers['Project-Id-Version'] = $project->getName(); } // relative path from bundle root to the template/source this file was created from if( $potfile && $post->link ){ $headers['X-Loco-Template'] = $potfile->getRelativePath( $bundle->getDirectoryPath() ); } $data->localize( $locale, $headers ); $posize = $pofile->putContents( $data->msgcat() ); $mosize = $mofile->putContents( $data->msgfmt() ); //$jssize = $jsfile && ( $sub = $data->splitJs() ) ? $jsfile->putContents($data->jedize($domain,$sub)) : 0; // set debug response data $this->set( 'debug', array ( 'poname' => $pofile->basename(), 'posize' => $posize, 'mosize' => $mosize, //'jssize' => $jssize, 'source' => $potfile ? $potfile->basename() : '', ) ); // push recent items on file creation // TODO push project and locale file Loco_data_RecentItems::get()->pushBundle( $bundle )->persist(); // front end will redirect to the editor $type = strtolower( $this->get('type') ); $this->set( 'redirect', Loco_mvc_AdminRouter::generate( sprintf('%s-file-edit',$type), array ( 'path' => $pofile->getRelativePath($base), 'bundle' => $bundle->getHandle(), 'domain' => $project->getId(), ) ) ); return parent::render(); } }DownloadController.php000066600000003642152140441670011105 0ustar00validate(); // we need a path, but it may not need to exist $file = new Loco_fs_File( $this->get('path') ); $file->normalize( loco_constant( 'WP_CONTENT_DIR') ); $is_binary = 'mo' === strtolower( $file->extension() ); // posted source must be clean and must parse as whatever the file extension claims to be if( $raw = $post->source ){ // compile source if target is MO if( $is_binary ) { $raw = Loco_gettext_Data::fromSource($raw)->msgfmt(); } } // else file can be output directly if it exists. // note that files on disk will not be parsed or manipulated. they will download strictly as-is else if( $file->exists() ){ $raw = $file->getContents(); } /*/ else if PO exists but MO doesn't, we can compile it on the fly else if( ! $is_binary ){ }*/ else { throw new Loco_error_Exception('File not found and no source posted'); } // Observe UTF-8 BOM setting if( ! $is_binary ){ $has_bom = "\xEF\xBB\xBF" === substr($raw,0,3); $use_bom = (bool) Loco_data_Settings::get()->po_utf8_bom; // only alter file if valid UTF-8. Deferring detection overhead until required if( $has_bom !== $use_bom && 'UTF-8' === mb_detect_encoding( $raw, array('UTF-8','ISO-8859-1'), true ) ){ if( $use_bom ){ $raw = "\xEF\xBB\xBF".$raw; // prepend } else { $raw = substr($raw,3); // strip bom } } } return $raw; } }SaveController.php000066600000015334152140441670010235 0ustar00validate(); // path parameter must not be empty $path = $post->path; if( ! $path ){ throw new InvalidArgumentException('Path parameter required'); } // locale must be posted to indicate whether PO or POT $locale = $post->locale; if( is_null($locale) ){ throw new InvalidArgumentException('Locale parameter required'); } $pofile = new Loco_fs_LocaleFile( $path ); $pofile->normalize( loco_constant('WP_CONTENT_DIR') ); $poexists = $pofile->exists(); // ensure we only deal with PO/POT source files. // posting of MO file paths is permitted when PO is missing, but we're about to fix that $ext = $pofile->extension(); if( 'mo' === $ext ){ $pofile = $pofile->cloneExtension('po'); } else if( 'pot' === $ext ){ $locale = ''; } else if( 'po' !== $ext ){ throw new Loco_error_Exception('Invalid file path'); } // force the use of remote file system when configured from front end $api = new Loco_api_WordPressFileSystem; // data posted may be either 'multipart/form-data' (recommended for large files) if( isset($_FILES['po']) ){ $data = Loco_gettext_Data::fromSource( Loco_data_Upload::src('po') ); } // else 'application/x-www-form-urlencoded' by default else { $data = Loco_gettext_Data::fromSource( $post->data ); } // WordPress-ize some headers that differ from JavaScript libs if( $compile = (bool) $locale ){ $head = $data->getHeaders(); $head['Language'] = strtr( $locale, '-', '_' ); } // backup existing file before overwriting, but still allow if backups fails $num_backups = Loco_data_Settings::get()->num_backups; if( $num_backups && $poexists ){ try { $api->authorizeCopy( $pofile ); $backups = new Loco_fs_Revisions( $pofile ); $backups->create(); $backups->prune($num_backups); } catch( Exception $e ){ Loco_error_AdminNotices::debug( $e->getMessage() ); $message = __('Failed to create backup file in "%s". Check file permissions or disable backups','loco-translate'); Loco_error_AdminNotices::warn( sprintf( $message, $pofile->getParent()->basename() ) ); } } // commit file directly to disk $api->authorizeSave( $pofile ); $bytes = $pofile->putContents( $data->msgcat() ); $mtime = $pofile->modified(); // add bundle to recent items on file creation try { $bundle = $this->getBundle(); Loco_data_RecentItems::get()->pushBundle( $bundle )->persist(); } catch( Exception $e ){ // editor permitted to save files not in a bundle, so catching failures $bundle = null; } // start success data with bytes written and timestamp $this->set('locale', $locale ); $this->set('pobytes', $bytes ); $this->set('poname', $pofile->basename() ); $this->set('modified', $mtime); $this->set('datetime', Loco_mvc_ViewParams::date_i18n($mtime) ); // Compile MO and JSON files unless saving template if( $compile ){ try { $mofile = $pofile->cloneExtension('mo'); $api->authorizeSave( $mofile ); $bytes = $mofile->putContents( $data->msgfmt() ); $this->set( 'mobytes', $bytes ); Loco_error_AdminNotices::success( __('PO file saved and MO file compiled','loco-translate') ); } catch( Exception $e ){ Loco_error_AdminNotices::debug( $e->getMessage() ); Loco_error_AdminNotices::warn( __('PO file saved, but MO file compilation failed','loco-translate') ); $this->set( 'mobytes', 0 ); // prevent further compilation if MO failed $compile = false; } } else { Loco_error_AdminNotices::success( __('POT file saved','loco-translate') ); } /*/ Compile JSON translations for WordPress >= 5 if( $compile && $bundle && function_exists('wp_set_script_translations') ){ $bytes = 0; try { list($domain) = Loco_package_Project::splitId( $this->get('domain') ); // hash file reference according to WordPress logic (see load_script_textdomain) $base = $pofile->dirname().'/'.$pofile->filename(); foreach( $data->exportRefs('\\.jsx?') as $ref => $messages ){ if( '.min.js' === substr($ref,-7) ) { $ref = substr($ref,0,-7).'.js'; } // filter similarly to WP's `load_script_textdomain_relative_path` which is called from `load_script_textdomain` $ref = apply_filters( 'loco_script_relative_path', $ref, $domain ); // referenced file must exist in bundle, or will never be loaded and so not require a .json file $file = new Loco_fs_File( $bundle->getDirectoryPath().'/'.$ref ); if( $file->exists() && ! $file->isDirectory() ){ $file = new Loco_fs_File( $base.'-'.md5($ref).'.json' ); $api->authorizeSave( $file ); $bytes += $file->putContents( $data->jedize($domain,$messages) ); } else { Loco_error_AdminNotices::warn( sprintf('%s not found in bundle',$ref) ); } } // single JSON file containing all .js ref from this file if( $messages = $data->splitJs() ){ $file = $pofile->cloneExtension('json'); $api->authorizeSave( $file ); $bytes = $file->putContents( $data->jedize($domain,$messages) ); } } catch( Exception $e ){ Loco_error_AdminNotices::debug( $e->getMessage() ); Loco_error_AdminNotices::warn( __('JSON compilation failed','loco-translate') ); } $this->set( 'jsbytes', $bytes ); }*/ return parent::render(); } }XgettextController.php000066600000005532152140441670011152 0ustar00validate(); $bundle = $this->getBundle(); $project = $this->getProject( $bundle ); // target location may not be next to POT file at all $base = loco_constant('WP_CONTENT_DIR'); $target = new Loco_fs_Directory( $this->get('path') ); $target->normalize( $base ); if( $target->exists() && ! $target->isDirectory() ){ throw new Loco_error_Exception('Target is not a directory'); } // basename should be posted from front end $name = $this->get('name'); if( ! $name ){ throw new Loco_error_Exception('Front end did not post $name'); } // POT file shouldn't exist currently $potfile = new Loco_fs_File( $target.'/'.$name ); $api = new Loco_api_WordPressFileSystem; $api->authorizeCreate($potfile); // Do extraction and grab only given domain's strings $ext = new Loco_gettext_Extraction( $bundle ); $domain = $project->getDomain()->getName(); $data = $ext->addProject($project)->includeMeta()->getTemplate( $domain ); // additional headers to set in new POT file $head = $data->getHeaders(); $head['Project-Id-Version'] = $project->getName(); // write POT file to disk returning byte length $potsize = $potfile->putContents( $data->msgcat(true) ); // set response data for debugging if( loco_debugging() ){ $this->set( 'debug', array ( 'potname' => $potfile->basename(), 'potsize' => $potsize, 'total' => $ext->getTotal(), ) ); } // push recent items on file creation // TODO push project and locale file Loco_data_RecentItems::get()->pushBundle( $bundle )->persist(); // put flash message into session to be displayed on redirected page try { Loco_data_Session::get()->flash('success', __('Template file created','loco-translate') ); Loco_data_Session::close(); } catch( Exception $e ){ Loco_error_AdminNotices::debug( $e->getMessage() ); } // redirect front end to bundle view. Discourages manual editing of template $type = strtolower( $bundle->getType() ); $href = Loco_mvc_AdminRouter::generate( sprintf('%s-view',$type), array( 'bundle' => $bundle->getHandle(), ) ); $hash = '#loco-'.$project->getId(); $this->set( 'redirect', $href.$hash ); return parent::render(); } }common/BundleController.php000066600000002235152140441670012034 0ustar00get('bundle') ){ // type may be passed as separate argument if( $type = $this->get('type') ){ return Loco_package_Bundle::createType( $type, $id ); } // else embedded in standalone bundle identifier // TODO standardize this across all Ajax end points return Loco_package_Bundle::fromId($id); } // else may have type embedded in bundle throw new Loco_error_Exception('No bundle identifier posted'); } /** * @param Loco_package_Bundle * @return Loco_package_Project */ protected function getProject( Loco_package_Bundle $bundle ){ $project = $bundle->getProjectById( $this->get('domain') ); if( ! $project ){ throw new Loco_error_Exception('Failed to find translation project'); } return $project; } }FsReferenceController.php000066600000017134152140441670011526 0ustar00exists() ){ return $srcfile; } }*/ // reference may be resolvable via referencing PO file's location $pofile = new Loco_fs_File( $this->get('path') ); $pofile->normalize( loco_constant('WP_CONTENT_DIR') ); if( ! $pofile->exists() ){ throw new InvalidArgumentException('PO/POT file required to resolve reference'); } $search = new Loco_gettext_SearchPaths; $search->init($pofile); if( $srcfile = $search->match($refpath) ){ return $srcfile; } // check against PO file location when no search paths or search paths failed $srcfile = new Loco_fs_File($refpath); $srcfile->normalize( $pofile->dirname() ); if( $srcfile->exists() ){ return $srcfile; } // reference may be resolvable via known project roots try { $bundle = $this->getBundle(); // Loco extractions will always be relative to bundle root $srcfile = new Loco_fs_File( $refpath ); $srcfile->normalize( $bundle->getDirectoryPath() ); if( $srcfile->exists() ){ return $srcfile; } // check relative to parent theme root if( $bundle->isTheme() && ( $parent = $bundle->getParent() ) ){ $srcfile = new Loco_fs_File( $refpath ); $srcfile->normalize( $parent->getDirectoryPath() ); if( $srcfile->exists() ){ return $srcfile; } } // final attempt - search all project source roots // TODO is there too large a risk of false positives? especially with files like index.php /* @var $root Loco_fs_Directory */ /*foreach( $this->getProject($bundle)->getConfiguredSources() as $root ){ if( $root->isDirectory() ){ $srcfile = new Loco_fs_File( $refpath ); $srcfile->normalize( $root->getPath() ); if( $srcfile->exists() ){ return $srcfile; } } }*/ } catch( Loco_error_Exception $e ){ // permitted for there to be no bundle or project when viewing orphaned file } throw new Loco_error_Exception( sprintf('Failed to find source file matching "%s"',$refpath) ); } /** * {@inheritdoc} */ public function render(){ $post = $this->validate(); // at the very least we need a reference to examine if( ! $post->has('ref') ){ throw new InvalidArgumentException('ref parameter required'); } // reference must parse as : $ref = $post->ref; if( ! preg_match('/^(.+):(\\d+)$/', $ref, $r ) ){ throw new InvalidArgumentException('Invalid file reference, '.$ref ); } // find file or fail list( , $refpath, $refline ) = $r; $srcfile = $this->findSourceFile($refpath); // deny access to sensitive files if( 'wp-config.php' === $srcfile->basename() ){ throw new InvalidArgumentException('File access disallowed'); } // validate allowed source file types $conf = Loco_data_Settings::get(); $ext = strtolower( $srcfile->extension() ); $allow = array_merge( array('php','js'), $conf->php_alias, $conf->jsx_alias ); if( ! in_array($ext,$allow,true) ){ throw new InvalidArgumentException('File extension disallowed, '.$ext ); } // get file type from registered file extensions: $type = $conf->ext2type( $ext ); $this->set('type', $type ); $this->set('line', (int) $refline ); $this->set('path', $srcfile->getRelativePath( loco_constant('WP_CONTENT_DIR') ) ); // source code will be HTML-tokenized into multiple lines $code = array(); // observe the same size limits for source highlighting as for string extraction as tokenizing will use the same amount of juice $maxbytes = wp_convert_hr_to_bytes( $conf->max_php_size ); // tokenizers require gettext utilities, easiest just to ping the extraction library if( ! class_exists('Loco_gettext_Extraction',true) ){ throw new RuntimeException('Failed to load tokenizers'); // @codeCoverageIgnore } // PHP is the most likely format. if( 'php' === $type && ( $srcfile->size() <= $maxbytes ) && loco_check_extension('tokenizer') ) { $tokens = new LocoPHPTokens( token_get_all( $srcfile->getContents() ) ); } else if( 'js' === $type ){ $tokens = new LocoJsTokens( $srcfile->getContents() ); } else { $tokens = null; } // highlighting on back end because tokenizer provides more control than highlight.js if( $tokens instanceof LocoTokensInterface ){ $thisline = 1; while( $tok = $tokens->advance() ){ if( is_array($tok) ){ // line numbers added in PHP 5.2.2 - WordPress minimum is 5.2.4 list( $t, $str, $startline ) = $tok; $clss = token_name($t); // tokens can span multiple lines (whitespace/html/comments) $lines = preg_split('/\\R/', $str ); } else { // scalar symbol will always start on the line that the previous token ended on $clss = 'T_NONE'; $lines = array( $tok ); $startline = $thisline; } // token can span multiple lines, so include only bytes on required line[s] foreach( $lines as $i => $line ){ $thisline = $startline + $i; $html = ''.htmlentities($line,ENT_COMPAT,'UTF-8').''; // append highlighted token to current line $j = $thisline - 1; if( isset($code[$j]) ){ $code[$j] .= $html; } else { $code[$j] = $html; } } } } // permit limited other file types, but without back end highlighting else if( 'js' === $type || 'twig' === $type || 'php' === $type ){ foreach( preg_split( '/\\R/u', $srcfile->getContents() ) as $line ){ $code[] = ''.htmlentities($line,ENT_COMPAT,'UTF-8').''; } } else { throw new Loco_error_Exception( sprintf('%s source view not supported', $type) ); // @codeCoverageIgnore } if( ! isset($code[$refline-1]) ){ throw new Loco_error_Exception( sprintf('Line %u not in source file', $refline) ); } $this->set( 'code', $code ); return parent::render(); } } PingController.php000066600000001207152140441670010226 0ustar00validate(); // echo back bytes posted if( $post->has('echo') ){ $this->set( 'ping', $post['echo'] ); } // else just send pong else { $this->set( 'ping', 'pong' ); } // always send tick symbol to check json serializing of unicode $this->set( 'utf8', "\xE2\x9C\x93" ); return parent::render(); } }module.php000066600000015465152141733020006557 0ustar00ajax_actions[ $tag ] = compact( 'tag', 'callback' ); } /** * Handle ajax request. * * Verify ajax nonce, and run all the registered actions for this request. * * Fired by `wp_ajax_elementor_ajax` action. * * @since 2.0.0 * @access public */ public function handle_ajax_request() { if ( ! $this->verify_request_nonce() ) { $this->add_response_data( false, __( 'Token Expired.', 'elementor' ) ) ->send_error( Exceptions::UNAUTHORIZED ); } $editor_post_id = 0; if ( ! empty( $_REQUEST['editor_post_id'] ) ) { $editor_post_id = absint( $_REQUEST['editor_post_id'] ); Plugin::$instance->db->switch_to_post( $editor_post_id ); } /** * Register ajax actions. * * Fires when an ajax request is received and verified. * * Used to register new ajax action handles. * * @since 2.0.0 * * @param self $this An instance of ajax manager. */ do_action( 'elementor/ajax/register_actions', $this ); $this->requests = json_decode( stripslashes( $_REQUEST['actions'] ), true ); foreach ( $this->requests as $id => $action_data ) { $this->current_action_id = $id; if ( ! isset( $this->ajax_actions[ $action_data['action'] ] ) ) { $this->add_response_data( false, __( 'Action not found.', 'elementor' ), Exceptions::BAD_REQUEST ); continue; } if ( $editor_post_id ) { $action_data['data']['editor_post_id'] = $editor_post_id; } try { $results = call_user_func( $this->ajax_actions[ $action_data['action'] ]['callback'], $action_data['data'], $this ); if ( false === $results ) { $this->add_response_data( false ); } else { $this->add_response_data( true, $results ); } } catch ( \Exception $e ) { $this->add_response_data( false, $e->getMessage(), $e->getCode() ); } } $this->current_action_id = null; $this->send_success(); } /** * Get current action data. * * Retrieve the data for the current ajax request. * * @since 2.0.1 * @access public * * @return bool|mixed Ajax request data if action exist, False otherwise. */ public function get_current_action_data() { if ( ! $this->current_action_id ) { return false; } return $this->requests[ $this->current_action_id ]; } /** * Create nonce. * * Creates a cryptographic token to * give the user an access to Elementor ajax actions. * * @since 2.3.0 * @access public * * @return string The nonce token. */ public function create_nonce() { return wp_create_nonce( self::NONCE_KEY ); } /** * Verify request nonce. * * Whether the request nonce verified or not. * * @since 2.3.0 * @access public * * @return bool True if request nonce verified, False otherwise. */ public function verify_request_nonce() { return ! empty( $_REQUEST['_nonce'] ) && wp_verify_nonce( $_REQUEST['_nonce'], self::NONCE_KEY ); } protected function get_init_settings() { return [ 'url' => admin_url( 'admin-ajax.php' ), 'nonce' => $this->create_nonce(), ]; } /** * Ajax success response. * * Send a JSON response data back to the ajax request, indicating success. * * @since 2.0.0 * @access protected */ private function send_success() { $response = [ 'success' => true, 'data' => [ 'responses' => $this->response_data, ], ]; $json = wp_json_encode( $response ); while ( ob_get_status() ) { ob_end_clean(); } if ( function_exists( 'gzencode' ) ) { $response = gzencode( $json ); header( 'Content-Type: application/json; charset=utf-8' ); header( 'Content-Encoding: gzip' ); header( 'Content-Length: ' . strlen( $response ) ); echo $response; } else { echo $json; } wp_die( '', '', [ 'response' => null ] ); } /** * Ajax failure response. * * Send a JSON response data back to the ajax request, indicating failure. * * @since 2.0.0 * @access protected * * @param null $code */ private function send_error( $code = null ) { wp_send_json_error( [ 'responses' => $this->response_data, ], $code ); } /** * Add response data. * * Add new response data to the array of all the ajax requests. * * @since 2.0.0 * @access protected * * @param bool $success True if the requests returned successfully, False * otherwise. * @param mixed $data Optional. Response data. Default is null. * * @param int $code Optional. Response code. Default is 200. * * @return Module An instance of ajax manager. */ private function add_response_data( $success, $data = null, $code = 200 ) { $this->response_data[ $this->current_action_id ] = [ 'success' => $success, 'code' => $code, 'data' => $data, ]; return $this; } }