SetupController.php000066600000015120152140441620010423 0ustar00getBundle(); // translators: where %s is a plugin or theme $this->set( 'title', sprintf( __('Set up %s','loco'),$bundle->getName() ) ); } /** * {@inheritdoc} */ public function getHelpTabs(){ return array ( __('Setup tab','loco') => $this->view('tab-bundle-setup'), ); } /** * {@inheritdoc} */ public function render(){ $this->prepareNavigation()->add( __('Bundle setup','loco') ); $bundle = $this->getBundle(); $action = 'setup:'.$bundle->getId(); // execute auto-configure if posted $post = Loco_mvc_PostParams::get(); if( $post->has('auto-setup') && $this->checkNonce( 'auto-'.$action) ){ if( 0 === count($bundle) ){ $bundle->createDefault(); } foreach( $bundle as $project ){ if( ! $project->getPot() && ( $file = $project->guessPot() ) ){ $project->setPot( $file ); } } // forcefully add every additional project into bundle foreach( $bundle->invert() as $project ){ if( ! $project->getPot() && ( $file = $project->guessPot() ) ){ $project->setPot( $file ); } $bundle[] = $project; } $this->saveBundle(); $bundle = $this->getBundle(); $this->set('auto', null ); } // execute XML-based config if posted else if( $post->has('xml-setup') && $this->checkNonce( 'xml-'.$action) ){ $bundle->clear(); $model = new Loco_config_XMLModel; $model->loadXml( trim( $post['xml-content'] ) ); $reader = new Loco_config_BundleReader($bundle); $reader->loadModel( $model ); $this->saveBundle(); $bundle = $this->getBundle(); $this->set('xml', null ); } // execute JSON-based config if posted else if( $post->has('json-setup') && $this->checkNonce( 'json-'.$action) ){ $bundle->clear(); $model = new Loco_config_ArrayModel; $model->loadJson( trim( $post['json-content'] ) ); $reader = new Loco_config_BundleReader($bundle); $reader->loadModel( $model ); $this->saveBundle(); $bundle = $this->getBundle(); $this->set('json', null ); } // execute reset if posted else if( $post->has('reset-setup') && $this->checkNonce( 'reset-'.$action) ){ $this->resetBundle(); $bundle = $this->getBundle(); } // bundle author links $info = $bundle->getHeaderInfo(); $this->set( 'credit', $info->getAuthorCredit() ); // render according to current configuration method (save type) $configured = $this->get('force') or $configured = $bundle->isConfigured(); $notices = new ArrayIterator; $this->set('notices', $notices ); // collect configuration warnings foreach( $bundle as $project ){ $potfile = $project->getPot(); if( ! $potfile ){ $notices[] = sprintf('No translation template for the "%s" text domain', $project->getSlug() ); } } // if extra files found consider incomplete if( $bundle->isTheme() || ( $bundle->isPlugin() && ! $bundle->isSingleFile() ) ){ $unknown = Loco_package_Inverter::export($bundle); $n = 0; foreach( $unknown as $ext => $files ){ $n += count($files); } if( $n ){ $notices[] = sprintf( _n("One file can't be matched to a known set of strings","%s files can't be matched to a known set of strings",$n,'loco'), number_format($n) ); } } // display setup options if at least one option specified $doconf = false; // enable form to invoke auto-configuration if( $this->get('auto') ){ $fields = new Loco_mvc_HiddenFields(); $fields->setNonce( 'auto-'.$action ); $this->set('autoFields', $fields ); $doconf = true; } // enable form to paste XML config if( $this->get('xml') ){ $fields = new Loco_mvc_HiddenFields(); $fields->setNonce( 'xml-'.$action ); $this->set('xmlFields', $fields ); $doconf = true; } // enable form to paste JSON config (via remote lookup) if( $this->get('json') ){ $fields = new Loco_mvc_HiddenFields( array( 'json-content' => '', 'version' => $info->Version, ) ); $fields->setNonce( 'json-'.$action ); $this->set('jsonFields', $fields ); // other information for looking up bundle via api $this->set('vendorSlug', $bundle->getSlug() ); // remote config is done via JavaScript $this->enqueueScript('setup'); $apiBase = apply_filters( 'loco_api_url', 'https://localise.biz/api' ); $this->set('js', new Loco_mvc_ViewParams( array( 'apiUrl' => $apiBase.'/wp/'.strtolower( $bundle->getType() ), ) ) ); $doconf = true; } // display configurator if configurating if( $doconf ){ return $this->view( 'admin/bundle/setup/conf' ); } // else set configurator links back to self with required option // ... if( ! $configured || ! count($bundle) ){ return $this->view( 'admin/bundle/setup/none' ); } if( 'db' === $configured ){ // form for resetting config $fields = new Loco_mvc_HiddenFields(); $fields->setNonce( 'reset-'.$action ); $this->set( 'reset', $fields ); return $this->view('admin/bundle/setup/saved'); } if( 'internal' === $configured ){ return $this->view('admin/bundle/setup/core'); } if( 'file' === $configured ){ return $this->view('admin/bundle/setup/author'); } if( count($notices) ){ return $this->view('admin/bundle/setup/partial'); } return $this->view('admin/bundle/setup/meta'); } }DebugController.php000066600000002664152140441620010362 0ustar00getBundle(); $this->set('title', 'Debug: '.$bundle ); } /** * {@inheritdoc} */ public function render(){ $this->prepareNavigation()->add( __('Bundle diagnostics','loco') ); $bundle = $this->getBundle(); $debugger = new Loco_package_Debugger($bundle); $this->set('notices', $notices = new Loco_mvc_ViewParams ); /* @var $notice Loco_error_Exception */ foreach( $debugger as $notice ){ $notices[] = new Loco_mvc_ViewParams( array( 'style' => 'notice inline notice-'.$notice->getType(), 'title' => $notice->getTitle(), 'body' => $notice->getMessage(), ) ); } $meta = $bundle->getHeaderInfo(); $this->set('meta', new Loco_mvc_ViewParams( array( 'vendor' => $meta->getVendorHost(), 'author' => $meta->getAuthorCredit(), ) ) ); if( count($bundle) ){ $writer = new Loco_config_BundleWriter( $bundle ); $this->set( 'xml', $writer->toXml() ); } return $this->view('admin/bundle/debug'); } }ViewController.php000066600000027032152140441620010242 0ustar00getBundle(); $this->set('title', $bundle->getName() ); $this->enqueueStyle('bundle'); } /** * {@inheritdoc} */ public function getHelpTabs(){ return array ( __('Overview','default') => $this->view('tab-bundle-view'), ); } /** * Generate a link for a specific file resource within a project * @return string */ private function getResourceLink( $page, Loco_package_Project $project, Loco_gettext_Metadata $meta, array $args = array() ){ $args['path'] = $meta->getPath(false); return $this->getProjectLink( $page, $project, $args ); } /** * Generate a link for a project, but without being for a specific file * @return string */ private function getProjectLink( $page, Loco_package_Project $project, array $args = array() ){ $args['bundle'] = $this->get('bundle'); $args['domain'] = $project->getId(); return $this->getLink( $page, $args ); } /** * Generate a link for the same type of bundle as this one * @return string */ private function getLink( $page, array $args ){ $route = strtolower( $this->get('type') ).'-'.$page; return Loco_mvc_AdminRouter::generate( $route, $args ); } /** * Initialize view parameters for a project * @return Loco_mvc_ViewParams */ private function createProjectParams( Loco_package_Project $project ){ $name = $project->getName(); $domain = $project->getDomain()->getName(); $slug = $project->getSlug(); $p = new Loco_mvc_ViewParams( array ( 'id' => $project->getId(), 'name' => $name, 'slug' => $slug, 'domain' => $domain, 'short' => ! $slug || $project->isDomainDefault() ? $domain : $domain.'→'.$slug, ) ); // POT template file $file = $project->getPot(); if( $file && $file->exists() ){ $meta = Loco_gettext_Metadata::load($file)->persistIfDirty( 0, true ); $p['pot'] = new Loco_mvc_ViewParams( array( // POT info 'name' => $file->basename(), 'time' => $file->modified(), // POT links 'info' => $this->getResourceLink('file-info', $project, $meta ), 'edit' => $this->getResourceLink('file-edit', $project, $meta ), ) ); } // PO/MO files $po = $project->findLocaleFiles('po'); $mo = $project->findLocaleFiles('mo'); $p['po'] = $this->createProjectPairs( $project, $po, $mo ); // also pull invalid files so everything is available to the UI $mo = $project->findNotLocaleFiles('mo'); $po = $project->findNotLocaleFiles('po')->augment( $project->findNotLocaleFiles('pot') ); $p['_po'] = $this->createProjectPairs( $project, $po, $mo ); // always offer msginit even if we find out later we can't extract any strings $p['nav'][] = new Loco_mvc_ViewParams( array( 'href' => $this->getProjectLink('msginit', $project ), 'name' => __('New language','loco'), 'icon' => 'add', ) ); // offer template editing if permitted if( ! $project->isPotLocked() ){ $pot = $project->getPot(); if( $pot && $pot->exists() ){ $p['pot'] = $pot; $meta = Loco_gettext_Metadata::load($pot)->persistIfDirty( 0, true ); $p['nav'][] = new Loco_mvc_ViewParams( array( 'href' => $this->getResourceLink('file-edit', $project, $meta ), 'name' => __('Edit template','loco'), 'icon' => 'pencil', ) ); } // else offer creation of new Template else { $p['nav'][] = new Loco_mvc_ViewParams( array( 'href' => $this->getProjectLink('xgettext', $project ), 'name' => __('Create template','loco'), 'icon' => 'add', ) ); } } return $p; } /** * Collect PO/MO pairings, ignoring any PO that is in use as a template */ private function createPairs( Loco_fs_FileList $po, Loco_fs_FileList $mo, Loco_fs_File $pot = null ){ $pairs = array(); /* @var $pofile Loco_fs_LocaleFile */ foreach( $po as $pofile ){ if( $pot && $pofile->equal($pot) ){ continue; } $pair = array( $pofile, null ); $mofile = $pofile->cloneExtension('mo'); if( $mofile->exists() ){ $pair[1] = $mofile; } $pairs[] = $pair; } /* @var $mofile Loco_fs_LocaleFile */ foreach( $mo as $mofile ){ $pofile = $mofile->cloneExtension('po'); if( $pot && $pofile->equal($pot) ){ continue; } if( ! $pofile->exists() ){ $pairs[] = array( null, $mofile ); } } return $pairs; } /** * Initialize view parameters for each row representing a localized resource pair * @return array collection of entries corresponding to available PO/MO pair. */ private function createProjectPairs( Loco_package_Project $project, Loco_fs_LocaleFileList $po, Loco_fs_LocaleFileList $mo ){ // populate official locale names for all found, or default to our own if( $locales = $po->getLocales() + $mo->getLocales() ){ $api = new Loco_api_WordPressTranslations; /* @var $locale Loco_Locale */ foreach( $locales as $tag => $locale ){ $locale->fetchName($api) or $locale->buildName() or $locale->setName($tag); } } // collate as unique [PO,MO] pairs ensuring canonical template excluded $pairs = $this->createPairs( $po, $mo, $project->getPot() ); $rows = array(); foreach( $pairs as $pair ){ // favour PO file if it exists list( $pofile, $mofile ) = $pair; $file = $pofile or $file = $mofile; // establish locale, or assume invalid $locale = null; if( 'pot' !== $file->extension() ){ $tag = $file->getSuffix(); if( isset($locales[$tag]) ){ $locale = $locales[$tag]; } } $rows[] = $this->createFileParams( $project, $file, $locale ); } return $rows; } /** * */ private function createFileParams( Loco_package_Project $project, Loco_fs_File $file, Loco_Locale $locale = null ){ // Pull Gettext meta data from cache if possible // TODO save write when cached version was used $meta = Loco_gettext_Metadata::load($file)->persistIfDirty( 0, true ); // Establish whether translations are official or otherwise $dir = new Loco_fs_LocaleDirectory( $file->dirname() ); // Retuen data required for PO table row return new Loco_mvc_ViewParams( array ( // locale info 'lcode' => $locale ? (string) $locale : '', 'lname' => $locale ? $locale->getName() : '', 'lattr' => $locale ? 'class="'.$locale->getIcon().'" lang="'.$locale->lang.'"' : '', // file info 'meta' => $meta, 'name' => $file->basename(), 'time' => $file->modified(), 'todo' => $meta->countIncomplete(), 'total' => $meta->getTotal(), // author / contrib 'store' => $dir->getTypeLabel( $dir->getTypeId() ), // links 'info' => $this->getResourceLink('file-info', $project, $meta ), 'edit' => $this->getResourceLink('file-edit', $project, $meta ), 'delete' => $this->getResourceLink('file-delete', $project, $meta ), 'copy' => $this->getProjectLink('msginit', $project, array( 'source' => $meta->getPath(false) ) ), ) ); } /** * Prepare view parameters for all projects in a bundle * @return array */ private function createBundleListing( Loco_package_Bundle $bundle ){ $projects = array(); /* @var $project Loco_package_Project */ foreach( $bundle as $project ){ $projects[] = $this->createProjectParams($project); } return $projects; } /** * {@inheritdoc} */ public function render(){ $this->prepareNavigation(); $bundle = $this->getBundle(); $this->set('name', $bundle->getName() ); // bundle may not be fully configured $configured = $bundle->isConfigured(); // Hello Dolly is an exception. don't show unless configured deliberately if( ! $configured && 'hello.php' === $bundle->getHandle() && 'Hello Dolly' === $bundle->getName() ){ $this->set( 'redirect', Loco_mvc_AdminRouter::generate('core-view') ); return $this->view('admin/bundle/alias'); } // Collect all configured projects $projects = $this->createBundleListing( $bundle ); $unknown = array(); // sniff additional unknown files if bundle is a theme or directory-based plugin that's been auto-detected if( 'file' === $configured || 'internal' === $configured ){ // presumed complete } else if( $bundle->isTheme() || ( $bundle->isPlugin() && ! $bundle->isSingleFile() ) ){ // TODO This needs absracting into the Loco_package_Inverter class $prefixes = array(); $po = new Loco_fs_LocaleFileList; $mo = new Loco_fs_LocaleFileList; foreach( Loco_package_Inverter::export($bundle) as $ext => $files ){ $list = 'mo' === $ext ? $mo : $po; foreach( $files as $file ){ $file = new Loco_fs_LocaleFile($file); $list->addLocalized( $file ); // Only look in system locations if locale is valid and domain/prefix available $locale = $file->getLocale(); if( $locale->isValid() && ( $domain = $file->getPrefix() ) ){ $prefixes[$domain] = true; } } } // pick up given files in system locations only foreach( $prefixes as $domain => $_bool ){ $dummy = new Loco_package_Project( $bundle, new Loco_package_TextDomain($domain), '' ); $bundle->addProject( $dummy ); // <- required to configure locations $dummy->excludeTargetPath( $bundle->getDirectoryPath() ); $po->augment( $dummy->findLocaleFiles('po') ); $mo->augment( $dummy->findLocaleFiles('mo') ); } // a fake project is required to disable functions that require a configured project $dummy = new Loco_package_Project( $bundle, new Loco_package_TextDomain(''), '' ); $unknown = $this->createProjectPairs( $dummy, $po, $mo ); } $this->set('projects', $projects ); $this->set('unknown', $unknown ); return $this->view( 'admin/bundle/view' ); } }ConfController.php000066600000010737152140441620010221 0ustar00enqueueStyle('config'); $this->enqueueScript('config'); $bundle = $this->getBundle(); // translators: where %s is a plugin or theme $this->set( 'title', sprintf( __('Configure %s','loco'),$bundle->getName() ) ); $post = Loco_mvc_PostParams::get(); // always set a nonce for current bundle $nonce = $this->setNonce( $this->get('_route').'-'.$this->get('bundle') ); $this->set('nonce', $nonce ); try { // Save configuration if posted if( $post->has('conf') ){ if( ! $post->name ){ $post->name = $bundle->getName(); } $this->checkNonce( $nonce->action ); $model = new Loco_config_FormModel; $model->loadForm( $post ); // configure bundle from model in full $bundle->clear(); $reader = new Loco_config_BundleReader( $bundle ); $reader->loadModel( $model ); $this->saveBundle(); } // Delete configuration if posted else if( $post->has('unconf') ){ $this->resetBundle(); } } catch( Exception $e ){ Loco_error_AdminNotices::warn( $e->getMessage() ); } } /** * {@inheritdoc} */ public function getHelpTabs(){ return array ( __('Advanced tab','loco') => $this->view('tab-bundle-conf'), ); } /** * {@inheritdoc} */ public function render() { $bundle = $this->getBundle(); $base = $bundle->getDirectoryPath(); // parent themes are inherited into bundle, we don't want them in the child theme config if( $bundle->isTheme() && ( $parent = $bundle->getParentTheme() ) ){ $this->set( 'parent', new Loco_mvc_ViewParams( array( 'name' => $parent->getName(), 'href' => Loco_mvc_AdminRouter::generate('theme-conf', array( 'bundle' => $parent->getSlug() ) + $_GET ), ) ) ); } // render postdata straight back to form if sent $data = Loco_mvc_PostParams::get(); // else build initial data from current bundle state if( ! $data->has('conf') ){ if( 0 === count($bundle) ){ $bundle->createDefault(''); } $writer = new Loco_config_BundleWriter($bundle); $data = $writer->toForm(); // removed parent bundle configs, as they are inherited /* @var Loco_package_Project $project */ foreach( $bundle as $i => $project ){ if( isset($parent) && $parent->hasProject($project) ){ $data['conf'][$i]['removed'] = true; } } } // build config blocks for form $i = 0; $conf = array(); foreach( $data['conf'] as $raw ){ if( empty($raw['removed']) ){ $slug = $raw['slug']; $domain = $raw['domain'] or $domain = 'untitled'; $raw['prefix'] = sprintf('conf[%u]', $i++ ); $raw['short'] = ! $slug || ( $slug === $domain ) ? $domain : $domain.'→'.$slug; $conf[] = new Loco_mvc_ViewParams( $raw ); } } // bundle level configs $name = $bundle->getName(); $excl = $data['exclude']; // access to type of configuration that's currently saved $this->set('saved', $bundle->isConfigured() ); // link to author if there are config problems $info = $bundle->getHeaderInfo(); $this->set('author', $info->getAuthorLink() ); // link for downloading current configuration XML file $args = array ( 'path' => 'loco.xml', 'action' => 'loco_download', 'bundle' => $bundle->getHandle(), 'type' => $bundle->getType() ); $this->set( 'xmlUrl', Loco_mvc_AjaxRouter::generate( 'DownloadConf', $args ) ); $this->prepareNavigation()->add( __('Advanced configuration','loco') ); return $this->view('admin/bundle/conf', compact('conf','base','name','excl') ); } } BaseController.php000066600000010054152140441620010176 0ustar00bundle ){ $type = $this->get('type'); $handle = $this->get('bundle'); $this->bundle = Loco_package_Bundle::createType( $type, $handle ); } return $this->bundle; } /** * Commit bundle config to database * @return Loco_admin_bundle_BaseController */ protected function saveBundle(){ $custom = new Loco_config_CustomSaved; if( $custom->setBundle($this->bundle)->persist() ){ Loco_error_AdminNotices::success( __('Configuration saved','loco') ); } // invalidate bundle in memory so next fetch is re-configured from DB $this->bundle = null; return $this; } /** * Remove bundle config from database * @return Loco_admin_bundle_BaseController */ protected function resetBundle(){ $option = $this->bundle->getCustomConfig(); if( $option && $option->remove() ){ Loco_error_AdminNotices::success( __('Configuration reset','loco') ); // invalidate bundle in memory so next fetch falls back to auto-config $this->bundle = null; } return $this; } /** * @return Loco_package_Project */ public function getProject(){ if( ! $this->project ){ $bundle = $this->getBundle(); $domain = $this->get('domain'); if( ! $domain ){ throw new Loco_error_Exception( sprintf('Translation set not known in %s', $bundle ) ); } $this->project = $bundle->getProjectById($domain); if( ! $this->project ){ throw new Loco_error_Exception( sprintf('Unknown translation set: %s not in %s', json_encode($domain), $bundle ) ); } } return $this->project; } /** * @return Loco_admin_Navigation */ protected function prepareNavigation(){ $bundle = $this->getBundle(); // navigate up to bundle listing page $breadcrumb = Loco_admin_Navigation::createBreadcrumb( $bundle ); $this->set( 'breadcrumb', $breadcrumb ); // navigate between bundle view siblings $tabs = new Loco_admin_Navigation; $this->set( 'tabs', $tabs ); $actions = array ( 'view' => __('Overview','loco'), 'setup' => __('Setup','loco'), 'conf' => __('Advanced','loco'), ); if( loco_debugging() ){ $actions['debug'] = __('Debug','loco'); } $suffix = $this->get('action'); $prefix = strtolower( $this->get('type') ); $getarg = array_intersect_key( $_GET, array('bundle'=>'') ); foreach( $actions as $action => $name ){ $href = Loco_mvc_AdminRouter::generate( $prefix.'-'.$action, $getarg ); $tabs->add( $name, $href, $action === $suffix ); } return $breadcrumb; } /** * Prepare file system connect * @return Loco_mvc_HiddenFields */ protected function prepareFsConnect( $type, $path ){ $fields = new Loco_mvc_HiddenFields( array( 'auth' => $type, 'path' => $path, 'loco-nonce' => wp_create_nonce('fsConnect'), ) ); $this->set('fsFields', $fields ); // may have fs credentials saved in session try { $session = Loco_data_Session::get(); if( isset($session['loco-fs']) ){ $fields['connection_type'] = $session['loco-fs']['connection_type']; } } catch( Exception $e ){ Loco_error_AdminNotices::debug( $e->getMessage() ); } return $fields; } }inc-po-table.php000066600000011064152140441660007540 0ustar00 lcode;?>
lattr?>>e('lcode')?> e('lname')?> () meta->printProgress()?> meta->getPercent()?>% n('todo')?> -- -- e('name')?> e('store')?>
extend('../layout'); ?>

.

conf.php000066600000023012152140441660006207 0ustar00extend('../layout'); ?>
$p ): $id = sprintf('loco-conf-%u',$i)?>
remove name === $p->short ):?>

e('name')?>

e('name')?> (e('short')?>)

:
:

. ()

:
:

. ()

. ()

parent ):?>

extend('../setup'); $this->start('header'); ?>

. .

render('inc-nav')?>
setup/inc-nav.php000066600000002215152140441660007757 0ustar00has('notices') ):?>

setup/conf.php000066600000006250152140441660007354 0ustar00extend('../setup'); $this->start('header'); if( $params->has('jsonFields') ):?>

_e()?>
has('xmlFields') ):?>

:

_e()?>
has('autoFields') ):?>

Auto setup

We can make some guesses about how this bundle is set up, but we can't guarantee they'll be right.

This is not recommended unless you're a developer able to make manual changes afterwards.

_e()?>
extend('../setup'); $this->start('header'); ?>

. .

render('inc-nav')?>
setup/partial.php000066600000001023152140441660010054 0ustar00extend('../setup'); $this->start('header'); ?>

.

render('inc-nav')?>
setup/saved.php000066600000001564152140441660007534 0ustar00extend('../setup'); $this->start('header'); ?>

.

_e()?>
setup/meta.php000066600000001162152140441660007352 0ustar00extend('../setup'); $this->start('header'); ?>

. .

render('inc-nav')?>
setup/core.php000066600000001260152140441660007353 0ustar00extend('../../layout'); ?>

. .

setup.php000066600000000755152140441660006433 0ustar00extend('../layout'); echo $header; ?>

.

view.php000066600000004240152140441660006236 0ustar00extend('../layout'); /* @var $p Loco_mvc_ViewParams */ if( $projects ): foreach( $projects as $p ): ?>
name === $p->short ):?>

e('name')?>

e('name')?> (e('short')?>)

render('inc-po-links', array( 'nav' => $p->nav ) ); echo $this->render('inc-po-table', array( 'pairs' => $p->po, 'domain' => $p->domain ) ); ?>

. setup tab to complete the bundle configuration','loco'), $tabs[1]->href )?>.

render('../common/inc-table-filter'); echo $this->render('inc-po-table', array( 'pairs' => $unknown, 'domain' => null ) )?>

e('name')?> ()

. setup tab to complete the bundle configuration','loco'), $tabs[1]->href )?>.

render('inc-po-table', array( 'pairs' => $unknown, 'domain' => null ) )?>
extend('../layout'); ?>

.

author?>

e('body')?>

has('xml') ):?>

Current configuration as XML:

e('xml')?>
inc-po-links.php000066600000000552152140441660007571 0ustar00 locale.php000066600000012041152141502760006521 0ustar00extend('../layout'); ?>

attr?>>e('code')?> e('name')?> : date('modified')?>

$group ): $type = $types[$t];?>

e('name')?>

render('../common/inc-table-filter'); ?> type ):?>
e('title')?> meta->printProgress()?> meta->getPercent()?>% n('todo')?> -- -- e('name')?> e('store')?>

e('text')?>