# PLUGIN PREVIEW BY TEXTPATTERN.INFO
// <?php
/**
* ied_plugin_composer
*
* A Textpattern CMS plugin for writing, editing and sharing plugins
* -> Create and edit admin-side or public plugins
* -> Supports plugin lifecycle events and prefs
* -> Supports Textpacks
* -> Optional syntax checker on save
*
* @author Yura Linnyk
* @author Stef Dawson
* @author Steve Dickinson
* @link http://stefdawson.com/
*/
// TODO:
// * Use href() (from 4.6.+) for anchor creation to avoid double-encoded ampersands
// * Figure out why syntax checker doesn't jump to line number sometimes (AJAX fails with error but it's not handled)
// * Show which langs have installed strings in the distribution section so the correct langs in the select list can be chosen
// * Find out why uploading PHP files sometiems throws an error even though it succeeds
// * jQuery on editor dropdowns in setup
// * phpdoc
if (txpinterface === 'admin') {
new ied_pc();
} elseif (txpinterface === 'public') {
if (class_exists('\Textpattern\Tag\Registry')) {
Txp::get('\Textpattern\Tag\Registry')
->register('ied_plugin_list')
->register('ied_plugin_info')
->register('ied_plugin_textpacks')
->register('ied_plugin_download')
->register('ied_plugin_download_link');
}
register_callback('ied_plugin_download', 'pretext');
/*
* Public tag: List plugins, filtered by name or prefix.
*
* @param array $atts Tag attributes
* @param string $thing Tag container content
*/
function ied_plugin_list($atts = array(), $thing = null)
{
global $ied_plugin_data;
extract(lAtts(array(
'from' => 'database', // database, cache (or both)
'name' => '', // List of plugin names to return
'prefix' => '', // Plugin prefixes
'exclude' => '', // names to exclude from the list
'type' => '', // 0-5 or comma-separated combos thereof
'wraptag' => '',
'class' => '',
'break' => '',
'breakclass' => '',
'html_id' => '',
'form' => '',
),$atts));
$thing = (empty($form)) ? ((empty($thing)) ? '<txp:ied_plugin_info item="name" />' : $thing) : fetch_form($form);
$location = do_list($from);
$names = do_list($name);
$prefixes = do_list($prefix);
$excludes = do_list($exclude);
if (in_array('database', $location)) {
$sql = array();
$sql[] = '1';
if ($name) {
$sql[] = "name IN ('".implode("','", doSlash($names))."')";
}
if ($prefix) {
$sqlor = array();
foreach ($prefixes as $pfx) {
$sqlor[] = "name LIKE '".doSlash($pfx)."%'";
}
$sql[] = '(' . implode(' OR ', $sqlor) . ')';
}
if ($exclude) {
$sql[] = "name NOT IN ('".implode("','", doSlash($excludes))."')";
}
$rs = safe_rows('*', 'txp_plugin', implode(' AND ', $sql) . ' ORDER BY name');
}
// TODO: Add the meta data from matching plugins in the cache directory
if (in_array('cache', $location)) {
}
$out = array();
$ied_pd_saved = $ied_plugin_data;
foreach ($rs as $row) {
$ied_plugin_data = $row;
$ied_plugin_data['help_unstyled'] = preg_replace('/\<\!\-\- \*\*\* BEGIN PLUGIN CSS.+END PLUGIN CSS \*\*\* \-\-\>/s', '', $row['help']);
$out[] = parse($thing);
$ied_plugin_data = array();
}
$ied_plugin_data = $ied_pd_saved;
return ($wraptag) ? doWrap($out, $wraptag, $break, $class, $breakclass, '', '', $html_id) : implode($break, $out);
}
/**
* Public tag: Display plugin data for form/container usage.
*
* @param array $atts Tag attributes
* @param string $thing Tag container content
*/
function ied_plugin_info($atts, $thing = null)
{
global $ied_plugin_data;
extract(lAtts(array(
'item' => '',
'wraptag' => '',
'break' => '',
'class' => '',
'debug' => 0,
), $atts));
$pdata = is_array($ied_plugin_data) ? $ied_plugin_data : array();
if ($debug) {
echo '++ AVAILABLE INFO ++';
dmp($pdata);
}
$items = do_list($item);
$out = array();
foreach ($items as $it) {
if (isset($pdata[$it])) {
$out[] = $pdata[$it];
}
}
return doWrap($out, $wraptag, $break, $class);
}
/**
* Public tag: List of available textpack information.
*
* @param array $atts Tag attributes
* @param string $thing Tag container content
*/
function ied_plugin_textpacks($atts, $thing = null)
{
global $ied_plugin_data;
extract(lAtts(array(
'name' => '',
'filename' => '',
'lang' => 'IED_ALL',
'wraptag' => '',
'break' => '',
'class' => '',
'form' => '',
), $atts));
if (!$name && !$filename) {
return;
}
if ($name) {
$theName = $name;
} elseif ($filename) {
$theName = $filename;
}
$thing = (empty($form)) ? ((empty($thing)) ? '<txp:ied_plugin_info item="lang" />' : $thing) : fetch_form($form);
$langs = array();
$tp_prefixes = unserialize(get_pref('ied_plugin_tp_prefix', ''));
if (isset($tp_prefixes[$theName])) {
$strings = $this->textpack_grab($lang, $tp_prefixes[$theName]);
foreach ($strings as $row) {
if (array_search($row['lang'], $langs) === false) {
$langs[] = $row['lang'];
}
}
}
$out = array();
$ied_pd_saved = $ied_plugin_data;
$idx = 0;
$num_langs = count($langs);
foreach ($langs as $row) {
$ied_plugin_data['lang'] = $row;
$ied_plugin_data['first_lang'] = (($idx === 0) ? 1 : 0);
$ied_plugin_data['last_lang'] = (($idx === $num_langs - 1) ? 1 : 0);
$out[] = parse($thing);
$ied_plugin_data['lang'] = $ied_plugin_data['first_lang'] = $ied_plugin_data['last_lang'] = '';
$idx++;
}
$ied_plugin_data = $ied_pd_saved;
return doWrap($out, $wraptag, $break, $class);
}
/**
* Public tag: Download a plugin.
*
* @param array $atts Tag attributes
* @param string $thing Tag container content
*/
function ied_plugin_download_link($atts, $thing = null)
{
extract(lAtts(array(
'type' => 'compressed', // uncompressed, compressed, template, textpack
'name' => '',
'filename' => '',
'label' => 'Download',
'class' => '',
'lang' => 'IED_ALL',
'form' => '',
'escape' => 'html',
), $atts));
if (!$name && !$filename) {
return;
}
$amp = ($escape === 'html') ? '&' : '&';
if ($name) {
$theName = $amp.'name='.urlencode($name);
} elseif ($filename) {
$theName = $amp.'filename='.urlencode($filename);
}
$theClass = '';
if ($class) {
$theClass = ' class="'.$class.'"';
}
$langopt = '';
if ($lang) {
$langs = do_list($lang);
$langopt = $amp.'lang='.implode(',', $langs);
}
$linkName = (empty($form)) ? ((empty($thing)) ? $label : parse($thing)) : parse_form($form);
if ($type === 'compressed') {
return href($linkName, '?ied_plugin_download=1'.$theName.$amp.'type=zip'.$langopt, $theClass);
} elseif ($type === 'uncompressed') {
return href($linkName, '?ied_plugin_download=1'.$theName.$amp.'type=txt'.$langopt, $theClass);
} elseif ($type === 'template') {
return href($linkName, '?ied_plugin_download=1'.$theName.$amp.'type=php'.$langopt, $theClass);
} elseif ($type === 'textpack') {
return href($linkName, '?ied_plugin_download=1'.$theName.$amp.'type=textpack'.$langopt, $theClass);
}
}
/**
* Handles downloading plugin content.
*/
function ied_plugin_download()
{
if (gps('ied_plugin_download')) {
$type = gps('type');
switch ($type) {
case 'zip':
case 'txt':
$ied_pc = new ied_pc();
$ied_pc->save_as_file();
break;
case 'php':
$ied_pc = new ied_pc();
$ied_pc->save_as_php_file();
break;
case 'textpack':
$ied_pc = new ied_pc();
$ied_pc->save_as_textpack();
break;
}
}
}
}
/**
* Plugin composer admin interface.
*/
class ied_pc
{
/**
* Shared information across methods.
*
* @var array
*/
protected $ied_plugin_globals = array();
/**
* The plugin's event as registered in Txp.
*
* @var string
*/
protected $ied_pc_event = 'ied_plugin_composer';
/**
* Constructor to set up callbacks and environment.
*/
public function __construct()
{
$this->ied_plugin_globals = array(
'css_start' => '<!--',
'css_end' => '-->',
'dlm_start' => '#',
'dlm_end' => '',
'start' => ' --- BEGIN PLUGIN SECTION ---',
'end' => ' --- END PLUGIN SECTION ---',
'size_help' => '63535',
'size_css' => '2000',
'size_code' => '16777215',
);
add_privs($this->ied_pc_event, '1,2');
add_privs('plugin_prefs.' . $this->ied_pc_event, '1,2');
register_tab('extensions', $this->ied_pc_event, gTxt('ied_plugin_composer'));
register_callback(array($this, 'plugin_composer'), $this->ied_pc_event);
register_callback(array($this, 'setup'), 'plugin_prefs.' . $this->ied_pc_event);
register_callback(array($this, 'welcome'), 'plugin_lifecycle.' . $this->ied_pc_event);
register_callback(array($this, 'inject_css'), 'admin_side', 'head_end');
}
/**
* CSS definitions: hopefully kind to themers.
*
* @return string Style rules
*/
protected function get_style_rules()
{
$ied_pc_styles = array(
'ied_plugin' => '
.ied_label { margin:0 0.2em 0 0.6em;}
.ied_plugin_link { float:right; }
.ied_plugin_info_bar { text-align:right; }
#ied_plugin_jumpToLine { width:4em; margin:0 1em 0 0.4em; }
.ied_editForm { width:{edwidth}; margin:0 auto; }
.ied_subdue { color:gray; padding:1px 2px 2px 1px; }
#ied_plugin_tp_controls input[type="text"] { width:16%; }
#options_group_pack ul { list-style-type:none; }
#options_group_pack ul label { margin:0 8px 0 0; }
#options_group_pack ul input { width:450px; }
#options_group_pack li { margin-bottom:.2em }
.distribution-code { height:4.2em!important; }
.ied_plugin_edit_toolbar { text-align:right; width:95%; display:inline-block; margin:-2em 0 0 0; }
#ied_plugin_msgpop { display:none; position:absolute; left:200px; max-width:500px; border:3px ridge #999; opacity:.92; filter:alpha(opacity:92); padding:15px 20px; background-color:#e2dfce; color:#80551e; }
#ied_plugin_msgpop .publish { float:right; }
',
);
return $ied_pc_styles;
}
/**
* Inject style rules into the <head> of the page.
*
* @param string $evt Textpattern event
* @param string $stp Textpattern step (action)
* @return string Style rules, or nothing if not the correct $event
*/
public function inject_css($evt, $stp)
{
global $event;
if ($event === $this->ied_pc_event) {
$ied_plugin_prefs = $this->get_prefs();
$ied_plugin_styles = $this->get_style_rules();
// Possible variable replacements.
$edwidth = get_pref('ied_plugin_editor_width', $ied_plugin_prefs['ied_plugin_editor_width']['default']);
$stylefind = array(
'{edwidth}',
);
$stylereps = array(
$edwidth,
);
echo '<style type="text/css">' . str_replace($stylefind, $stylereps, $ied_plugin_styles['ied_plugin']) . '</style>';
}
return;
}
/**
* Plugin jumpoff point.
*
* @param string $evt Textpattern event
* @param string $stp Textpattern step (action)
*/
public function plugin_composer($evt, $stp)
{
$available_steps = array(
'code_save' => true,
'create' => true,
'edit' => false,
'generate_phpdoc' => true,
'help' => true,
'help_viewer' => false,
'install' => true,
'lang_set' => true,
'table' => false,
'meta_save' => true,
'multi_edit' => true,
'prefs' => false,
'restore' => true,
'save' => true,
'save_as_file' => true,
'save_as_php_file' => true,
'save_as_textpack' => true,
'set_tp_prefix' => true,
'switch_status' => true,
'textpack_del' => true,
'textpack_get' => true,
'textpack_load' => true,
'textpack_save' => true,
'upload' => true,
'save_pane_state' => true,
);
if (!$stp or !bouncer($stp, $available_steps)) {
$stp = 'table';
}
$this->$stp();
}
/**
* Lifecycle handling, post-install / delete.
*
* @param string $evt Textpattern event
* @param string $stp Textpattern step (action)
* @return string Success/failure message
*/
public function welcome($evt, $stp)
{
$msg = '';
switch ($stp) {
case 'installed':
$this->prefs_update();
$msg = 'Thanks for installing the plugin composer. Happy authoring :-)';
break;
case 'deleted':
$this->prefs_remove(0);
break;
}
return $msg;
}
/**
* Table of plugins in both database and file system cache.
*
* @param string $message Flash message to display success/error
* @return string HTML
*/
public function table($message = '')
{
pagetop(gTxt('ied_plugin_composer'), $message);
require_privs('ied_plugin_composer');
$ied_plugin_prefs = $this->get_prefs();
$lc_opts = do_list(get_pref('ied_plugin_lifecycle_options'));
$checked = in_array('installed', $lc_opts);
$auto_en = get_pref('ied_plugin_auto_enable');
$pcd = get_pref('plugin_cache_dir');
$aeRadio[] = '<p class="ied_plugin_radioset ied_plugin_autoenable txp-layout-2col">';
$aeRadio[] = '<label class="ied_label">' . gTxt('ied_plugin_auto_enable') . '</label>';
$aeRadio[] = radioset($ied_plugin_prefs['ied_plugin_auto_enable']['content'], 'ied_plugin_autoenable', $auto_en);
$aeRadio[] = '</p>';
$ioRadio[] = '<p class="ied_plugin_radioset ied_plugin_installopts txp-layout-2col" hidden>';
$ioRadio[] = '<label class="ied_label">' . gTxt('ied_plugin_run_install') . '</label>';
$ioRadio[] = yesnoradio('ied_plugin_installopts', $checked);
$ioRadio[] = '</p>';
extract(gpsa(array('sort', 'dir')));
if ($sort === '') $sort = get_pref('ied_plugin_sort_column', 'name');
if ($dir === '') $dir = get_pref('ied_plugin_sort_dir', 'asc');
$dir = ($dir == 'desc') ? 'desc' : 'asc';
if (!in_array($sort, array('name', 'status', 'author', 'version', 'load_order'))) $sort = 'name';
$sort_sql = $sort.' '.$dir;
set_pref('ied_plugin_sort_column', $sort, 'ied_plugin', PREF_HIDDEN, '', 0, PREF_PRIVATE);
set_pref('ied_plugin_sort_dir', $dir, 'ied_plugin', PREF_HIDDEN, '', 0, PREF_PRIVATE);
$switch_dir = ($dir == 'desc') ? 'asc' : 'desc';
// Top control-panel part of screen
echo '<div class="txp-layout">'.
n. '<div class="txp-layout-2col">'.
n. '<h1 class="txp-heading">'.gTxt('ied_plugin_composer').sp.$this->anchor($this->ied_pc_event, 'help_viewer', '?', array('name' => 'ied_plugin_composer'), array('class' => 'pophelp')).'</h1>'.
n. '</div>'.
n. '<div id="ied_plugin_control" class="txp-layout-2col">'.
n. sLink($this->ied_pc_event, 'prefs', gTxt('ied_plugin_setup'), 'ied_plugin_link').
n. '</div>'.
n. '<div class="txp-layout-1col">'.
n. '<div class="summary-details clear"><h3 class="lever txp-summary'.(get_pref('pane_ied_plugin_cpanel_visible') ? ' expanded' : '').'"><a href="#ied_plugin_cpanel">' . gTxt('ied_plugin_cpanel_legend') . '</a></h3><div id="ied_plugin_cpanel" class="toggle" style="display:'.(get_pref('pane_ied_plugin_cpanel_visible') ? 'block' : 'none').'">'.
n. '<form class="ied_plugin_form" enctype="multipart/form-data" action="index.php" method="post">'.
n. implode(n, $aeRadio).
n. implode(n, $ioRadio).
n. '<p class="clear">'.
n. '<label for="ied_plugin_newname" class="ied_label">'.gTxt('name').'</label>'.
n. fInput('text', 'name', '', '', '', '', INPUT_REGULAR, '', 'ied_plugin_newname', false, false, 'Create a new plugin').
n. fInput('submit', 'plugin_create', gTxt('create')).
n. '</p>'.
n. '<p>'.
n. '<label for="ied_plugin_file" class="ied_label">'.gTxt('ied_plugin_upload_php').'</label>'.
n. fInput('file', 'thefile', '', '', '', '', '', '', 'ied_plugin_file').
n. fInput('submit', 'plugin_upload', gTxt('upload')).
n. '</p>'.
n. '<p>'.
n. '<label for="ied_plugin64" class="ied_label">'.gTxt('ied_plugin_install_txt').'</label>'.
n. text_area('plugin64', '', '', '', 'ied_plugin64').
n. fInput('submit', 'plugin_install', gTxt('install')).
n. '</p>'.
n. eInput($this->ied_pc_event).
n. sInput('create').
n. hInput('MAX_FILE_SIZE', 1000000).
n. tInput().
n. '</form>'.
n. '</div>'.
n. '</div>'.
n. '</div>';
// Main plugin list
$rs = safe_rows('*', 'txp_plugin', '1=1 ORDER BY '.$sort_sql);
if ($rs) {
echo '<div class="txp-layout-1col">'.
n. '<div class="summary-details">'.
n. '<form action="index.php" id="ied_plugin_db_form" method="post">'.
n. '<h3 class="lever txp-summary'.(get_pref('pane_ied_plugin_dbplugs_visible') ? ' expanded' : '').'">'.
n. '<a href="#ied_plugin_dbplugs">' . gTxt('ied_plugin_dbplugs_legend') . '</a>'.
n. '</h3>'.
n. '<div id="ied_plugin_dbplugs" class="toggle" style="display:'.(get_pref('pane_ied_plugin_dbplugs_visible') ? 'block' : 'none').'">'.
n. '<div class="txp-listtables">'.
n. startTable('', '', 'txp-list').
n.'<thead>'.
n. tr(
n.hCell(fInput('checkbox', 'select_all', 0, '', '', '', '', '', 'select_all'), '', ' title="'.gTxt('toggle_all_selected').'" class="multi-edit"')
.n.column_head('plugin', 'name', 'ied_plugin_composer', true, $switch_dir, '', '', (('name' == $sort) ? "$dir " : '').'name')
.n.column_head('author', 'author', 'ied_plugin_composer', true, $switch_dir, '', '', (('author' == $sort) ? "$dir " : '').'author')
.n.column_head(gTxt('version').' ('.gTxt('plugin_modified').')', 'version', 'ied_plugin_composer', true, $switch_dir, '', '', (('version' == $sort) ? "$dir " : '').'version')
.n.hCell(gTxt('description'), '', ' class="description"')
.n.hCell(gTxt('manage'), '', ' class="manage"')
.n.column_head('active', 'status', 'ied_plugin_composer', true, $switch_dir, '', '', (('status' == $sort) ? "$dir " : '').'status')
).
n. '</thead>'.
n. '<tbody>';
foreach ($rs as $row) {
extract($row);
$ename = $this->anchor($this->ied_pc_event, 'edit', $name, array('name' => $name));
$hlink = ($help) ? $this->anchor($this->ied_pc_event, 'help_viewer', gTxt('ied_plugin_docs'), array('name' => $name)) : gTxt('none');
$fnames = $this->get_name($name, $version);
$pubtag = $this->anchor($this->ied_pc_event, 'save_as_file', gTxt('publish'), array('name' => $name), array('title' => gTxt('ied_plugin_export', array('{name}' => $fnames[0]))));
$pubztag = $this->anchor($this->ied_pc_event, 'save_as_file', gTxt('ied_plugin_compress'), array('name' => $name, 'type' => 'zip'), array('title' => gTxt('ied_plugin_export', array('{name}' => $fnames[1]))));
$modified = (strtolower($code) != (strtolower($code_restore)));
$plugpref = ($flags & PLUGIN_HAS_PREFS) ? (sp.$this->anchor('plugin_prefs.'.urlencode($name), '', '['.gTxt('plugin_prefs').']', array('name' => $name), array('class' => 'plugin_prefs'.( ($status) ? '' : ' empty'))) ) : '';
echo tr(
n.td(
fInput('checkbox', 'selected[]', $name)
,'', 'multi-edit')
.n.td($ename.$plugpref)
.n.td(( ($author_uri) ? '<a href="'.txpspecialchars($author_uri).'">'.txpspecialchars($author).'</a>' : txpspecialchars($author)))
.n.td(( ($modified) ? $this->anchor($this->ied_pc_event, 'restore', $version, array('name' => $name), array('title' => gTxt('ied_plugin_restore_help'), 'onclick' => 'return verify(\''.gTxt('ied_plugin_restore_verify', array('{name}' => $name)).'\');')) : $version) . (($modified) ? sp.'('.gTxt('yes').')' : ''))
.n.td(txpspecialchars($description))
.n.td($pubtag .sp. '|' .sp. $pubztag .sp. '|' .sp. $hlink)
.n.td($this->status_link($status,$name,yes_no($status)))
);
unset($name,$page);
}
echo n. '</tbody>'.
n. endTable().
n. '</div>'.
n. '</div>'.
n. tInput().
$this->multiedit_form('db', '', $sort, $dir, '', '').
n. '</form>'.
n. '</div>'.
n. '</div>';
}
if (!empty($pcd) && file_exists($pcd)) {
$filenames = array();
$directory = dir($pcd);
while ($file = $directory->read()) {
if ($file != "." && $file != "..") {
$fileaddr = $pcd . DS . $file;
if (!is_dir($fileaddr)) {
$filenames[]=$file;
}
}
}
$directory->close();
($filenames)?natcasesort($filenames):'';
$out = array();
foreach ($filenames as $filename) {
$parts = explode ('.',$filename);
$fileext = array_pop($parts);
if ($fileext=='php') {
$basename = basename($filename);
$plugin = $this->read_file($pcd . DS . $filename);
$hlink = ($plugin['help']) ? $this->anchor($this->ied_pc_event, 'help_viewer', gTxt('ied_plugin_docs'), array('filename' => $filename)) : gTxt('none');
$efile = $this->anchor($this->ied_pc_event, 'edit', $plugin['name'], array('filename' => $filename));
$fnames = $this->get_name($plugin['name'], $plugin['version']);
$plugpref = (($plugin['flags'] & PLUGIN_HAS_PREFS)) ? ' '.$this->anchor('plugin_prefs.'.urlencode($plugin['name']), '', ' ['.gTxt('plugin_prefs').']') : '';
$pubtag = $this->anchor($this->ied_pc_event, 'save_as_file', gTxt('publish'), array('filename' => $filename), array('title' => gTxt('ied_plugin_export', array('{name}' => $fnames[0]))));
$pubztag = $this->anchor($this->ied_pc_event, 'save_as_file', gTxt('ied_plugin_compress'), array('filename' => $filename, 'type' => 'zip'), array('title' => gTxt('ied_plugin_export', array('{name}' => $fnames[1]))));
$out[] = tr(
n.td(
fInput('checkbox', 'selected-cache[]', $filename)
,'', 'multi-edit')
.n.td(
tag($filename,'div',' class="ied_subdue"')
.(isset($plugin['name']) ? $efile.$plugpref.'<br />' : '').' '
)
.n.td(
( isset($plugin['author_uri']) ? '<a href="'.$plugin['author_uri'].'">' : '' ) .
( isset($plugin['author']) ? $plugin['author'] : ' ' ).
( isset($plugin['author_uri']) ? '</a>' : '' )
)
.n.td(
(isset($plugin['version']) ? $plugin['version'] : ' ')
)
.n.td(
(isset($plugin['description']) ? $plugin['description'] : ' ')
)
.n.td(
(isset($plugin['name']) ? $pubtag .sp. '|' .sp. $pubztag
: tag(' ', 'span')
)
.sp. '|' .sp. $hlink
)
);
}
}
if ($out) {
echo '<div class="txp-layout-1col">'.
n. '<div class="summary-details">'.
n. '<form action="index.php" id="ied_plugin_cache_form" method="post">'.
n. '<h3 class="lever txp-summary'.(get_pref('pane_ied_plugin_cacheplugs_visible') ? ' expanded' : '').'">'.
n. '<a href="#ied_plugin_cacheplugs">' . gTxt('ied_plugin_cacheplugs_legend') . '</a>'.
n. '</h3>'.
n. '<div id="ied_plugin_cacheplugs" class="toggle" style="display:'.(get_pref('pane_ied_plugin_cacheplugs_visible') ? 'block' : 'none').'">'.
n. '<div class="txp-listtables ied_plugin_cacheplugs">'.
n.startTable('', '', 'txp-list').
n. '<thead>'.
n. tr(
n.hCell(fInput('checkbox', 'select_all', 0, '', '', '', '', '', 'select_all'), '', ' title="'.gTxt('toggle_all_selected').'" class="multi-edit"')
.n.hCell(gTxt('plugin'), '', ' class="name"')
.n.hCell(gTxt('author'), '', ' class="author"')
.n.hCell(gTxt('version') . ' ('.gTxt('plugin_modified').')', '', ' class="version"')
.n.hCell(gTxt('description'), '', ' class="description"')
.n.hCell(gTxt('manage'), '', ' class="manage"')
).
n. '</thead>'.
n. '<tbody>'.
n. implode(n, $out).
n. '</tbody>'.
n. endTable().
n. '</div>'.
n. tInput().
$this->multiedit_form('cache', '', $sort, $dir, '', '').
n. '</form>'.
n. '</div>'.
n. '</div>'.
n. '</div>';
}
}
echo '</div>'.
n. script_js( <<<EOJS
$(document).ready(function () {
$('#ied_plugin_db_form').txpMultiEditForm({
'checkbox' : 'input[name="selected[]"][type=checkbox]'
});
$('#ied_plugin_cache_form').txpMultiEditForm({
'checkbox' : 'input[name="selected-cache[]"][type=checkbox]'
});
var ied_io = $('.ied_plugin_installopts');
$('#ied_plugin_file').change(function() {
var fn = $(this).val();
if (fn.substr(fn.lastIndexOf('.') + 1) === 'txt') {
ied_io.show();
} else {
ied_io.hide();
}
});
$('#ied_plugin64').on('blur, keyup', function() {
if ($(this).val()) {
ied_io.show();
} else {
ied_io.hide();
}
});
});
EOJS
);
// Show/hide "Options" link by setting the appropriate class on the plugin's <tr>
echo script_js(<<<EOJS
textpattern.Relay.register('txpAsyncHref.success', function (event, data) {
jQuery(data.this).closest('tr').find('a.plugin_prefs').toggleClass('empty');
});
EOJS
);
}
/**
* Render a multi-edit dropdown list of actions.
*
* @param string $flavour Whether the dropdown is for file-based plugins or those in the DB
* @param string $page Current page of results (unused)
* @param string $sort Current sort column name
* @param string $dir Current sort direction (asc or desc)
* @param string $crit Current filter criteria (unused)
* @param string $search_method Column name upon which the search is being performed (unused)
* @return HTML
*/
protected function multiedit_form($flavour, $page, $sort, $dir, $crit, $search_method)
{
$orders = selectInput('order', array(1=>1, 2=>2, 3=>3, 4=>4, 5=>5, 6=>6, 7=>7, 8=>8, 9=>9), 5, false);
$stati = selectInput('switch_status', array('toggle' => gTxt('ied_plugin_toggle'), 'on' => gTxt('on'), 'off' => gTxt('off')), 'toggle', false);
$lifecycles = selectInput('ied_lc_event', array(
'installed' => gTxt('ied_plugin_lbl_lc_install'),
'enabled' => gTxt('ied_plugin_lbl_lc_enable'),
'installed,enabled' => gTxt('ied_plugin_lbl_lc_instable'),
'disabled' => gTxt('ied_plugin_lbl_lc_disable'),
'deleted' => gTxt('ied_plugin_lbl_lc_delete'),
'disabled,deleted' => gTxt('ied_plugin_lbl_lc_disdel'),
));
if ($flavour === 'db') {
$methods = array(
'changestatus' => array('label' => gTxt('changestatus'), 'html' => $stati),
'changeorder' => array('label' => gTxt('changeorder'), 'html' => $orders),
'lifecycle' => array('label' => gTxt('ied_plugin_lifecycle'), 'html' => $lifecycles),
'delete' => gTxt('delete'),
);
} else {
$methods = array(
'lifecycle' => array('label' => gTxt('ied_plugin_lifecycle'), 'html' => $lifecycles),
'textpack' => array('label' => gTxt('ied_plugin_install_textpack')),
'delete' => gTxt('delete'),
);
}
return multi_edit($methods, $this->ied_pc_event, 'multi_edit', $page, $sort, $dir, $crit, $search_method);
}
/**
* Handle multi-edit actions.
*/
public function multi_edit()
{
$selected = ps('selected');
$selected_cache = ps('selected-cache');
$method = assert_string(ps('edit_method'));
if ($selected && is_array($selected)) {
$where = "name IN ('".implode("','", doSlash($selected))."')";
$lc_opts = do_list(get_pref('ied_plugin_lifecycle_options'));
switch ($method) {
case 'delete':
foreach ($selected as $name) {
if (safe_field('flags', 'txp_plugin', "name ='".doSlash($name)."'") & PLUGIN_LIFECYCLE_NOTIFY) {
load_plugin($name, true);
if (in_array('disabled', $lc_opts)) {
callback_event("plugin_lifecycle.$name", 'disabled');
}
if (in_array('deleted', $lc_opts)) {
callback_event("plugin_lifecycle.$name", 'deleted');
}
}
}
safe_delete('txp_plugin', $where);
break;
case 'changestatus':
switch (ps('switch_status')) {
case 'on':
$newstat = '1';
break;
case 'off':
$newstat = '0';
break;
case 'toggle':
default:
$newstat = '(1-status)';
break;
}
foreach ($selected as $name) {
if (safe_field('flags', 'txp_plugin', "name ='".doSlash($name)."'") & PLUGIN_LIFECYCLE_NOTIFY) {
$status = safe_field('status', 'txp_plugin', "name ='".doSlash($name)."'");
$status = $status ? 'disabled' : 'enabled';
load_plugin($name, true);
if (in_array($status, $lc_opts)) {
callback_event("plugin_lifecycle.$name", $status);
}
}
}
safe_update('txp_plugin', 'status = '.$newstat, $where);
break;
case 'changeorder':
$order = min(max(intval(ps('order')), 1), 9);
safe_update('txp_plugin', 'load_order = '.$order, $where);
break;
case 'lifecycle':
$lc_evs = do_list(ps('ied_lc_event'));
foreach ($selected as $name) {
foreach ($lc_evs as $lc_ev) {
callback_event("plugin_lifecycle.$name", $lc_ev);
}
}
break;
}
} else {
$selected = array();
}
if ($selected_cache && is_array($selected_cache)) {
$pcd = get_pref('plugin_cache_dir');
switch ($method) {
case 'delete':
foreach ($selected_cache as $name) {
$filenames = array();
$dir = dir($pcd);
while ($file = $dir->read()) {
if ($file != "." && $file != ".." && in_array($file, $selected_cache)) {
$fileaddr = $pcd . DS . $file;
if (!is_dir($fileaddr)) {
unlink($fileaddr);
}
}
}
$dir->close();
}
break;
case 'lifecycle':
$lc_evs = do_list(ps('ied_lc_event'));
foreach ($selected_cache as $name) {
$name = str_replace('.php', '', $name);
foreach ($lc_evs as $lc_ev) {
callback_event("plugin_lifecycle.$name", $lc_ev);
}
}
break;
case 'textpack':
// Read the textpack from the .php file and call install_textpack()
$textpack = array();
foreach ($selected_cache as $name) {
$fileaddr = $pcd . DS . $name;
$contents = file($fileaddr);
$in_tp = $in_comment = false;
foreach ($contents as $row) {
if (strpos($row, '/**') === 0) {
$in_comment = true;
}
if (strpos($row, '**/') === 0) {
$in_comment = false;
}
if (strpos($row, 'EOT;') !== false) {
break;
}
if ($in_tp === true) {
$textpack[] = trim($row);
}
if (!$in_comment && strpos($row, '$plugin[\'textpack\']') !== false) {
$in_tp = true;
}
}
}
$done = install_textpack(implode(n, $textpack));
break;
}
} else {
$selected_cache = array();
}
$message = '';
if ($selected || $selected_cache) {
if ($method === 'delete') {
$message = gTxt('plugin_deleted', array('{name}' => implode(', ', array_merge($selected, $selected_cache))));
} elseif ($method === 'lifecycle') {
$message = gTxt('ied_plugin_lc_fired', array('{name}' => implode(', ', array_merge($selected, $selected_cache)), '{event}' => implode(',', $lc_evs)));
} elseif ($method === 'textpack') {
$message = gTxt('textpack_strings_installed', array('{count}' => $done));
} else {
$message = gTxt('plugin_updated', array('{name}' => implode(', ', $selected)));
}
}
$this->table($message);
}
/**
* Render a hyperlink to another plugin composer method.
*
* @param string $evt Textpattern event destination
* @param string $stp Textpattern step (action) destination
* @param string $linktext Text for the hyperlink
* @param array $nv Name-value tuples to add to the URL
* @param array $atts Name-value tuples to add to the anchor tag
* @return HTML
*/
protected function anchor($evt, $stp, $linktext, $nv = array(), $atts = array())
{
$nv['_txp_token'] = form_token();
$atts['href'] = '?event=' . $evt . ($stp ? '&step=' . $stp : '') . '&' . http_build_query($nv);
$attribs = '';
foreach ($atts as $n => $v) {
$attribs .= ' ' . txpspecialchars($n) . '="' . txpspecialchars($v) . '"';
}
return tag($linktext, 'a', $attribs);
}
/**
* Render an asynchronous anchor tag for toggling plugin status.
*
* @param string $status Current plugin status (0=disabled, 1=enabled)
* @param string $name Plugin name to toggle
* @param string $linktext Link text to display on the anchor
* @return string HTML
*/
protected function status_link($status, $name, $linktext)
{
return asyncHref($linktext, array('step' => 'switch_status', 'thing' => $name),' title="'.($status==1 ? gTxt('disable') : gTxt('enable')).'"' );
}
/**
* Toggle the status of the given plugin (params sent via POST)
*
* @return string New status
*/
public function switch_status()
{
extract(array_map('assert_string', gpsa(array('thing', 'value'))));
$change = ($value == gTxt('yes')) ? 0 : 1;
safe_update('txp_plugin', "status = $change", "name = '".doSlash($thing)."'");
$lc_opts = do_list(get_pref('ied_plugin_lifecycle_options'));
$stp = $change ? 'enabled' : 'disabled';
if (in_array($stp, $lc_opts) && (safe_field('flags', 'txp_plugin', "name='".doSlash($thing)."'") & PLUGIN_LIFECYCLE_NOTIFY) ) {
load_plugin($thing, true);
$message = callback_event("plugin_lifecycle.$thing", $stp);
}
echo gTxt($change ? 'yes' : 'no');
}
/**
* Reset the given plugin to its last restore point.
*/
public function restore()
{
$name = doSlash(gps('name'));
safe_update("txp_plugin", "code = code_restore", "name='$name'");
$this->table(gTxt('ied_plugin_restored', array('{name}' => $name)));
}
/**
* Edit the selected plugin data.
*
* @param string $message Flash message to display after action
* @param string $newfile Filename if plugin being edited is in the filesystem
*/
public function edit($message = '', $newfile = '')
{
$newname = trim(gps('newname'));
$filename = gps('filename');
$editfile = $filename ? 1 : 0;
$name = empty($newname) ? gps('name') : $newname;
$name = ($newfile) ? $newfile : (($filename) ? $filename : $name);
pagetop(gTxt('ied_plugin_editing', array('{name}' => txpspecialchars($name))), $message);
require_privs('ied_plugin_composer');
echo $this->insert_editors();
if (!$editfile) {
$rs = safe_row("author, author_uri, version, description, code, help, status, type, load_order, flags", "txp_plugin", "name='".doSlash($name)."'");
extract($rs);
list($css,$help) = ($help) ? $this->extract_hunk($help, "CSS", "<!--|-->", true) : array('',$help);
} else {
$plugin = $this->read_file(get_pref('plugin_cache_dir').DS.$name);
$filename = $name;
$name = explode ('.', $name);
$fileext = array_pop($name);
$name = implode($name);
extract($plugin);
$status = ($fileext=='php')? 1: 0;
}
$ifel = get_pref('ied_plugin_interface_elems');
$distblock = (strpos($ifel, 'distribution') !== false);
$styleblock = (strpos($ifel, 'style') !== false);
$distribution = '';
list ($start_css, $end_css) = $this->make_markers("CSS", $this->ied_plugin_globals['css_start'], $this->ied_plugin_globals['css_end']);
if ($distblock) {
$plugin['name'] = $name;
$plugin['author'] = $author;
$plugin['author_uri'] = $author_uri;
$plugin['version'] = $version;
$plugin['description'] = $description;
$plugin['help'] = $this->textile($name, $help, $css, $start_css, $end_css);
$plugin['code'] = $code;
$plugin['type'] = $type;
$plugin['order'] = $load_order;
$plugin['flags'] = $flags;
$plugin['md5'] = md5( $plugin['code'] );
$distribution = '<textarea name="distribution" class="distribution-code" rows="1" onclick="this.select()">'.base64_encode(serialize($plugin)).'</textarea>';
}
for ($i = 1; $i <= 9; $i++) $orders[$i] = $i;
$tp_pfx = unserialize(get_pref('ied_plugin_tp_prefix', '', 1));
$tp_pfx = isset($tp_pfx[$name]) ? $tp_pfx[$name] : '';
$fnames = $this->get_name($name, $version);
$namedLink = ($filename) ? array('filename' => $filename) : array('name' => $name);
$zippedLink = array_merge($namedLink, array('type' => 'zip'));
$slink = $this->anchor($this->ied_pc_event, 'save_as_file', gTxt('ied_plugin_export', array('{name}' => $fnames[0])), $namedLink);
$sziplink = $this->anchor($this->ied_pc_event, 'save_as_file', gTxt('ied_plugin_export_zip', array('{name}' => $fnames[1])), $zippedLink);
$sphplink = $this->anchor($this->ied_pc_event, 'save_as_php_file', gTxt('ied_plugin_save_as', array('{name}' => $fnames[2])), $namedLink);
$stxtlink = $this->anchor($this->ied_pc_event, 'save_as_textpack', gTxt('ied_plugin_export_textpack'), $namedLink);
$vhelplinkfull = ($help) ? '[ ' .$this->anchor($this->ied_pc_event, 'help_viewer', gTxt('ied_plugin_docs'), $namedLink) . ' ]' : '';
$msgpop = '<div id="ied_plugin_msgpop"><input type="button" class="publish" value="'.gTxt('ok').'" onclick="ied_plugin_toggle_msgpop(\'0\');" /><h2>'.gTxt('ied_plugin_msgpop_lbl').'</h2><span class="ied_plugin_msgpop_content"></span></div>';
$newname = fInput('text', 'newname', $name, '', '', '', INPUT_REGULAR);
$author_widget = fInput('text', 'author', $author, '', '', '', INPUT_REGULAR);
$author_uri_widget = fInput('text', 'author_uri', $author_uri, '', '', '', INPUT_REGULAR);
$version_widget = fInput('text', 'version', $version, 'input-small', '', '',INPUT_SMALL) .sp. (($editfile) ? checkbox('rename_file', '1', 0, '','rename_file') . ' <label for="rename_file">'.gTxt('ied_plugin_rename_file').'</label>' : checkbox('restore_point', '1', 0, '','restore_point') . ' <label for="restore_point">'.gTxt('ied_plugin_restore_point').'</label>');
$description_widget = fInput('text', 'description', $description, 'input-xlarge', '', '', INPUT_REGULAR);
$codeblock = '<textarea name="code" id="plugin_editor" rows="'.INPUT_REGULAR.'" class="code codepress php" maxlength="'.$this->ied_plugin_globals['size_code'].'">'.txpspecialchars($code).'</textarea><div class="ied_plugin_info_bar"><span>'.gTxt('ied_plugin_jump_to_line').'</span><input type="text" id="ied_plugin_jumpToLine" size="5" maxlength="6" /><span class="ied_plugin_charsRemain"></span></div>';
$help_widget = '<textarea name="help" id="plugin_help" rows="'.INPUT_REGULAR.'" class="mceEditor" maxlength="'.$this->ied_plugin_globals['size_help'].'">'.txpspecialchars($help).'</textarea><div class="ied_plugin_info_bar"><span class="ied_plugin_charsRemain"></span></div>';
$css_widget = ($styleblock) ? '<textarea name="css" id="plugin_help_css" rows="'.INPUT_MEDIUM.'" class="code" maxlength="'.$this->ied_plugin_globals['size_css'].'">'.txpspecialchars($css).'</textarea><div class="ied_plugin_info_bar"><span class="ied_plugin_charsRemain"></span></div>' : '';
$plugstatus = (!$editfile) ? sp.sp.checkbox('status',1,$status, '','status'). ' <label for="status">'.gTxt('ied_plugin_enable').'</label>' : '';
$plugtypes = array(
'0' => gTxt('ied_plugin_type_0'),
'1' => gTxt('ied_plugin_type_1'),
'2' => gTxt('ied_plugin_type_2'),
'3' => gTxt('ied_plugin_type_3'),
'4' => gTxt('ied_plugin_type_4'),
'5' => gTxt('ied_plugin_type_5'),
);
$plugtype = selectInput('type', $plugtypes, $type, false, '', 'type');
$plugorder = selectInput('load_order', $orders, $load_order, 0, 0);
$flaglist = checkbox(
'flags[]',
PLUGIN_HAS_PREFS,
(($flags & PLUGIN_HAS_PREFS) ? 1 : 0),
'',
'ied_plugin_flag_has_prefs'
)
.n. '<label for="ied_plugin_flag_has_prefs">' . gTxt('ied_plugin_flag_has_prefs') . '</label>'
.n. checkbox(
'flags[]',
PLUGIN_LIFECYCLE_NOTIFY,
(($flags & PLUGIN_LIFECYCLE_NOTIFY) ? 1 : 0),
'',
'ied_plugin_flag_lifecycle_notify'
)
.n. '<label for="ied_plugin_flag_lifecycle_notify">' . gTxt('ied_plugin_flag_lifecycle_notify') . '</label>';
$sub = graf(fInput('submit', '', gTxt('save'), 'publish', '', '', '', '', 'ied_editSave'), array('class' => 'txp-save'));
$codesub = (!$editfile) ? '<a class="navlink" name="ied_plugin_code_save" id="ied_plugin_code_save">' . gTxt('ied_plugin_code_save') . '</a>' : '';
$metasub = (!$editfile) ? '<a class="navlink" name="ied_plugin_meta_save" id="ied_plugin_meta_save">' . gTxt('ied_plugin_meta_save') . '</a>' : '';
// Language info. ied_visible_langs is the user's choice of which ones they want to see available.
// ied_available_langs is the list of actual, currently-installed langs.
$theLang = get_pref('language');
$string_count = ($tp_pfx) ? safe_rows('lang, count(*) as count', 'txp_lang', "name like '".$tp_pfx."%' group by lang") : array();
$ied_listlangs = get_pref('ied_plugin_lang_choose', 'installed');
$ied_visible_langs = self::lang_list($ied_listlangs);
$ied_available_langs = ($ied_listlangs == 'installed') ? $ied_visible_langs : self::lang_list('installed');
$dflt_lang = get_pref('ied_plugin_lang_default', $theLang);
$dflt_lang = array_key_exists($dflt_lang, $ied_visible_langs) ? $dflt_lang : $theLang;
$dflt_lang_string_count = 0;
foreach ($string_count as $str_totals) {
if (isset($ied_visible_langs[$str_totals['lang']])) {
$ied_visible_langs[$str_totals['lang']] .= ' ['.$str_totals['count'].']';
if ($str_totals['lang'] === $dflt_lang) {
$dflt_lang_string_count = $str_totals['count'];
}
}
}
$langsel = selectInput('ied_plugin_tp_lang', $ied_visible_langs, $dflt_lang, '', '', 'ied_plugin_tp_lang')
.fInput('button', 'ied_plugin_tp_refresh', gTxt('ied_plugin_load'), '', '', '', '', '', 'ied_plugin_tp_refresh');
$preselected = do_list(get_pref('ied_plugin_lang_selected', ''));
$op_langs[] = '<select name="ied_plugin_tp_oplangs" id="ied_plugin_tp_oplangs" multiple="multiple"><option value=""></option>';
foreach ($ied_available_langs as $langcode => $alang) {
$sel = in_array($langcode, $preselected) ? ' selected="selected"' : '';
$op_langs[] = '<option value="'.$langcode.'"'.$sel.'>'.$alang.'</option>';
}
$op_langs[] = '</select>';
$tp_strings = array();
$tp_rows = $this->textpack_grab($dflt_lang, $tp_pfx);
foreach ($tp_rows as $tp_string) {
$apsel = selectInput('ied_plugin_tp_event', array('admin' => gTxt('admin'), 'public' => gTxt('public'), 'common' => gTxt('both')), ($tp_string['event'] == 'public' ? 'public' : ($tp_string['event'] == 'common' ? 'common' : 'admin')) );
$tp_strings[] = '<li>'.fInput('text', 'textpack_'.$tp_string['name'], $tp_string['data']).' '.$apsel.' <label>'.$tp_string['name'].'</label>'.'</li>';
}
$err_prefix = gTxt('ied_plugin_syntax_err');
$codesave_ok = gTxt('ied_plugin_code_saved');
$phpdoc = '';
// TODO
$classFinder = '/class[\s\n]+(\w+)[\s\n]*\{?(function[\s\n]+(\w+)[\s\n]*\(.*\)[\s\n]*\{?)*/';
$functionFinder = '/function[\s\n]+(\w+)[\s\n]*\(.*\)[\s\n]*\{?/';
preg_match_all($functionFinder, $code , $functionArray);
// selectInput requires key and value to be the same
//dmp($functionArray);
$fnArray = array();
/*
foreach ($functionArray[1] as $key => $val) {
$fnArray[$val] = $functionArray[1][$key];
}
$phpdoc = selectInput('ied_plugin_to_phpdoc', $fnArray, '', false, '', 'ied_plugin_to_phpdoc')
.'<a class="navlink" name="ied_plugin_btn_phpdoc" id="ied_plugin_btn_phpdoc">' . gTxt('ied_plugin_php_doc') . '</a>';
*/
$phpdoc = '';
$tabCount = $tabActive = 0;
$selectedTab = get_pref('pane_' . $this->ied_pc_event . '_visible');
$tokenSuffix = $this->ied_pc_event . form_token() . get_pref('blog_uid');
$panelSet = array(
0 => 'meta',
1 => 'code',
2 => 'pack',
3 => 'docs',
);
$switcherList = array();
foreach ($panelSet as $tabId => $panel) {
$switcherList[] = '<li><a href="#options_group_'.$panel.'" data-txp-pane="'.$panel.'" data-txp-token="' . md5($panel.$tokenSuffix).'">' . gTxt('ied_plugin_'.$panel.'_legend') . '</a></li>';
if ($panel === $selectedTab) {
$tabActive = $tabCount;
}
$tabCount++;
}
echo
n. form(
'<div class="txp-layout">'
.n. '<div class="txp-layout-2col">'
.n. hed(gTxt('ied_plugin_edit', array('{name}' => $name, '{version}' => $version)), 1, array('class' => 'txp-heading'))
.n. '</div>'
.n. '<div class="txp-layout-2col">'
.n. sLink($this->ied_pc_event, '', gTxt('ied_plugin_list'), 'ied_plugin_link')
.n. '</div>'
.n. '<div class="txp-layout-4col-alt" id="ied_edit_switcher" role="region">'
.n. '<section class="txp-details" id="all_options" aria-labelledby="all_options-label">'
.n. '<h3 id="all_options-label">Panels</h3>'
.n. '<div role="group">'
.n. '<ul class="switcher-list">'
.implode(n, $switcherList)
.n. '</ul>'
.n. '</div>'
.n. '</section>'
.n. $sub
.n. '<section class="txp-prefs-group" id="options_group_dist" aria-labelledby="options_group_dist-label">'
.n. hed(gTxt('ied_plugin_utils_legend'), 2, array('id' => 'options_group_dist-label'))
.n. graf(gTxt('ied_plugin_utils_legend_extra'), array('class' => 'warning'))
.n. (($distblock) ? '<div>' . $distribution . '</div>' : '')
.n. '<div class="txp-form-field txp-form-field-textarea">'
.n. '<div class="txp-form-field-label"><label for="ied_plugin_tp_oplangs">' . gTxt('ied_plugin_lang_choose') . '</label></div>'
.n. '<div class="txp-form-field-value">' . implode(n, $op_langs) . '</div>'
.n. '</div>'
.n. '<ul>'
.n. '<li>' . $slink . '</li>'
.n. '<li>' . $sziplink . '</li>'
.n. '<li>' . $stxtlink . '</li>'
.n. '<li>' . $sphplink . '</li>'
.n. '</ul>'
.n. '</section>'
.n. '</div>'
.n. '<div class="txp-layout-4col-3span" id="ied_edit_content" role="region">'
.n. '<section class="txp-prefs-group" id="options_group_meta" aria-labelledby="options_group_meta-label">'
.n. hed(gTxt('ied_plugin_meta_legend'), 2, array('id' => 'options_group_meta-label'))
.n. '<span class="ied_plugin_edit_toolbar">' . $metasub . '</span>'
.n. '<div class="txp-form-field">'
.n. '<div class="txp-form-field-label"><label for="newname">' . gTxt('name') . '</label></div>'
.n. '<div class="txp-form-field-value">' . $newname . '</div>'
.n. '</div>'
.n. '<div class="txp-form-field">'
.n. '<div class="txp-form-field-label"><label for="version">' . gTxt('version') . '</label></div>'
.n. '<div class="txp-form-field-value">'
.n. $version_widget
.n. $plugstatus
.n. (($filename) ? tag(sp.sp.'('.$filename.')','span',' style="color:gray;"').hInput('filename', $filename) : '')
.n. '</div>'
.n. '</div>'
.n. '<div class="txp-form-field">'
.n. '<div class="txp-form-field-label"><label for="description">' . gTxt('description') . '</label></div>'
.n. '<div class="txp-form-field-value">' . $description_widget . '</div>'
.n. '</div>'
.n. '<div class="txp-form-field">'
.n. '<div class="txp-form-field-label"><label for="author">' . gTxt('author') . '</label></div>'
.n. '<div class="txp-form-field-value">' . $author_widget . '</div>'
.n. '</div>'
.n. '<div class="txp-form-field">'
.n. '<div class="txp-form-field-label"><label for="author_uri">' . gTxt('website') . '</label></div>'
.n. '<div class="txp-form-field-value">' . $author_uri_widget . '</div>'
.n. '</div>'
.n. '<div class="txp-form-field">'
.n. '<div class="txp-form-field-label"><label for="type">' . gTxt('ied_plugin_type') . '</label></div>'
.n. '<div class="txp-form-field-value">' . $plugtype . '</div>'
.n. '</div>'
.n. '<div class="txp-form-field">'
.n. '<div class="txp-form-field-label"><label>' . gTxt('ied_plugin_flags') . '</label></div>'
.n. '<div class="txp-form-field-value">' . $flaglist . '</div>'
.n. '</div>'
.n. '<div class="txp-form-field">'
.n. '<div class="txp-form-field-label"><label>' . gTxt('ied_plugin_load_order') . gTxt('ied_plugin_load_order_help') . '</label></div>'
.n. '<div class="txp-form-field-value">' . $plugorder . '</div>'
.n. '</div>'
.n. '</section>'
.n. '<section class="txp-prefs-group" id="options_group_code" aria-labelledby="options_group_code-label">'
.n. hed(gTxt('ied_plugin_code_legend'), 2, array('id' => 'options_group_code-label'))
.n. '<span class="ied_plugin_edit_toolbar">' . $msgpop . $phpdoc . sp . $codesub . '</span>'
.n. '<div class="txp-form-field txp-form-field-textarea">'
.n. '<div class="txp-form-field-label"><label for="plugin_editor">PHP</label></div>'
.n. '<div class="txp-form-field-value">' . $codeblock . '</div>'
.n. '</div>'
.n. '</section>'
.n. '<section class="txp-prefs-group" id="options_group_pack" aria-labelledby="options_group_pack-label">'
.n. hed(gTxt('ied_plugin_pack_legend'), 2, array('id' => 'options_group_pack-label'))
.n. '<div class="txp-form-field">'
.n. '<div class="txp-form-field-label"><label for="ied_plugin_tp_prefix">' . gTxt('ied_plugin_tp_prefix') . '</label></div>'
.n. '<div class="txp-form-field-value">'
.n. fInput('text', 'ied_plugin_tp_prefix', $tp_pfx, '', '', '', '', '', 'ied_plugin_tp_prefix')
.n. $langsel
.n. fInput('hidden', 'ied_plugin_tp_lang_dflt', $dflt_lang, '', '', '', '', '', 'ied_plugin_tp_lang_dflt')
.n. '</div>'
.n. '</div>'
.n. '<div class="txp-form-field">'
.n. '<div class="txp-form-field-label"><label>' . gTxt('ied_plugin_tp_populate') . '</label></div>'
.n. '<div class="txp-form-field-value">'
.n. fInput('text', 'ied_plugin_tp_populate', '', '', '', '', '', '', 'ied_plugin_tp_populate')
.n. '<button id="ied_plugin_tp_load">' . gTxt('go') . '</button>'
.n. '<span id="ied_plugin_tp_load_count"></span>'
.n. '</div>'
.n. '</div>'
.n. '<a href="#" id="ied_plugin_add_string" class="txp-button">+</a>'
.n. '<ul>'
.n. implode(n, $tp_strings)
.n. '</ul>'
.n. '</section>'
.n. '<section class="txp-prefs-group" id="options_group_docs" aria-labelledby="options_group_docs-label">'
.n. hed(gTxt('ied_plugin_docs_legend') . n . $vhelplinkfull, 2, array('id' => 'options_group_docs-label'))
.n. '<div class="txp-form-field txp-form-field-textarea">'
.n. '<div class="txp-form-field-label"><label for="plugin_help">' . gTxt('text') . '</label></div>'
.n. '<div class="txp-form-field-value">' . $help_widget . '</div>'
.n. '</div>'
.n. (($styleblock)
? '<div class="txp-form-field txp-form-field-textarea">'
. '<div class="txp-form-field-label"><label for="plugin_help_css">' . gTxt('css') . '</label></div>'
. '<div class="txp-form-field-value">' . $css_widget . '</div>'
. '</div>'
: '')
.n. '</section>'
.n. '</div>'
.n. sInput('save')
.n. eInput($this->ied_pc_event)
.n. hInput('name',$name)
, '', '', 'post', 'ied-edit-form').
script_js(<<<EOJS
var selectedTab = '{$tabActive}';
var iedPluginGroup = $('.ied-edit-form');
var iedPluginTabs = iedPluginGroup.find('.switcher-list li');
iedPluginGroup.tabs({active: selectedTab}).removeClass('ui-widget ui-widget-content ui-corner-all').addClass('ui-tabs-vertical');
iedPluginGroup.find('.switcher-list').removeClass('ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all');
iedPluginTabs.removeClass('ui-state-default ui-corner-top');
iedPluginGroup.find('.txp-prefs-group').removeClass('ui-widget-content ui-corner-bottom');
iedPluginTabs.on('click', 'a', function(ev) {
var me = $(this);
sendAsyncEvent({
event : 'pane',
step : 'tabVisible',
pane : me.data('txp-pane'),
origin : textpattern.event,
token : me.data('txp-token')
});
});
var ied_plugin_tp_total = 0;
jQuery.fn.selectRange = function(start, end) {
return this.each(function() {
if (this.setSelectionRange) {
this.focus();
this.setSelectionRange(start, end);
} else if (this.createTextRange) {
var range = this.createTextRange();
range.collapse(true);
range.moveEnd('character', end);
range.moveStart('character', start);
range.select();
}
});
};
Array.prototype.unique = function() {
var r = new Array();
o:for (var i = 0, n = this.length; i < n; i++) {
for (var x = 0, y = r.length; x < y; x++) {
if (r[x]==this[i]) {
continue o;
}
}
r[r.length] = this[i];
}
return r;
};
function ied_goToLine()
{
var line = parseInt(jQuery('#ied_plugin_jumpToLine').val());
var ied_ed = jQuery('#plugin_editor');
var ied_edd = document.getElementById('plugin_editor'); // Dunno how to convert a jQuery obj back to DOM
var lines = ied_ed.val().split('\\n');
var numchars = 0;
var count = 0;
var findstr = '';
jQuery.each(lines, function () {
count++;
if (count >= line) {
findstr = this;
return false;
}
numchars += (this.length)+2; // Don't ask. +2 is something to do with line endings I think
});
// Find the line containing the string we found. Start counting from the line before.
// Those pesky line endings come into play again so we need to subtract the number
// of lines found from the start character position *shrug*
start = ied_ed.val().indexOf(findstr, numchars-count);
start = (findstr == '') ? start+1 : start;
end = start+findstr.length;
ied_ed.selectRange(end-1, end);
if (document.createEvent) {
var ied_theCode = ied_ed.val().charCodeAt(end-1);
if (window.KeyEvent) {
var ev = document.createEvent('KeyEvents');
ev.initKeyEvent('keypress', false, true, window, false, false, false, false, 0, ied_theCode);
} else {
var ev = document.createEvent('UIEvents');
ev.initUIEvent('keypress', false, true, window, 0);
ev.keyCode = ied_theCode;
}
ied_edd.dispatchEvent(ev); // cause scroll to cursor by replacing last char with itself
}
ied_ed.selectRange(start, end);
return false;
}
function ied_plugin_toggle_msgpop(state)
{
var obj = jQuery("#ied_plugin_msgpop");
if (state != undefined) {
if (state == 1) {
obj.show('normal');
} else {
obj.hide('normal');
}
} else {
obj.toggle('normal');
}
}
function ied_plugin_rtrim(str, chars)
{
chars = chars || "\s";
return str.replace(new RegExp("[" + chars + "]+$", "g"), "");
}
function ied_plugin_update_tp_count()
{
var tp_count = tp_warns = tp_has_content = 0;
jQuery('#options_group_pack ul li').each(function() {
var self = jQuery(this);
tp_count++;
if (self.find('input').val() !== '') {
tp_has_content++;
}
if (self.find('label').hasClass('warning')) {
tp_warns++;
}
});
jQuery('#ied_plugin_tp_count').empty().append('(' +tp_count+ ' | ' +tp_has_content+ ' | ' +tp_warns+ ')');
jQuery('#ied_plugin_tp_lang option').find(':selected').data('string-count', tp_has_content);
// Update the global var for use when loading strings
ied_plugin_tp_total = tp_count;
}
jQuery(function () {
curh = getCookie('ied_plugin_edheight');
curh = (curh == null) ? '480' : curh;
jQuery('textarea[maxlength]').keyup(function () {
var max = parseInt(jQuery(this).attr('maxlength'));
if (jQuery(this).val().length > max) {
jQuery(this).val(jQuery(this).val().substr(0, jQuery(this).attr('maxlength')));
}
jQuery(this).parent().find('.ied_plugin_charsRemain').html('Chars remaining: '+ (max - jQuery(this).val().length));
});
jQuery('textarea[maxlength]').keyup();
jQuery('#ied_plugin_jumpToLine').keydown(function (e) {
var code = (e.keyCode ? e.keyCode : e.which);
if (code == 13) {
e.preventDefault();
e.stopPropagation();
ied_goToLine();
return false;
}
});
// Store the prefix
jQuery('#ied_plugin_tp_prefix').blur(function () {
var pfx = jQuery(this).val();
sendAsyncEvent(
{
event: textpattern.event,
step: 'set_tp_prefix',
plugin: '{$name}',
prefix: pfx
});
});
// Find all occurrences of gTxt('something')
jQuery('#plugin_editor, #ied_plugin_tp_prefix').blur(function () {
var ied_tp_pfx = jQuery('#ied_plugin_tp_prefix').val();
if (ied_tp_pfx != '') {
var ied_gtxt_re = /gTxt\([\'\"]([a-zA-Z0-9_]*?)[\"\'][,\)]/gi;
var ied_tp_ta = jQuery('#plugin_editor').val().replace(/\s*/g,''); // Strip spaces to make lookups easier
var ied_tp_items = ied_tp_ta.match(ied_gtxt_re);
// if JS RegExp captured parenthical expressions in global searches or it was easy to inject variables
// into new RegExp() calls, this loop could be avoided
var ied_tp_used = [];
for (var idx = 0; idx < ied_tp_items.length; idx++) {
var pos = ied_tp_items[idx].lastIndexOf("'");
pos = (pos == -1) ? ied_tp_items[idx].lastIndexOf('"') : pos;
tpstr = ied_tp_items[idx].substr(6,pos-6);
if (tpstr.indexOf(ied_tp_pfx) == 0) {
ied_tp_used[ied_tp_used.length] = tpstr;
}
}
ied_tp_used = ied_tp_used.unique();
// List of all current textpack strings in use (as of last Save operation)
var ied_tp_curr = [];
jQuery('#options_group_pack ul label').each(function () {
ied_tp_curr[ied_tp_curr.length] = jQuery(this).text();
});
// Iterate over current array and check if each name is in the used textpack item list.
// If it is, remove it from the final list.
for (var idx = 0; idx < ied_tp_curr.length; idx++) {
if ((pos = jQuery.inArray(ied_tp_curr[idx], ied_tp_used)) > -1) {
ied_tp_used.splice(pos, 1);
jQuery('#options_group_pack ul label:contains('+ied_tp_curr[idx]+')').toggleClass('warning', false).next(".destroy").remove();
} else {
setclass = 1;
jQuery('#options_group_pack ul label:contains('+ied_tp_curr[idx]+')').toggleClass('warning', true).next(".destroy").remove().end().after('<a href="#" class="destroy"><span class="ui-icon ui-icon-close">Delete</span></a>');
}
}
// For each remaining item that has been used, add an input box
// TODO: i18n the select options
for (var idx = 0; idx < ied_tp_used.length; idx++) {
jQuery('#options_group_pack ul').prepend('<li><input type="text" name="textpack_'+ied_tp_used[idx]+'" value="" /> <select name="ied_plugin_tp_event"><option value="admin">Admin</option><option value="public">Public</option><option value="common">Both</option></select> <label>'+ied_tp_used[idx]+'</label></li>');
}
ied_plugin_update_tp_count();
}
}).blur();
// Handle adding new strings manually
jQuery('#ied_plugin_add_string').click(function (ev) {
jQuery('#options_group_pack ul').before('<div id="ied_plugin_new_container"><label>'+jQuery('#ied_plugin_tp_prefix').val()+'_<input type="text" name="ied_plugin_tp_newname" id="ied_plugin_tp_newname" value="" /></label></div>');
jQuery('#ied_plugin_tp_newname').focus();
ev.preventDefault();
});
jQuery(document).on('blur', '#ied_plugin_tp_newname', function () {
var newname = ied_plugin_rtrim(jQuery('#ied_plugin_tp_prefix').val()+'_'+jQuery('#ied_plugin_tp_newname').val(), '_');
var newok = true;
jQuery('#options_group_pack ul li label').each(function () {
if (jQuery(this).text() == newname) {
jQuery('#ied_plugin_tp_newname').css('color', '#E00');
newok = false;
}
});
// TODO: i18n select option text
if (newok) {
jQuery('#options_group_pack ul').prepend('<li><input type="text" name="textpack_'+newname+'" value="" /> <select name="ied_plugin_tp_event"><option value="admin">Admin</option><option value="public">Public</option><option value="common">Both</option></select> <label>'+newname+'</label></li>');
jQuery('#ied_plugin_new_container').remove();
jQuery('input[name="textpack_'+newname+'"]').focus();
}
ied_plugin_update_tp_count();
});
// Initialise the generic AJAX error handler
jQuery('.ied_editForm').ajaxError(function (event, request, settings) {
var xhr = jQuery(request.responseText);
// phpdoc generation barfed
if (settings.data.indexOf('step=ied_plugin_generate_phpdoc') > -1) {
var msgContent = jQuery("#ied_plugin_msgpop .ied_plugin_msgpop_content");
status = xhr.find('http-status').attr('value')
if (status == '200 OK') {
msgContent.append(xhr.find('ied_plugin_phpdoc').attr('value'));
} else if (status == '501 Not Implemented') {
msgContent.append(xhr.find('error_msg').attr('value'));
}
ied_plugin_toggle_msgpop('1');
}
// code save barfed
if (settings.data.indexOf('step=ied_plugin_code_save') > -1) {
var msg = xhr.find('ied_plugin_msg').attr('value');
var line = xhr.find('ied_plugin_err_line').attr('value');
jQuery('#ied_plugin_jumpToLine').val(line);
ied_goToLine();
var codeobj = jQuery("#plugin_editor");
codeobj.css({'opacity': '.75'}); // Reduced opacity as a visual cue that something's wrong
eval(msg); // bleurgh!
}
});
// Handle 'x' button
jQuery(document).on('click', '.destroy', function (event) {
var elem = jQuery(this).prev('label');
var tp_lbl = elem.text();
sendAsyncEvent(
{
event: textpattern.event,
step: 'textpack_del',
ied_tp_lbl: tp_lbl
});
elem.parent().remove();
event.preventDefault();
ied_plugin_update_tp_count();
});
// Save textpack string to database
function ied_plugin_tp_save(event)
{
var elem = jQuery(this);
var isSel = elem.is('select');
var tp_lbl = elem.nextAll('label').text();
var tp_str = (isSel) ? elem.prevAll('input').val() : elem.val();
var tp_ev = (isSel) ? elem.val() : elem.nextAll('select').val();
var tp_evt = (tp_ev=='public' || tp_ev=='common') ? tp_ev : jQuery('#ied_plugin_tp_prefix').val();
var tp_lng = jQuery('#ied_plugin_tp_lang').val();
var tp_own = jQuery('#ied_plugin_tp_prefix').val();
sendAsyncEvent(
{
event: textpattern.event,
step: 'textpack_save',
ied_tp_evt: tp_evt,
ied_tp_lbl: tp_lbl,
ied_tp_lng: tp_lng,
ied_tp_str: tp_str,
ied_tp_own: tp_own
});
}
// Handle saving textpack string
jQuery(document).on('blur', '#options_group_pack ul li input', ied_plugin_tp_save);
jQuery(document).on('change', '#options_group_pack ul li select', ied_plugin_tp_save);
// Handle language change.
jQuery("#ied_plugin_tp_lang").change(function (event) {
jQuery('#ied_plugin_tp_load_count').empty().show();
var tp_lng = jQuery(this).val();
var tp_dflt = jQuery('#ied_plugin_tp_lang_dflt').val();
var sel = '#options_group_pack ul li';
var numStrings = sel.length;
var numFetched = 0;
jQuery(sel).each(function () {
var obj = jQuery(this);
var tp_lbl = obj.find('label').text();
var tp_dest = obj.find('input');
sendAsyncEvent(
{
event: textpattern.event,
step: 'textpack_get',
ied_tp_lbl: tp_lbl,
ied_tp_lng: tp_lng,
ied_tp_dflt: tp_dflt
}, function (data) {
numFetched++;
var theVal = data.ied_plugin_tp_string;
var xl8str = data.ied_plugin_tp_dflt;
tp_dest.val(theVal);
if (xl8str == undefined || xl8str == '') {
obj.removeAttr('title');
} else {
obj.attr('title', xl8str);
}
if (numFetched < ied_plugin_tp_total) {
jQuery('#ied_plugin_tp_load_count').text(numFetched + '/' + ied_plugin_tp_total);
} else {
jQuery('#ied_plugin_tp_load_count').text('OK').hide('slow');
ied_plugin_update_tp_count();
}
},
'json');
});
});
// Current language refresh.
jQuery('#ied_plugin_tp_refresh').click(function () {
// Trigger the change event
jQuery("#ied_plugin_tp_lang").change();
});
// Load textpack strings from plugin's custom gTxt()
jQuery("#ied_plugin_tp_load").click(function (event) {
var ied_fn = jQuery("#ied_plugin_tp_populate").val();
jQuery('#options_group_pack ul li').each(function () {
var obj = jQuery(this);
var tp_lbl = obj.find('label').text();
var tp_dest = obj.find('input');
sendAsyncEvent(
{
event: textpattern.event,
step: 'textpack_load',
ied_tp_fn: ied_fn,
ied_tp_lbl: tp_lbl
}, function (data) {
// Paste the returned string into the input box and save it by invoking blur()
tp_dest.val(data.ied_plugin_tp_string).blur();
},
'json');
});
event.preventDefault();
});
// Handle saving metadata.
jQuery("#ied_plugin_meta_save").click(function(event) {
var msgarea = jQuery("#ied_plugin_messages");
msgarea.empty();
var metaobj = jQuery("#options_group_meta");
var datablock = {};
jQuery(metaobj).find('input[type="text"], select').each(function() {
var obj = jQuery(this);
var objid = obj.attr('name');
datablock[objid] = obj.val();
});
jQuery(metaobj).find('input[type="radio"], input[type="checkbox"]').each(function() {
var obj = jQuery(this);
var objid = obj.attr('name').replace(/\[\]/, '');
if (typeof datablock[objid] == 'undefined') {
datablock[objid] = [];
}
if (obj.prop('checked') === true) {
datablock[objid].push(obj.val());
}
});
jQuery(metaobj).find('select').each(function() {
var obj = jQuery(this);
var objid = obj.attr('name');
if (obj.prop('selected') === true) {
datablock[objid] = obj.val();
}
});
var plugin = '{$name}';
metaobj.css('opacity', '0.4');
sendAsyncEvent(
{
event: textpattern.event,
step: 'meta_save',
plugin: plugin,
data: datablock
}, function(data) {
metaobj.css({'opacity': '1'});
var msg = jQuery(data).find('ied_plugin_msg').attr('value');
eval(msg); // yuk!
});
event.preventDefault();
});
// Handle saving code.
jQuery("#ied_plugin_code_save").click(function (event) {
var msgarea = jQuery("#ied_plugin_messages");
msgarea.empty();
var codeobj = jQuery("#plugin_editor");
var codeblock = codeobj.val();
var plugin = '{$name}';
codeobj.css('opacity', '0.4');
sendAsyncEvent(
{
event: textpattern.event,
step: 'code_save',
plugin: plugin,
codeblock: codeblock
}, function (data) {
codeobj.css({'opacity': '1'});
var msg = jQuery(data).find('ied_plugin_msg').attr('value');
eval(msg); // yuk!
});
event.preventDefault();
});
// Handle generating phpdoc
jQuery("#ied_plugin_btn_phpdoc").click(function (event) {
var msgarea = jQuery("#ied_plugin_msgpop");
var msgContent = jQuery("#ied_plugin_msgpop .ied_plugin_msgpop_content");
msgContent.empty();
var fnobj = jQuery("#ied_plugin_to_phpdoc");
var fn = fnobj.val();
var plugin = '{$name}';
sendAsyncEvent(
{
event: textpattern.event,
step: 'generate_phpdoc',
plugin: plugin,
fn: fn
}, function (data) {
msgContent.append(jQuery(data).find('ied_plugin_phpdoc').attr('value'));
ied_plugin_toggle_msgpop('1');
});
event.preventDefault();
});
jQuery('#ied_plugin_tp_oplangs').change(function () {
sel = jQuery('#ied_plugin_tp_oplangs option:selected').map(function () { return this.value }).get().join(', ');
sendAsyncEvent(
{
event: textpattern.event,
step: 'lang_set',
ied_tp_langsel: sel
});
});
});
EOJS
)
.n. '</div>';
}
/**
* Save the plugin to the DB or filesystem.
*/
public function save()
{
extract(doSlash(gpsa(array('name','newname','filename','code','author','author_uri','version','description','help','css','status','type','load_order','rename_file','restore_point','ied_plugin_tp_prefix'))));
$flags = gps('flags');
list ($start_css, $end_css) = $this->make_markers("CSS", $this->ied_plugin_globals['css_start'], $this->ied_plugin_globals['css_end']);
$extraMsg = $newfilename = $msg1 = $msg2 = '';
$newname = trim($newname);
if ($flags) {
$flagout = 0;
foreach ($flags as $flag) {
$flagout |= $flag;
}
$flags = $flagout;
}
if (empty($newname)) {
$msg1 = gTxt('ied_plugin_name_first');
$msgType = E_ERROR;
} else {
if (empty($filename)) {
$hout = array();
$hout[0] = $help;
if ($css) { $hout[1] = n.$start_css.n.$css.n.$end_css; }
safe_update(
'txp_plugin',
"name='".$newname."',
status = ".intval($status).",
type = ".intval($type).",
author = '".$author."',
author_uri = '".$author_uri."',
version = '".$version."',
description = '".$description."',
help = '".implode('',$hout)."',
code = '".$code."',
flags = ".intval($flags).",
".(($restore_point == 1)? "code_restore = '".$code."'," : '')."
load_order = ".$load_order,
"name = '".$name."'");
$msg1 = gTxt('plugin_saved', array('{name}' => $newname));
$msgType = '';
} else {
$pcd = get_pref('plugin_cache_dir');
$dir = $pcd.DS;
if (empty($pcd)) {
$filecontent = '';
$msg2 = gTxt('ied_plugin_cache_not_set');
} else {
$ioo = get_pref('ied_plugin_output_order');
$oporder = (isset($ioo) && is_numeric($ioo)) ? $ioo : 0;
$textpack = $this->textpack_build($newname);
$helpchunk = $this->build_template('help', array($help, ' ')); // Use a space to force a CSS hunk in the file
$codechunk = $this->build_template('code', $code);
$filecontent = $this->build_template("preamble")
.$this->build_template("name", $newname)
.$this->build_template("html_help")
.$this->build_template("version", $version)
.$this->build_template("author", $author)
.$this->build_template("author_uri", $author_uri)
.$this->build_template("description", $description)
.$this->build_template("load_order", $load_order)
.$this->build_template("type", $type)
.$this->build_template("flags", $flags)
.$this->build_template("textpack", $textpack)
.$this->build_template("include")
.(($oporder == 0) ? $codechunk : $helpchunk)
.(($oporder == 1) ? $codechunk : $helpchunk)
.$this->build_template("postamble");
$msgType = '';
}
$filecontent = $this->make_array($filecontent);
$metavars = array("name" => "$newname",
"version" => "$version",
"author" => "$author",
"author_uri" => "$author_uri",
"description" => "$description",
"type" => "$type",
"flags" => "$flags",
"order" => "$load_order",
);
$hunkvars = array("CODE" => doStrip(str_replace('\r\n','
',$code)), // newline workaround
"HELP" => doStrip(str_replace('\r\n','
',$help)),
"CSS" => doStrip(str_replace('\r\n','
',$css)),
);
foreach ($metavars as $varname => $value) {
for ($idx = 0; $idx < count($filecontent); $idx++) {
if (strpos($filecontent[$idx], '$plugin[\''.$varname.'\']') === 0) {
$filecontent[$idx] = '$plugin[\''.$varname.'\'] = '.doQuote($value).';';
break;
}
}
}
foreach ($hunkvars as $varname => $hunk) {
list ($start_delim, $end_delim) = $this->make_markers($varname, $this->ied_plugin_globals['dlm_start'], $this->ied_plugin_globals['dlm_end']);
$start = array_search($start_delim, $filecontent) + 1;
$end = array_search($end_delim, $filecontent);
if (is_numeric($start) && is_numeric($end) && $end >= $start) {
array_splice($filecontent, $start, $end-$start, $hunk);
}
}
$filecontent = implode("\n", $filecontent);
if ($filecontent) {
$fh = fopen($dir.$filename, 'w+');
fwrite($fh, $filecontent);
fclose($fh);
}
$msg1 = (empty($msg1)) ? gTxt('plugin_saved', array('{name}' => $newname)) : $msg1;
$msgType = '';
// Make new file if required
if ($rename_file == 1) {
$fnames = $this->get_name($newname, $version);
$newfilename = $fnames[2];
$res = rename($dir.$filename, $dir.$newfilename);
$extraMsg = ($res) ? gTxt('ied_plugin_renamed') : gTxt('ied_plugin_rename_failed');
}
}
// Store the plugin textpack prefix
$this->set_tp_prefix($newname, $ied_plugin_tp_prefix);
}
if ($msg2) {
$this->table($msg2);
} else {
// Check the plugin type matches the code used.
$extraMsg .= ($this->admin_check($code, $type)) ? '' : strong(gTxt('ied_plugin_check_type'));
$message = $msg1.$extraMsg;
$this->edit(array($message, $msgType), $newfilename);
}
}
/**
* Export the plugin as a distributable txt file.
*/
public function save_as_file()
{
if (gps('name')) {
$name = gps('name');
$rs = safe_row('description, author, author_uri, version, code, help, type, load_order, flags', 'txp_plugin', "name='".doSlash($name)."'");
extract($rs);
list($css,$help) = ($help) ? $this->extract_hunk($help, "CSS", "<!--|-->", true) : array('',$help);
} elseif (gps('filename')) {
$plugin = $this->read_file(get_pref('plugin_cache_dir').DS.gps('filename'));
extract($plugin);
}
$zip = gps('type');
if (gps('trim')==1) {
$code=explode("\r\n",$code);
$code=array_map('trim',$code);
$code=implode("\r\n",$code);
}
// Get any textpack strings
$textpack = $this->textpack_build($name);
list ($start_css, $end_css) = $this->make_markers("CSS", $this->ied_plugin_globals['css_start'], $this->ied_plugin_globals['css_end']);
$fnames = $this->get_name($name, $version);
header('Content-type: text/plain');
header('Content-Disposition: attachment; filename=' . (($zip === 'zip') ? $fnames[1] : $fnames[0]));
$types = array('Public' , 'Admin/Public' , 'Library' , 'Admin', 'Admin/AJAX', 'Admin/Public/AJAX'); // No gTxt() because the template is English
$plugin['name'] = $name;
$plugin['author'] = $author;
$plugin['author_uri'] = $author_uri;
$plugin['version'] = $version;
$plugin['description'] = $description;
$plugin['code'] = $code;
$plugin['type'] = $type;
$plugin['order'] = $load_order;
$plugin['flags'] = $flags;
// $plugin['allow_html_help'] = true;
// $plugin['help_raw'] = $help.$start_css.$css.$end_css;
$plugin['help'] = $this->textile($name, $help, $css, $start_css, $end_css);
$plugin['md5'] = md5( $plugin['code'] );
if ($textpack) {
$plugin['textpack'] = $textpack;
}
echo '# Name: '.$name.' v'.$version.' '.(($zip === 'zip') ? "(compressed)" : "").'
# Type: '.$types[$type].' plugin
# '.$description.'
# Author: '.$author.'
# URL: '.$author_uri.'
# Recommended load order: '.$load_order.'
# .....................................................................
# This is a plugin for Textpattern CMS - http://textpattern.com/
# To install: textpattern > admin > plugins
# Paste the following text into the \'Install plugin\' box:
# .....................................................................
'.(($zip === 'zip') ? chunk_split(base64_encode(gzencode(serialize($plugin))), 72) : chunk_split(base64_encode(serialize($plugin)), 72));
die();
}
/**
* Export the plugin as a plugin template PHP file.
*/
public function save_as_php_file()
{
if (gps('name')) {
$name = gps('name');
$rs = safe_row("description, author, author_uri, version, code, help, type, load_order, flags", "txp_plugin", "name='".doSlash($name)."'");
extract($rs);
list($css,$help) = ($help) ? $this->extract_hunk($help, "CSS", "<!--|-->", true) : array('',$help);
} elseif (gps('filename')) {
$plugin=$this->read_file(get_pref('plugin_cache_dir').DS.gps('filename'));
extract($plugin);
}
$ioo = get_pref('ied_plugin_output_order');
$oporder = (isset($ioo) && is_numeric($ioo)) ? $ioo : 0;
$fnames = $this->get_name($name, $version);
header('Content-type: text/php');
header('Content-Disposition: attachment; filename=' .$fnames[2]);
$textpack = $this->textpack_build($name);
$helpchunk = $this->build_template('help', array($help, $css));
$codechunk = $this->build_template('code', str_replace("\r\n","\n",$code));
echo $this->build_template('preamble').
$this->build_template('name', $name).
$this->build_template('html_help').
$this->build_template('version', $version).
$this->build_template('author', $author).
$this->build_template('author_uri', $author_uri).
$this->build_template('description', $description).
$this->build_template('load_order', $load_order).
$this->build_template('type', $type).
$this->build_template('flags', $flags).
$this->build_template('textpack', $textpack).
$this->build_template('include').
(($oporder == 0) ? $codechunk : $helpchunk).
(($oporder == 1) ? $codechunk : $helpchunk).
$this->build_template('postamble');
die();
}
/**
* Export only the textpacks from the plugin.
*/
public function save_as_textpack()
{
if (gps('name')) {
$name = $base_name = gps('name');
$version = safe_field('version', "txp_plugin", "name='".doSlash($name)."'");
} elseif (gps('filename')) {
$name = gps('filename');
$base_name = str_replace('.php', '', $name);
$plugin=$this->read_file(get_pref('plugin_cache_dir').DS.gps('filename'));
$version = $plugin['version'];
}
$langs = gps('lang');
if (!$langs) {
$langs = get_pref('ied_plugin_lang_selected', '');
}
$force = 0;
if ($langs == '') {
$langstr = 'all';
$force = 1;
} else {
$langlist = do_list($langs);
if (count($langlist) == 1) {
$langstr = $langlist[0];
} else {
$country_codes = array();
foreach ($langlist as $ln) {
$lparts = do_list($ln, '-');
$country_codes[] = $lparts[0];
}
$langstr = implode('+', array_unique($country_codes));
}
}
$textpack = $this->textpack_build($base_name, $force);
$fnames = $this->get_name($name, $version, $langstr);
header('Content-type: text/html; charset=UTF-8');
header('Content-Disposition: attachment; filename=' . $fnames[3]);
echo $textpack;
die();
}
/**
* Create a plugin in standard template format.
*/
public function build_template($prt, $val='')
{
$css = $help = '';
if (is_array($val)) {
$help = $val[0];
$css = isset($val[1]) ? $val[1] : '';
$val = '';
}
$template = array(
"preamble" => '<?php'.n.n
.'// This is a PLUGIN TEMPLATE for Textpattern CMS.'.n.n
.'// Copy this file to a new name like abc_myplugin.php. Edit the code, then'.n
.'// run this file at the command line to produce a plugin for distribution:'.n
.'// $ php abc_myplugin.php > abc_myplugin-0.1.txt'.n.n,
"name" => '// Plugin name is optional. If unset, it will be extracted from the current'.n
.'// file name. Plugin names should start with a three letter prefix which is'.n
.'// unique and reserved for each plugin author ("abc" is just an example).'.n
.'// Uncomment and edit this line to override:'.n
.'$plugin[\'name\'] = '.doQuote(doSlash($val)).';'.n.n,
"html_help" => '// Allow raw HTML help, as opposed to Textile.'.n
.'// 0 = Plugin help is in Textile format, no raw HTML allowed (default).'.n
.'// 1 = Plugin help is in raw HTML. Not recommended.'.n
.'# $plugin[\'allow_html_help\'] = 1;'.n.n,
"version" => '$plugin[\'version\'] = '.doQuote($val).';'.n,
"flags" => '// Plugin "flags" signal the presence of optional capabilities to the core plugin loader.'.n
.'// Use an appropriately OR-ed combination of these flags.'.n
.'// The four high-order bits 0xf000 are available for this plugin\'s private use'.n
.'if (!defined(\'PLUGIN_HAS_PREFS\')) define(\'PLUGIN_HAS_PREFS\', 0x0001); // This plugin wants to receive "plugin_prefs.{$plugin[\'name\']}" events'.n
.'if (!defined(\'PLUGIN_LIFECYCLE_NOTIFY\')) define(\'PLUGIN_LIFECYCLE_NOTIFY\', 0x0002); // This plugin wants to receive "plugin_lifecycle.{$plugin[\'name\']}" events'.n.n
.'$plugin[\'flags\'] = '.doQuote($val).';'.n.n,
"textpack" => '// Plugin \'textpack\' is optional. It provides i18n strings to be used in conjunction with gTxt().'.n
.'// Syntax:'.n
.'// ## arbitrary comment'.n
.'// #@event'.n
.'// #@language ISO-LANGUAGE-CODE'.n
.'// abc_string_name => Localized String'.n.n
.(($val)
? '$plugin[\'textpack\'] = <<<EOT'.n
.$val.n
.'EOT;'.n.n
: '/** Uncomment me, if you need a textpack'.n
.'$plugin[\'textpack\'] = <<< EOT'.n
.'#@admin'.n
.'#@language en-gb'.n
.'abc_sample_string => Sample String'.n
.'abc_one_more => One more'.n
.'#@language de-de'.n
.'abc_sample_string => Beispieltext'.n
.'abc_one_more => Noch einer'.n
.'EOT;'.n
.'**/'.n
.'// End of textpack'.n.n),
"author" => '$plugin[\'author\'] = '.doQuote(doSlash($val)).';'.n,
"author_uri" => '$plugin[\'author_uri\'] = '.doQuote(doSlash($val)).';'.n,
"description" => '$plugin[\'description\'] = '.doQuote(doSlash($val)).';'.n.n,
"load_order" => '// Plugin load order:'.n
.'// The default value of 5 would fit most plugins, while for instance comment'.n
.'// spam evaluators or URL redirectors would probably want to run earlier'.n
.'// (1...4) to prepare the environment for everything else that follows.'.n
.'// Values 6...9 should be considered for plugins which would work late.'.n
.'// This order is user-overrideable.'.n
.'$plugin[\'order\'] = '.doQuote($val).';'.n.n,
"type" => '// Plugin \'type\' defines where the plugin is loaded'.n
.'// 0 = public : only on the public side of the website (default)'.n
.'// 1 = public+admin : on both the public and admin side'.n
.'// 2 = library : only when include_plugin() or require_plugin() is called'.n
.'// 3 = admin : only on the admin side (no AJAX)'.n
.'// 4 = admin+ajax : only on the admin side (AJAX supported)'.n
.'// 5 = public+admin+ajax : on both the public and admin side (AJAX supported)'.n
.'$plugin[\'type\'] = '.doQuote($val).';'.n.n,
"include" => 'if (!defined(\'txpinterface\'))'.n
.' @include_once(\'zem_tpl.php\');'.n.n,
"help" => 'if (0) {'.n
.'?>'.n
. (($css) ? '<!--'.n
.'# --- BEGIN PLUGIN CSS ---'.n
.$css.n
.'# --- END PLUGIN CSS ---'.n
.'-->'.n : '')
.'<!--'.n
.'# --- BEGIN PLUGIN HELP ---'.n
.$help.n
.'# --- END PLUGIN HELP ---'.n
.'-->'.n
.'<?php'.n
.'}'.n,
"code" => '# --- BEGIN PLUGIN CODE ---'.n
.$val.n
.'# --- END PLUGIN CODE ---'.n,
"postamble" => '?>',
);
return (array_key_exists($prt, $template) ? $template[$prt] : '');
}
/**
* Create / upload / install a new plugin.
*/
public function create()
{
extract(doSlash(gpsa(array('name', 'plugin_create', 'plugin_upload', 'plugin_install', 'ied_plugin_autoenable'))));
if ($plugin_create) {
$fname = '';
if ($name == '') {
$this->table(array(gTxt('ied_plugin_name_first'), E_ERROR));
return;
}
if (strpos($name, ".php") !== false) {
$fname = $name;
$name = basename($name, ".php");
}
$exists = fetch('name', 'txp_plugin', 'name', $name);
// MySQL defaults to set status=1 so we need to explicitly override this if this
// is a fresh installation
$state = ($ied_plugin_autoenable==1) ? 1 : 0;
if (!$exists) {
if ($fname) {
// Put plugin in cache dir by faking a POST submission and saving
$_POST['filename'] = $fname;
$_POST['newname'] = $name;
$_POST['status'] = 1; // Cache_dir plugins are always on so this setting is ignored
$_POST['type'] = 0;
$_POST['version'] = '0.1';
$_POST['load_order'] = 5;
$_POST['flags'] = 0;
$this->save();
} else {
safe_insert("txp_plugin", "
name='$name',
status=$state,
type=0,
version='0.1',
load_order=5,
flags=0,
description='',
help='',
code='',
code_restore=''
");
$this->edit(gTxt('ied_plugin_edit_new'), $name);
}
} else {
$this->table(array(gTxt('plugin').' <strong>'.$name.'</strong> '.gTxt('already_exists'), E_ERROR));
}
} elseif ($plugin_upload) {
list ($start_css, $end_css) = $this->make_markers("CSS", $this->ied_plugin_globals['css_start'], $this->ied_plugin_globals['css_end']);
if (!$_FILES['thefile']['tmp_name']) {
$this->table(array(gTxt('ied_plugin_choose_file'), E_ERROR));
return;
}
$info = explode ('.',$_FILES['thefile']['name']);
$lastpart = count($info)-1;
$ext = $info[$lastpart];
if ($ext == 'php') {
$plugin = $this->read_file($_FILES['thefile']['tmp_name']);
// $newname = (empty($plugin['name'])) ? basename($_FILES['thefile']['name'], '.php') : $plugin['name'];
$newname = ($name) ? $name : doSlash($plugin['name']);
if (empty($plugin['code'])) {
$plugin['code'] = file_get_contents($_FILES['thefile']['tmp_name']);
}
extract(doSlash($plugin));
$md5 = md5($code);
$version = ($version) ? $version : "0.1";
$help = $help.(($css) ? n.$start_css.n.$css.n.$end_css : '');
$exists = fetch('name', 'txp_plugin', 'name', $newname);
// MySQL defaults to set status=1 so we need to explicitly override this if this
// is a fresh installation
$state = ($ied_plugin_autoenable==1) ? 'status=1,' : ($exists && ($ied_plugin_autoenable==2) ? '' : 'status=0,');
if ($exists) {
// Note: status omitted so it retains its value
$rs = safe_update(
"txp_plugin",
"$state
type = ".intval($type).",
author = '$author',
author_uri = '$author_uri',
version = '$version',
description = '$description',
help = '$help',
code = '$code',
code_restore = '$code',
code_md5 = '$md5',
flags = ".intval($flags).",
load_order = ".intval($load_order),
"name = '$newname'"
);
} else {
$rs = safe_insert(
"txp_plugin",
"name = '$newname',
$state
type = ".intval($type).",
author = '$author',
author_uri = '$author_uri',
version = '$version',
description = '$description',
help = '$help',
code = '$code',
code_restore = '$code',
code_md5 = '$md5',
flags = ".intval($flags).",
load_order = ".intval($load_order)
);
}
$msg = ($exists) ? gTxt('ied_plugin_updated', array('{name}' => $newname)) : gTxt('ied_plugin_uploaded', array('{name}' => $newname));
$this->table($msg, $newname);
} elseif ($ext === 'txt') {
$plugin64 = file_get_contents($_FILES['thefile']['tmp_name']);
$ret = $this->install($plugin64);
if ($ret['err'] == '') {
$this->table($ret['msg'], $ret['nam']);
} else {
$this->table(array($ret['msg'], $ret['err']));
}
}
} elseif ($plugin_install) {
$ret = $this->install(ps('plugin64'));
if ($ret['err'] == '') {
$this->table($ret['msg'], $ret['nam']);
} else {
$this->table(array($ret['msg'], $ret['err']));
}
}
}
/**
* Install plugin from distributable txt file.
*
* Frankensteined shamelessly from include/txp_plugin.php
*
* @param string $plugin The raw base64-encoded/gzipped plugin block
*/
public function install($plugin = '')
{
if (strpos($plugin, '$plugin=\'') !== false) {
@ini_set('pcre.backtrack_limit', '1000000');
$plugin = preg_replace('@.*\$plugin=\'([\w=+/]+)\'.*@s', '$1', $plugin);
}
$plugin = preg_replace('/^#.*$/m', '', $plugin);
if (trim($plugin)) {
$plugin = base64_decode($plugin);
if (strncmp($plugin, "\x1F\x8B", 2) === 0) {
$plugin = gzinflate(substr($plugin, 10));
}
if ($plugin = @unserialize($plugin)) {
if (is_array($plugin)) {
extract($plugin);
$type = empty($type) ? 0 : min(max(intval($type), 0), 5);
$order = empty($order) ? 5 : min(max(intval($order), 1), 9);
$flags = empty($flags) ? 0 : intval($flags);
$exists = fetch('name', 'txp_plugin', 'name', $name);
if (isset($help_raw) && empty($plugin['allow_html_help'])) {
// Default: help is in Textile format.
include_once txpath.'/lib/classTextile.php';
$textile = new Textile();
$help = $textile->TextileRestricted($help_raw, 0, 0);
}
// MySQL defaults to set status=1 so we need to explicitly override this if this
// is a fresh installation.
$ied_plugin_autoenable = ps('ied_plugin_autoenable');
$state = ($ied_plugin_autoenable==1) ? 'status=1,' : ($exists && ($ied_plugin_autoenable==2) ? '' : 'status=0,');
if ($exists) {
$rs = safe_update(
"txp_plugin",
"$state
type = $type,
author = '".doSlash($author)."',
author_uri = '".doSlash($author_uri)."',
version = '".doSlash($version)."',
description = '".doSlash($description)."',
help = '".doSlash($help)."',
code = '".doSlash($code)."',
code_restore = '".doSlash($code)."',
code_md5 = '".doSlash($md5)."',
flags = $flags",
"name = '".doSlash($name)."'"
);
} else {
$rs = safe_insert(
"txp_plugin",
"name = '".doSlash($name)."',
$state
type = $type,
author = '".doSlash($author)."',
author_uri = '".doSlash($author_uri)."',
version = '".doSlash($version)."',
description = '".doSlash($description)."',
help = '".doSlash($help)."',
code = '".doSlash($code)."',
code_restore = '".doSlash($code)."',
code_md5 = '".doSlash($md5)."',
load_order = '".$order."',
flags = $flags"
);
}
if ($rs and $code) {
if (!empty($textpack)) {
install_textpack($textpack, false);
}
if (ps('ied_plugin_installopts') && ($flags & PLUGIN_LIFECYCLE_NOTIFY) ) {
load_plugin($name, true);
$message = callback_event("plugin_lifecycle.$name", 'installed');
}
if (empty($message)) $message = gTxt('plugin_installed', array('{name}' => $name));
return array('msg' => $message, 'err' => '', 'nam' => $name);
} else {
return array('msg' => gTxt('plugin_install_failed', array('{name}' => $name)), 'err' => E_ERROR);
}
}
}
}
return array('msg' => gTxt('bad_plugin_code'), 'err' => E_ERROR);
}
/**
* Store the state of the togglable panes.
*/
public function save_pane_state()
{
$panes = array('ied_plugin_tp_strings', 'ied_plugin_code', 'ied_plugin_docs', 'ied_plugin_meta', 'ied_plugin_utils', 'ied_plugin_cpanel', 'ied_plugin_dbplugs', 'ied_plugin_cacheplugs');
$pane = gps('pane');
if (in_array($pane, $panes)) {
set_pref("pane_{$pane}_visible", (gps('visible') == 'true' ? '1' : '0'), 'ied_plugin', PREF_HIDDEN, 'yesnoradio', 0, PREF_PRIVATE);
send_xml_response();
} else {
send_xml_response(array('http-status' => '400 Bad Request'));
}
}
/**
* Get plugin (file)name information.
*
* Returns an array of (file)names;
* 1) the standard plugin
* 2) the compressed plugin
* 3) the PHP template
* 4) the textpack
*/
public function get_name($name, $version = '', $lang='')
{
$ied_plugin_prefs = $this->get_prefs();
$op = get_pref('ied_plugin_output_sfile');
$opc = get_pref('ied_plugin_output_sfilec');
$opp = get_pref('ied_plugin_output_sfilep');
$opt = get_pref('ied_plugin_output_sfilet');
$out = array(
( (empty($op)) ? $ied_plugin_prefs['ied_plugin_output_sfile']['default'] : $op ),
( (empty($opc)) ? $ied_plugin_prefs['ied_plugin_output_sfilec']['default'] : $opc ),
( (empty($opp)) ? $ied_plugin_prefs['ied_plugin_output_sfilep']['default'] : $opp ),
( (empty($opt)) ? $ied_plugin_prefs['ied_plugin_output_sfilet']['default'] : $opt ),
);
$from = array('{name}', '{version}', '{lang}');
$to = array($name, $version, $lang);
foreach ($out as $fidx => $fname) {
$fname = str_replace($from, $to, $fname);
$out[$fidx] = sanitizeForFile($fname);
}
return $out;
}
/**
* Parse a plugin in Standard Textpattern Template format.
*
* @param string $filepath Full path to the file to be read
*/
public function read_file($filepath)
{
$content = file($filepath);
$justfile = basename($filepath);
$parts = explode ('.',$justfile);
$ext = array_pop($parts);
$ext = ($ext==$justfile) ? '' : '.'.$ext; // Only assign an extension if one exists
$source_lines = count($content);
$commentblock = false;
$in_textpack = false;
$metadata = array(
'name' => '',
'version' => '',
'revision' => '',
'author' => '',
'author_uri' => '',
'description' => '',
'order' => '5',
'type' => '',
'flags' => '',
'textpack' => '',
);
for ($idx=0; $idx < $source_lines; $idx++) {
$content[$idx] = rtrim($content[$idx]);
// Bomb out if we reach the end of the definition area
if (strpos( $content[$idx], 'if (!defined(\'txpinterface\')' ) === 0) {
break;
}
if (strpos($content[$idx], '/*') === 0) {
$commentblock = true;
continue;
}
if ( (strpos($content[$idx], '*/') === 0) || (strpos($content[$idx], '**/') === 0) ) {
$commentblock = false;
continue;
}
if (strpos($content[$idx], 'EOT;') === 0) {
$in_textpack = false;
continue;
}
if ($in_textpack) {
$metadata['textpack'] .= $content[$idx].n;
}
if (!$commentblock && strpos($content[$idx], '$plugin[') === 0) {
// Found a plugin variable so extract it
$parts = explode(" = ", $content[$idx]);
$parts[0] = str_replace("'", "", $parts[0]); // Make the match easier!
$semicolon = strpos($parts[1], ';');
if ($semicolon !== false) {
$parts[1] = substr($parts[1], 0, $semicolon); // Ditto
}
preg_match("/plugin\[(.*)\]/", $parts[0], $var); // Extract just the variable name
if (is_numeric($parts[1])) {
$parts[1] = "'".$parts[1]."'";
}
if (strpos($parts[1], '<<<') === false) {
preg_match("/.*'(.*)'.*/", $parts[1], $val); // Remove anything outside the quotes (e.g. $revision)
if (empty($val)) {
// Try unquoted - may be a constant or two.
preg_match("/(.*)/", $parts[1], $val);
if (strtoupper($val[1]) == $val[1]) {
// It's a constant so get its value
$val[1] = 0;
$constants = do_list($parts[1], '|');
foreach ($constants as $constant) {
$val[1] |= (defined($constant)) ? constant($constant) : 0;
}
}
}
}
if ($var[1] === 'revision' && isset($val[1]) && !empty($val[1])) {
$revparts = explode(' ', trim($val[1], '$ '));
$val[1] = $revparts[count($revparts)-1];
$val[1] = (empty($val[1])) ? '' : '.' .$val[1];
}
if ($var[1] === 'textpack') {
$in_textpack = true;
continue;
}
$metadata[$var[1]] = $val[1];
}
}
$metadata['name'] = (empty($metadata['name'])) ? basename($justfile, $ext) : $metadata['name'];
$metadata['load_order'] = $metadata['order'];
$metadata['version'] .= $metadata['revision'];
$metadata['help'] = $this->extract_hunk($content, 'HELP');
$metadata['css'] = $this->extract_hunk($content, 'CSS' );
$metadata['code'] = $this->extract_hunk($content, 'CODE');
return $metadata;
}
/**
* Pull out a chunk of text from the Standard Template Format.
*
* Ripped and modded from net-carver's zem_tpl template.
*
* @param [type] $content [description]
* @param [type] $hunk [description]
* @param string $cmnt [description]
* @param boolean $delete [description]
* @return [type] [description]
*/
public function extract_hunk($content, $hunk, $cmnt="#", $delete=false)
{
$dlm = explode("|", $cmnt);
$dlmStart = $dlm[0];
$dlmEnd = (isset($dlm[1])) ? $dlm[1] : '';
$lines = $this->make_array($content);
list ($start_delim, $end_delim) = $this->make_markers($hunk, $dlmStart, $dlmEnd);
$start = array_search($start_delim, $lines) + 1;
$end = array_search($end_delim, $lines);
// Kludge to get round the delimiter change in v0.83
if ($hunk == 'CSS' && $end === false) {
$start_delim = str_replace('---', '***', $start_delim);
$end_delim = str_replace('---', '***', $end_delim);
$start = array_search($start_delim, $lines) + 1;
$end = array_search($end_delim, $lines);
}
$extracted = array();
if (count($lines) > 0 && is_numeric($start) && is_numeric($end) && $end > $start) {
$extracted = array_slice($lines, $start, $end-$start);
$lineNum = count($extracted)-1;
while ($lineNum > 0 && trim($extracted[$lineNum--]) == "") {
array_pop($extracted);
}
while (count($extracted) > 0 && trim($extracted[0]) == "") {
array_shift($extracted);
}
if ($delete) {
$chopped = array_splice($lines, $start-1, $end-$start+2, "");
}
}
$remains = $lines;
// Check if the CSS section has <style> tags around it; add them if not
if ($hunk == "CSS") {
$numrows = count($extracted);
if ($numrows > 1) {
if (strpos($extracted[$numrows-1], '</style>') === false) {
$extracted[] = '</style>';
}
if (strpos($extracted[0], '<style ') === false) {
array_unshift($extracted, '<style type="text/css">');
}
}
}
if ($delete) {
return array(trim(implode("\n", $extracted)), trim(implode("\n", $remains)));
} else {
return trim(implode("\n", $extracted));
}
}
/**
* Create a trimmed array from a multi-line document.
*
* @param [type] $arr [description]
* @return [type] [description]
*/
public function make_array($arr)
{
if (!is_array($arr)) {
$arr = explode("\n", $arr);
}
$source_lines = count($arr);
for ($idx=0; $idx < $source_lines; $idx++) {
$arr[$idx] = rtrim($arr[$idx]);
}
return $arr;
}
/**
* Create start and end markers for the given hunk.
*
* @param [type] $hunk [description]
* @param [type] $start [description]
* @param [type] $end [description]
* @return [type] [description]
*/
public function make_markers($hunk, $start, $end)
{
$smarker = $start . str_replace("SECTION", $hunk, $this->ied_plugin_globals['start']) . (($end)?' '.$end:'');
$emarker = $start . str_replace("SECTION", $hunk, $this->ied_plugin_globals['end']) . (($end)?' '.$end:'');
return (array($smarker, $emarker));
}
/**
* Check if the code smells like an admin-side plugin.
*
* Ripped and modded from net-carver's zem_tpl template.
*
* @param string $codeblock The source code
* @param string $type The plugin's type
* @return bool true if the code is a public-side plugin
* false if it seems to be admin-side
*/
public function admin_check($codeblock, $type)
{
// Short circuit since we're only interested in public plugins.
if ($type != '0' || strpos($codeblock, 'txpinterface') === false) {
return true;
}
// This is several orders of magnitude quicker than a single preg_match
// as plugin size increases.
$cb = str_replace(array('\t', ' '), '', $codeblock);
$cb = str_replace('===', '==', $cb);
if (strpos($cb, 'txpinterface==\"admin') ||
strpos($cb, "txpinterface==\'admin") ||
strpos($cb, 'admin\"==txpinterface') ||
strpos($cb, "admin\'==txpinterface") ||
strpos($cb, 'admin\"==@txpinterface') ||
strpos($cb, "admin\'==@txpinterface")) {
return false;
}
return true;
}
/**
* Textile the passed help doc and style info.
*
* Cache the content if the prefs indicate to do so.
*
* @param [type] $name [description]
* @param [type] $help [description]
* @param [type] $css [description]
* @param [type] $start_dlm [description]
* @param [type] $end_dlm [description]
* @return [type] [description]
*/
public function textile($name, $help, $css, $start_dlm, $end_dlm)
{
$tmpdir = $hlpfile = '';
$changed = true;
$out = array();
$iotc = get_pref('ied_plugin_output_tmpcache');
if (isset($iotc) && !empty($iotc) && is_writable($iotc)) {
$tmpdir = $iotc;
$cache = build_file_path($tmpdir, 'ied_plugin_composer.cache');
if (!file_exists($cache)) {
touch($cache); // Create file if it doesn't exist
}
$plugs = parse_ini_file($cache);
$old_md5 = (is_array($plugs) && array_key_exists($name, $plugs)) ? $plugs[$name] : '';
$curr_md5 = md5($help);
$hlpfile = build_file_path($tmpdir, $name.'_help.txtl');
if ($old_md5 == $curr_md5) {
$out[0] = file_get_contents($hlpfile);
$changed = false;
} else {
$plugs[$name] = $curr_md5;
$fd = fopen($cache, "w");
foreach ($plugs as $idx => $val) {
fwrite($fd, $idx.' = '.$val.n);
}
fclose($fd);
}
}
if (!$out) {
@include_once txpath.'/lib/classTextile.php';
$out[0] = $help;
if (class_exists('Textile')) {
// Try and be a little more intelligent about applying textile or not
$re = '/h[0-6](\(.*\))?\./';
if (preg_match($re, $help)) {
$textile = new Textile();
$out[0] = $textile->TextileThis($help);
}
}
}
if ($tmpdir && $changed) {
if (!file_exists($hlpfile)) {
touch($hlpfile);
}
$fd = fopen($hlpfile, "w");
fwrite($fd, $out[0]);
fclose($fd);
}
// Replace the triple '---' in the delimiters so the help file can validate
$start_dlm = str_replace('---', '***', $start_dlm);
$end_dlm = str_replace('---', '***', $end_dlm);
$out[1] = ($css) ? "\n$start_dlm\n$css\n$end_dlm\n" : '';
return substr(implode('', $out), 0, $this->ied_plugin_globals['size_help']+$this->ied_plugin_globals['size_css']);
}
/**
* Inject any necessary javascript on the page for code helpers.
*/
public function insert_editors()
{
$out = array();
$ed = array();
$ced = get_pref('ied_plugin_editor');
$hed = get_pref('ied_plugin_help_editor');
$cop = get_pref('ied_plugin_editor_options');
$hop = get_pref('ied_plugin_help_editor_options');
$ed[$ced] = get_pref('ied_plugin_editor_path');
$ed[$hed] = get_pref('ied_plugin_help_editor_path');
$cop = $cop ? ','.$cop : '';
$hop = $hop ? ','.$hop : '';
foreach ($ed as $editor => $editor_locs) {
$jsop = array();
$locs = do_list($editor_locs);
foreach ($locs as $loc) {
if (strpos($loc, 'css:') !== false) {
$jsop[] = '<link rel="stylesheet" href="'.substr($loc, 4).'">';
} else {
$jsop[] = '<script type="javascript" src="'.$loc.'"></script>';
}
}
$jsop = implode(n, $jsop);
switch ($editor) {
case "tiny_mce":
$out[] = <<<EOJS
{$jsop}
<script type="text/javascript">
tinyMCE.init({
mode : "specific_textareas",
editor_selector : "mceEditor"
{$hop}
});
</script>;
EOJS;
break;
case "edit_area":
$out[] = <<<EOJS
{$jsop}
<script type="text/javascript">
// initialisation
editAreaLoader.init({
id: "plugin_editor",
syntax: "php"
{$cop}
});
</script>
EOJS;
break;
case "codemirror":
$out[] = <<<EOJS
{$jsop}
<script type="text/javascript">
myTextArea = document.getElementById("plugin_editor");
var ied_pc_editor = CodeMirror.fromTextArea(myTextArea, {
value: myTextArea.value
{$cop}
});
</script>
EOJS;
break;
case "codepress":
$out[] = $jsop;
break;
}
}
return implode("\n", $out);
}
/**
* Show plugin help documentation.
*
* If raw, will Textile first.
*
* @param string $message [description]
* @return [type] [description]
*/
public function help_viewer($message = '')
{
if (gps('name')) {
$name = gps('name');
$help = ($name) ? fetch('help','txp_plugin','name',$name) : '';
list($css,$help) = ($help) ? $this->extract_hunk($help, "CSS", "<!--|-->", true) : array('',$help);
} elseif (gps('filename')) {
$plugin = $this->read_file(get_pref('plugin_cache_dir').DS.gps('filename'));
extract($plugin);
}
$out = '';
if (empty($help)) {
$out = gTxt('ied_plugin_help_not_available');
} else {
list ($start_css, $end_css) = $this->make_markers("CSS", $this->ied_plugin_globals['css_start'], $this->ied_plugin_globals['css_end']);
$out = $this->textile($name, $help, $css, $start_css, $end_css);
}
pagetop(gTxt('ied_plugin_view_help', array('{name}' => $name)), $message);
echo n. '<div id="plugin_container" class="txp-container txp-view">'.
n. '<div class="text-column">' . $out . '</div>'.
n. '</div>';
}
/**
* Wrap the passed widget with standard markup.
*
* @param string $widget The widget to wrap
* @param string $class The CSS class to apply to the wrapper
* @return string HTML
*/
public function wrap_widget($widget, $class = 'txp-form-field-value')
{
return '<div class="' . $class . '">'.$widget.'</div>';
}
/**
* Preferences stub with correct signature for lifecycle callback.
*
* @param string $evt Textpattern event
* @param string $stp Textpattern step (action)
*/
public function setup($evt = '', $stp = '')
{
$this->prefs();
}
/**
* Display the setup / prefs panel.
*
* @param string $message Any feedback error / info message to disply
* @return string HTML
*/
public function prefs($message = '')
{
require_privs('plugin_prefs.'.$this->ied_pc_event);
if (ps('ied_plugin_pref_save')) {
$this->prefs_update();
$message = gTxt('preferences_saved');
}
$ied_plugin_prefs = $this->get_prefs();
pagetop(gTxt('ied_plugin_lbl_setup'), $message);
$btnSave = fInput('submit', 'submit', gTxt('save'), 'publish');
echo '<div class="txp-layout">'
.n. '<div class="txp-layout-2col">'
.n. '<h1 class="txp-heading">' . gTxt('ied_plugin_lbl_setup') . '</h1>'
.n. '</div>'
.n. '<div class="txp-layout-2col">'
.n. sLink($this->ied_pc_event, '', gTxt('ied_plugin_list'), 'ied_plugin_link')
.n. '</div>'
.n. script_js(<<<EOJS
var ied_plugin_path_re = new RegExp("^.*[/\\]", "g")
function ied_plugin_prefswap(selID, selValue)
{
var id = selID+'_path';
var nuval = ((basename($("#"+id).val()) == selValue) ? $("#"+id).val() : dirname($("#"+id).val())+selValue);
if ($("#"+selID)[0].selectedIndex == 0) {
$("#"+id).attr("disabled", true);
} else {
$("#"+id).attr("disabled", false);
$("#"+id).val(nuval);
}
}
function basename(path, suffix)
{
return path.replace(ied_plugin_path_re, '');
}
function dirname(path)
{
return path.match(ied_plugin_path_re);
}
jQuery(function () {
jQuery(".ied_plugin_setup select option:selected").each(function (obj) {
var item = jQuery(this);
ied_plugin_prefswap(item.parent().attr('id'), item.val());
});
});
EOJS
);
$out = array();
$out[] = n.'<div class="txp-layout-1col">';
$out[] = '<form name="ied_pc_prefs" id="ied_pc_prefs" class="txp-prefs-group" action="index.php" method="post">';
$out[] = eInput($this->ied_pc_event).sInput('prefs');
$last_grp = '';
foreach ($ied_plugin_prefs as $idx => $prefobj) {
if ($last_grp != $prefobj['group']) {
$out[] = hed(gTxt($prefobj['group']), 2);
}
$last_grp = $prefobj['group'];
$subout = array();
$label = '<div class="txp-form-field-label">'
.'<label>'.gTxt($idx).'</label>'
.'</div>';
$val = get_pref($idx, $prefobj['default'], 1);
$vis = (isset($prefobj['visible']) && !$prefobj['visible']) ? 'empty' : 'txp-form-field';
switch ($prefobj['html']) {
case 'text_input':
$subout[] = $this->wrap_widget(fInput('text', $idx, $val, '', '', '', INPUT_REGULAR, '', $idx));
break;
case 'textarea':
$subout[] = $this->wrap_widget(text_area($idx, '', '', $val, $idx));
$vis = (isset($prefobj['visible']) && !$prefobj['visible']) ? 'empty' : 'txp-form-field-textarea';
break;
case 'yesnoradio':
$subout[] = $this->wrap_widget(yesnoRadio($idx, $val));
break;
case 'radioset':
$subout[] = $this->wrap_widget(radioSet($prefobj['content'], $idx, $val));
break;
case 'checkboxset':
$vals = do_list($val);
$lclout = array();
$cb_count = 0;
foreach ($prefobj['content'] as $cb => $val) {
$checked = in_array($cb, $vals);
$cbId = txpspecialchars($idx.'-'.$cb_count);
$lclout[] = checkbox($idx . '[]', $cb, $checked, '', $cbId) . n . '<label for="' . $cbId . '">' . gTxt($val) . '</label>';
$cb_count++;
}
$subout[] = $this->wrap_widget(implode(n, $lclout));
break;
case 'selectlist':
$subout[] = $this->wrap_widget(selectInput($idx, $prefobj['content'][0], $val, $prefobj['content'][1]));
break;
default:
if (is_callable($prefobj['html'])) {
$subout[] = $this->wrap_widget($prefobj['html']($idx, $val));
}
break;
}
$out[] = tag($label . n.implode(n ,$subout), 'div', ($vis ? ' class="'.$vis.'"' : ''));
}
$out[] = graf(fInput('submit', 'ied_plugin_pref_save', gTxt('save'), 'publish'), array('class' => 'txp-save'));
$out[] = tInput();
$out[] = '</form></div></div>';
echo implode(n, $out);
}
/**
* Save plugin prefs from setup panel.
*/
public function prefs_update()
{
global $prefs;
$ied_plugin_prefs = $this->get_prefs();
$saved = ps('ied_plugin_pref_save');
// Loop through each plugin setting and make sure it's in the prefs table, as follows:
// 1) if the value has been POSTed from the prefs panel, set the passed value
// 2) if the value exists in the prefs (i.e. run on upgrade), set the existing value
// 3) if neither exist, set the plugin default value
foreach ($ied_plugin_prefs as $key => $prefobj) {
$val = ($saved || isset($_POST[$key])) ? ps($key) : ((isset($prefs[$key])) ? $prefs[$key] : $prefobj['default']);
$val = (is_array($val)) ? implode(', ', $val) : $val;
set_pref($key, $val, 'ied_plugin', $prefobj['type'], $prefobj['html'], $prefobj['position']);
}
}
/**
* Delete plugin prefs.
*
* @param integer $showpane Whether to display the panel afterwards
*/
public function prefs_remove($showpane = 1)
{
safe_delete('txp_prefs', "name like 'ied_plugin_%'");
if ($showpane) {
$message = gTxt('ied_plugin_prefs_deleted');
$this->prefs($message);
}
}
/**
* Generate list of available languages.
*
* @param string $flavour If 'installed' then show only those available, else 'all'
* @return array
*/
public static function lang_list($flavour = 'installed')
{
$ied_langs = array();
if ($flavour === 'installed') {
// Self-join to get all the installed langs and language strings in one step.
// $installed_langs = safe_query('select t1.lang, t2.data from '.PFX.'txp_lang as t1, '.PFX.'txp_lang as t2 WHERE t1.lang = t2.name GROUP BY lang');
$ied_langs = safe_column('lang', 'txp_lang', '1=1 GROUP BY lang');
} else {
// Grab all available langs from the RPC server.
require_once txpath.'/lib/IXRClass.php';
$client = new IXR_Client(RPC_SERVER);
// Get items from RPC.
@set_time_limit(5);
if ($client->query('tups.listLanguages', get_pref('blog_uid'))) {
$response = $client->getResponse();
foreach ($response as $language) {
$ied_langs[] = $language['language'];
}
}
}
// Build the select list array.
$langlist = array();
foreach ($ied_langs as $ied_lang) {
$langlist[$ied_lang] = gTxt($ied_lang);
}
return $langlist;
}
/**
* Build textpack from strings in the database.
*
* @param string $name Plugin name
* @param integer $force_all Whether to build for the current langue (0) or all installed languages (1)
* @return string The Textpack
*/
public function textpack_build($name, $force_all = 0)
{
$fetch_lang = null;
if ($force_all === 0) {
$fetch_lang = gps('lang');
}
if (!$fetch_lang) {
$fetch_lang = ($force_all === 1)
? implode(',', array_keys(self::lang_list('installed')))
: get_pref('ied_plugin_lang_selected', '');
}
$tpout = array();
if ($fetch_lang) {
$chosen_lang = get_pref('ied_plugin_lang_default', '');
// Guard against situations when the chosen default lang is 'any'.
$dflt_lang = ($chosen_lang === '') ? get_pref('language') : $chosen_lang;
$tp_pfx = unserialize(get_pref('ied_plugin_tp_prefix', '', 1));
$tp_pfx = isset($tp_pfx[$name]) ? $tp_pfx[$name] : '';
$tp_rows = $this->textpack_grab($fetch_lang, $tp_pfx);
if ($tp_rows) {
$ctr = 0;
$prevlang = '';
// Go through all the languages and put the default language at the start of the array.
foreach ($tp_rows as $row) {
// Add the event marker
$theEvent = in_array($row['event'], array('public', 'common')) ? $row['event'] : $tp_pfx;
if ($prevlang != $row['lang']) {
$ctr++;
}
$idx = ($row['lang'] == $dflt_lang) ? 0 : $ctr;
$tplang[$idx][$theEvent][$row['lang']][$row['name']] = $row['data'];
$prevlang = $row['lang'];
$prevevent = $row['event'];
}
// Make sure default language is actually first.
ksort($tplang);
// Build the final textpack array with language markers.
// Note the marker for the default language may (should!) be omitted if the author wants
// the strings to be installed regardless of language on destination server.
// If a specific language is set and the user does not have that language
// installed, the strings would not be inserted.
$prevevent = '';
foreach ($tplang as $idx => $langblock) {
foreach ($langblock as $ev => $codeblock) {
$tpheader = array();
$tpstrings = array();
if ($prevevent != $ev) {
$tpheader[] = '#@' . $ev;
}
foreach ($codeblock as $code => $data) {
if (($idx == 0 && $chosen_lang) || ($idx > 0)) {
$tpheader[] = '#@language ' . $code;
}
foreach ($data as $key => $val) {
// Don't output empty strings.
if ($val) {
$tpstrings[] = $key . ' => ' . $val;
}
}
if ($tpstrings) {
$tpout = array_merge($tpout, $tpheader, $tpstrings);
}
}
}
}
}
}
return implode(n, $tpout);
}
/**
* Read textpack strings with the given prefix from the database.
*
* @param string $lang Language of strings to fetch
* @param string $prefix Prefix to find
* @return array Record set of matching strings
*/
public function textpack_grab($lang, $prefix)
{
if ($lang === 'IED_ALL') {
$lang_query = '';
} else {
$lang = (empty($lang)) ? get_pref('language', 'en-gb') : $lang;
$langs = quote_list(do_list($lang));
$lang_query = "lang IN (".implode(', ', $langs).") AND ";
}
return ($prefix) ? safe_rows('name, data, lang, event', 'txp_lang', $lang_query."name LIKE '".doSlash($prefix)."%' ORDER BY event,lang,name") : array();
}
/**
* Set the current Textpack language from which to display strings in the interface.
*
* Requires POST variable:
* param string ied_tp_langsel The language name to set
*/
public function lang_set()
{
$sel = doSlash(gps('ied_tp_langsel'));
set_pref('ied_plugin_lang_selected', $sel, 'ied_plugin', PREF_HIDDEN, 'text_input', 0, PREF_PRIVATE);
}
/**
* Store the plugin textpack prefix.
*
* @param string $plugname Plugin name to store (if omitted, tries GET/POST for 'plugin')
* @param string $pfx Prefix to store (if omitted, tries GET/POST for 'prefix')
*/
public function set_tp_prefix($plugname = '', $pfx = '')
{
$plugname = ($plugname) ? $plugname : gps('plugin');
$pfx = ($pfx) ? $pfx : gps('prefix');
if ($pfx) {
$curr_pfx = unserialize(get_pref('ied_plugin_tp_prefix'));
$curr_pfx[$plugname] = $pfx;
set_pref('ied_plugin_tp_prefix', serialize($curr_pfx), 'ied_plugin', PREF_HIDDEN, 'text_input');
}
}
/**
* AJAX: Return a Textpack string from a (type 4 or 5) plugin gTxt() function/method.
*
* Given a function name (usually 'abc_plugin_gTxt') it calls the function
* to fetch the given string from the plugin. By calling this repeatedly
* or each string, an entire Textpack can be built from strings that were
* previously hard-coded in old plugins.
*
* Note the destination plugin MUST be of type 4 or 5 in order for the
* function to be callable so you may have to (temporarily) alter the
* plugin type before using this feature.
*
* Requires POST variables:
* param string ied_tp_fn The function to call
* param string ied_tp_lbl The language label (key)
* @return array JSON structure containing the string's value
* @todo sanitize $fn
*/
public function textpack_load()
{
$fn = doSlash(gps('ied_tp_fn'));
$lbl = doSlash(gps('ied_tp_lbl'));
$ret = '';
$fnparts = do_list($fn, '::');
if (count($fnparts) == 2) {
// Callable class -> method
$fobj = array($fnparts[0], $fnparts[1]);
} else {
$fobj = $fnparts[0];
}
if (is_callable($fobj)) {
$ret = call_user_func_array($fobj, array($lbl));
}
if ($ret) {
echo json_encode(array('ied_plugin_tp_string' => $ret));
}
}
/**
* AJAX: Delete a Textpack string from the database.
*
* Requires POST variable:
* param string ied_tp_lbl The language label (key) to delete
*/
public function textpack_del()
{
$lbl = doSlash(gps('ied_tp_lbl'));
$ret = safe_delete('txp_lang', "name='$lbl'");
}
/**
* AJAX: save a Textpack string to the database.
*
* Requires POST variables:
* param string ied_tp_lbl The language label (key)
* param string ied_tp_str The data value to store
* param string ied_tp_lng Desired language of string
* param string ied_tp_evt Event (group) that the string belongs in
*/
public function textpack_save()
{
global $DB;
$lbl = doSlash(gps('ied_tp_lbl'));
$str = doSlash(gps('ied_tp_str'));
$lng = doSlash(gps('ied_tp_lng'));
$evt = doSlash(gps('ied_tp_evt'));
$own = doSlash(gps('ied_tp_own'));
$where = "name='$lbl' AND lang='$lng'";
$ret = safe_update('txp_lang', "data='$str', event='$evt', owner='$own'", $where);
if ($ret && (mysqli_affected_rows($DB->link) || safe_count('txp_lang', $where))) {
// Update OK: do nothing else.
} else {
$ret = safe_insert('txp_lang', "name='$lbl', lang='$lng', event='$evt', owner='$own', data='$str'");
}
}
/**
* AJAX: Fetch a Textpack string from the database.
*
* Requires POST variables:
* param string ied_tp_lbl The language label (key)
* param string ied_tp_lng Language of string to fetch
* param string ied_tp_dflt Default language to fetch string if main lang missing
* @return array JSON response
*/
public function textpack_get()
{
$lbl = doSlash(gps('ied_tp_lbl'));
$lng = doSlash(gps('ied_tp_lng'));
$dflt = doSlash(gps('ied_tp_dflt'));
$rs = safe_rows('lang, data', 'txp_lang', "name='$lbl' AND (lang='$lng' OR lang='$dflt')");
$out = array();
foreach ($rs as $row) {
if (($row['lang'] == $dflt) && ($lng != $dflt)) {
$out['ied_plugin_tp_dflt'] = $row['data'];
} else {
$out['ied_plugin_tp_string'] = $row['data'];
}
}
echo json_encode($out);
}
/**
* AJAX: Save just the plugin code, optionally checking syntax.
*
* Requires POST variables:
* param string plugin Name of plugin to save against
* param string codeblock PHP code
* @return array XML response
*/
public function code_save()
{
global $theme;
$syntax_check = get_pref('ied_plugin_syntax_check');
$plug = doSlash(ps('plugin'));
$code = ps('codeblock');
$ret = ($syntax_check) ? $this->check_syntax_err($code) : false;
$msg = '';
if ($ret === false) {
$ret = @safe_update('txp_plugin', "code='".doSlash($code)."'", "name='$plug'");
if ($ret) {
$msg = $theme->announce_async(gTxt('ied_plugin_code_saved'));
} else {
$msg = $theme->announce_async(array(gTxt('ied_plugin_code_saved_fail'), E_ERROR));
}
send_xml_response(array('ied_plugin_msg' => $msg));
} else {
$msg = $theme->announce_async(array(htmlentities($ret[0], ENT_QUOTES), E_ERROR));
send_xml_response(array('http-status' => '412 Precondition Failed', 'ied_plugin_msg' => $msg, 'ied_plugin_err_line' => $ret[1]));
}
}
/**
* AJAX: Save just the plugin metadata.
*
* Requires POST variables:
* param string plugin Name of plugin to save against
* param array data Name-value pairs to store
* @return array XML response
*/
public function meta_save() {
global $theme;
$ied_plugin_prefs = $this->get_prefs();
$plug = doSlash(ps('plugin'));
$data = ps('data');
$msg = '';
$meta = array();
// Defaults required because HTML forms don't post unchecked items.
$set = array(
'flags' => "flags='0'",
'status' => "status='0'",
);
foreach ($data as $key => $value) {
switch ($key) {
case 'description':
case 'version':
case 'author':
case 'author_uri':
case 'type':
case 'load_order':
$set[$key] = $key . "='".doSlash($value)."'";
break;
case 'newname':
$val = doSlash($value);
$set['name'] = "name='".$val."'";
$meta['name'] = $val;
break;
case 'status':
case 'flags':
$part = array();
foreach ($value as $item) {
if (!isset($part[$key])) {
$part[$key] = '';
}
$part[$key] += $item;
}
foreach ($part as $idx => $val) {
$set[$idx] = $idx . "='" . doSlash($val)."'";
}
break;
}
}
$update = implode(',', $set);
$ret = safe_update('txp_plugin', $update, "name='$plug'");
if ($ret) {
$msg = $theme->announce_async(gTxt('ied_plugin_meta_saved'));
} else {
$msg = $theme->announce_async(array(gTxt('ied_plugin_meta_saved_fail'), E_ERROR));
}
send_xml_response(array('ied_plugin_msg' => $msg));
}
/**
* Check the syntax of some PHP code.
*
* Mostly from a comment in http://php.net/manual/en/function.php-check-syntax.php
*
* @param string $code PHP code to check.
* @return boolean|array If false, then check was successful, otherwise an array(message,line) of errors is returned.
*/
public function check_syntax_err($code)
{
if (!defined('CR')) define('CR',chr(13));
if (!defined('LF')) define('LF',chr(10));
$braces=0;
$inString=0;
foreach (token_get_all('<?php ' . $code) as $token) {
if (is_array($token)) {
switch ($token[0]) {
case T_CURLY_OPEN:
case T_DOLLAR_OPEN_CURLY_BRACES:
case T_START_HEREDOC: ++$inString; break;
case T_END_HEREDOC: --$inString; break;
}
} elseif ($inString & 1) {
switch ($token) {
case '`': case '\'':
case '"': --$inString; break;
}
} else {
switch ($token) {
case '`': case '\'':
case '"': ++$inString; break;
case '{': ++$braces; break;
case '}':
if ($inString) {
--$inString;
} else {
--$braces;
if ($braces < 0) break 2;
}
break;
}
}
}
$inString = @ini_set('log_errors', false);
$token = @ini_set('display_errors', true);
ob_start();
$braces || $code = "if (0) {{$code}\n}";
if (eval($code) === false) {
if ($braces) {
$braces = PHP_INT_MAX;
} else {
false !== strpos($code,CR) && $code = str_replace(CR,LF,str_replace(CRLF,LF,$code));
$braces = substr_count($code,LF);
}
$code = ob_get_clean();
$code = strip_tags($code);
if (preg_match("'syntax error, (.+) in .+ on line (+)$'s", $code, $code)) {
$code[2] = (int) $code[2];
$code = $code[2] <= $braces
? array($code[1], $code[2])
: array('unexpected $end' . substr($code[1], 14), $braces);
} else $code = array('syntax error', 0);
} else {
ob_end_clean();
$code = false;
}
@ini_set('display_errors', $token);
@ini_set('log_errors', $inString);
return $code;
}
// Reflection utility for phpdoc generation
public function reflunction_factory($callback)
{
if (is_array($callback)) {
// must be a class method
list($class, $method) = $callback;
return new ReflectionMethod($class, $method);
}
// class::method syntax
if (is_string($callback) && strpos($callback, "::") !== false) {
list($class, $method) = explode("::", $callback);
return new ReflectionMethod($class, $method);
}
// objects as functions (PHP 5.3+)
if (version_compare(PHP_VERSION, "5.3.0", ">=") && method_exists($callback, "__invoke")) {
return new ReflectionMethod($callback, "__invoke");
}
// assume it's a function
return new ReflectionFunction($callback);
}
public function generate_phpdoc()
{
$plug = gps('plugin');
$obj = gps('fn');
$ret = load_plugin($plug);
if ($ret) {
try {
$obj = $this->reflunction_factory($obj);
$name = $obj->getName();
$doc = $obj->getDocComment();
if ($doc) {
//TODO: make list of allowed docblock tags a pref
//TODO: parse existing doc block and append any new vars/params
preg_match_all('/\s*\*\s*\@(abstract|access|author|category|copyright|deprecated|example|final|filesource|global|ignore|internal|license|link|method|name|package|param|property|return|see|since|static|staticvar|subpackage|todo|tutorial|uses|var|version)\s+(\w+)\s+(\$\w+(?::\w+|->\w+)*)\s+(\[(\&|\+|abstract|final|private|protected|public|static)\])*\s+(.*)/', $doc, $matches, PREG_SET_ORDER);
$tags = array();
// Reshuffle the matches to index on [tag][var]
foreach ($matches as $idx => $data) {
$ful = isset($data[0]) ? $data[0] : '';
$tag = isset($data[1]) ? $data[1] : '';
$typ = isset($data[2]) ? $data[2] : '';
$var = isset($data[3]) ? $data[3] : '';
$mod = isset($data[4]) ? $data[4] : '';
$tags[$tag][$var] = array(
'type' => $typ,
'full' => $ful,
'mod' => $mod,
);
}
}
$fntype = array('meta' => array(), 'type' => array());
if (method_exists($obj, 'isConstructor')) {
// Dealing with a class method
if ($obj->isConstructor()) {
$fntype['meta'][] = ' [c]';
}
if ($obj->isPublic()) {
$fntype['mod'][] = '[public]';
}
if ($obj->isPrivate()) {
$fntype['mod'][] = '[private]';
}
if ($obj->isProtected()) {
$fntype['mod'][] = '[protected]';
}
if ($obj->isAbstract()) {
$fntype['mod'][] = '[abstract]';
}
if ($obj->isFinal()) {
$fntype['mod'][] = '[final]';
}
if ($obj->isStatic()) {
$fntype['mod'][] = '[static]';
}
}
$params = $obj->getParameters();
$param_list = array();
if ($params != null) {
foreach ($params as $param) {
$has_dflt = false;
$type = 'string';
// Crude type checker
if ($param->isDefaultValueAvailable()) {
$has_dflt = true;
$dflt = $param->getDefaultValue();
if (is_bool($dflt)) {
$type = 'boolean';
$dflt = $dflt ? 'true' : 'false';
} elseif (is_null($dflt)) {
$dflt = 'NULL';
} elseif ($dflt == (string) (int) $dflt) {
$type = 'integer';
} elseif ($dflt == (string) (float) $dflt) {
$type = 'float';
} elseif (is_numeric($dflt)) {
$type = 'number';
} elseif (is_array($dflt)) {
$type = 'array';
}
if ( ($type == 'string' || $type == 'array') && empty($dflt) ) {
$dflt = 'empty';
} elseif ($type == 'string' && $dflt != 'NULL') {
$dflt = '"'.$dflt.'"';
}
}
$item = '@param'.t.$type.t.'$'.$param->getName();
if ($param->isPassedByReference()) {
$item .= ' [&]';
}
if ($param->allowsNull()) {
$item .= ' [+]';
}
$item .= ($has_dflt) ? t.'(Default: ' .$dflt. ')' : t;
$item .= t.'Param description';
$param_list[] = $item;
}
}
$final = '<pre>';
$final .= "/**".br;
$final .= " * $name" . ((!empty($fntype['meta'])) ? $fntype['meta'] : '') .t. (($fnType['mod']) ? implode(' ', $fnType['mod']).' ' : '') . "Description".br;
$final .= " *".br;
$final .= " * Summary goes here".br;
$final .= " *".br;
foreach ($param_list as $item) {
$final .= " * ". $item.br;
}
$final .= " */</pre>".br;
send_xml_response(array('ied_plugin_phpdoc' => $final));
} catch (Exception $ex) {
send_xml_response( array( 'http-status' => '501 Not Implemented', 'error_msg' => $ex->getMessage() . gTxt('ied_plugin_fn_not_exist') ) );
}
} else {
send_xml_response(array('http-status' => '400 Bad Request'));
}
/* $classInterfaces=$reflection->getInterfaces();
//get information about the interfaces
if ($classInterfaces != null) {
fwrite($hf,"\n</i>\t</td></tr>\n\t<tr>".
"<td align=\"center\" colspan=\"0\">".
"<font face=\"arial\" size=\"2\"".
" color=\"purple\">Implemented".
" interfaces:</td><td align=\"center\"".
" colspan=\"0\"><font face=\"arial\"".
" size=\"2\" color=\"black\"><b>Name</b>".
"</font></td><td align=\"center\"".
" colspan=\"3\"><font face=\"arial\"".
" size=\"2\" color=\"black\"><b>".
"Description</b></font></td></tr>\n");
foreach ($classInterfaces as $in) {
fwrite($hf,"\t<tr><td></td><td align=".
"\"center\">");
fwrite($hf,$in->getName());
fwrite($hf,"</td><td align=\"center\"".
" colspan=\"3\"><i>\n");
fwrite($hf,$in->getDocComment());
fwrite($hf,"</i></td></tr>\n");
}
}
//get the superclass information
$superclass=$reflection->getParentClass();
if ($superclass != null) {
fwrite($hf,"\t<tr><td align=\"center\"".
"colspan=\"0\"><font face=\"arial\"".
" size=\"2\" color=\"purple\">".
"The superclass is:</td><td".
" align=\"center\" colspan=\"4\">".
"<font face=\"arial\" size=\"2\"".
" color=\"black\"><b>\n");
fwrite($hf,$superclass->getName());
fwrite($hf,"</font></td></tr>\n");
}
//get information about the constants
$constants=$reflection->getConstants();
if ($constants != null) {
$constantsNumber=count($constants);
fwrite($hf,"\t<tr><td align=\"center\"".
" colspan=\"0\"><font face=\"arial\"".
" size=\"2\" color=\"purple\">".
"Constants:</td><td align=\"center\"".
" colspan=\"0\"><font face=\"arial\"".
" size=\"2\" color=\"black\"><b>Name</b>".
"</font></td><td align=\"center\"".
" colspan=\"3\"><font face=\"arial\"".
" size=\"2\" color=\"black\"><b>Value</b>".
"</font></td></tr>\n");
foreach ($constants as $keys=>$value) {
fwrite($hf,"\t<tr><td></td><td".
" align=\"center\">");
fwrite($hf,$keys);
fwrite($hf,"</td><td align=\"center\"".
" colspan=\"3\">");
fwrite($hf,$value);
fwrite($hf,"</td></tr>\n");
}
}
//get information about properties
$properties=$reflection->getProperties();
if ($properties != null) {
fwrite($hf,"\t<tr><td align=\"center\"".
" colspan=\"0\"><font face=\"arial\"".
" size=\"2\" color=\"purple\">".
"Properties:</td><td align=\"center\"".
" colspan=\"0\"><font face=\"arial\"".
" size=\"2\" color=\"black\"><b>Name</b>".
"</font></td><td align=\"center\"".
" colspan=\"3\"><font face=\"arial\"".
" size=\"2\" color=\"black\"><b>".
"Modifiers</b></font></td></tr>\n");
foreach ($properties as $in) {
fwrite($hf,"\t<tr><td></td><td".
" align=\"center\" colspan=\"0\">");
fwrite($hf,$in->getName());
fwrite($hf,"</td><td align=\"center\"".
" colspan=\"3\">");
if ($in->isPublic()) { fwrite($hf,"[public]"); }
if ($in->isPrivate()) { fwrite($hf,"[private]"); }
if ($in->isProtected()) { fwrite($hf,"[protected]"); }
if ($in->isStatic()) { fwrite($hf,"[static]"); }
fwrite($hf,"</td></tr>\n");
}
}
*/
}
// ------------------------
// Settings for the plugin
public function get_prefs()
{
$tempdir = get_pref('tempdir');
$ied_pc_prefs = array(
'ied_plugin_editor' => array(
'html' => 'ied_plugin_code_editors',
'type' => PREF_HIDDEN,
'position' => 10,
'default' => 'none',
'group' => 'ied_plugin_if_settings',
),
'ied_plugin_editor_path' => array(
'html' => 'text_input',
'type' => PREF_HIDDEN,
'position' => 20,
'default' => hu.'js/',
'group' => 'ied_plugin_if_settings',
),
'ied_plugin_editor_options' => array(
'html' => 'textarea',
'type' => PREF_HIDDEN,
'position' => 30,
'default' => '',
'group' => 'ied_plugin_if_settings',
),
'ied_plugin_editor_width' => array(
'html' => 'text_input',
'type' => PREF_HIDDEN,
'position' => 40,
'default' => '90%',
'group' => 'ied_plugin_if_settings',
),
'ied_plugin_help_editor' => array(
'html' => 'ied_plugin_help_editors',
'type' => PREF_HIDDEN,
'position' => 50,
'default' => 'textile',
'group' => 'ied_plugin_if_settings',
),
'ied_plugin_help_editor_path' => array(
'html' => 'text_input',
'type' => PREF_HIDDEN,
'position' => 60,
'default' => hu.'js/',
'group' => 'ied_plugin_if_settings',
),
'ied_plugin_help_editor_options' => array(
'html' => 'textarea',
'type' => PREF_HIDDEN,
'position' => 70,
'default' => 'theme_advanced_toolbar_location : "top",
theme_advanced_buttons1 : "bold,italic,underline,strikethrough,forecolor,backcolor,removeformat,numlist,bullist,outdent,indent,justifyleft,justifycenter,justifyright,justifyfull",
theme_advanced_buttons2 : "link,unlink,separator,ibrowser,separator,search,replace,separator,cut,copy,paste,separator,code,separator,formatselect",
theme_advanced_buttons3 : ""',
'group' => 'ied_plugin_if_settings',
),
'ied_plugin_interface_elems' => array(
'html' => 'checkboxset',
'type' => PREF_HIDDEN,
'position' => 80,
'content' => array('distribution' => 'ied_plugin_if_el_dist', 'style' => 'ied_plugin_if_el_style'),
'default' => 'style',
'group' => 'ied_plugin_if_settings',
),
'ied_plugin_lifecycle_options' => array(
'html' => 'checkboxset',
'type' => PREF_HIDDEN,
'position' => 90,
'content' => array('installed' => 'ied_plugin_lbl_lc_install', 'enabled' => 'ied_plugin_lbl_lc_enable', 'disabled' => 'ied_plugin_lbl_lc_disable', 'deleted' => 'ied_plugin_lbl_lc_delete'),
'default' => '',
'group' => 'ied_plugin_prefs',
),
'ied_plugin_auto_enable' => array(
'html' => 'radioset',
'type' => PREF_HIDDEN,
'position' => 100,
'content' => array('0' => gTxt('no'), '1' => gTxt('yes'), '2' => gTxt('ied_plugin_same')),
'default' => '2',
'group' => 'ied_plugin_prefs',
),
'ied_plugin_syntax_check' => array(
'html' => 'yesnoradio',
'type' => PREF_HIDDEN,
'position' => 110,
'default' => '1',
'group' => 'ied_plugin_prefs',
),
'ied_plugin_lang_choose' => array(
'html' => 'ied_plugin_lang_options',
'type' => PREF_HIDDEN,
'position' => 120,
'default' => 'installed',
'group' => 'ied_plugin_prefs',
),
'ied_plugin_lang_default' => array(
'html' => 'ied_plugin_lang_default',
'type' => PREF_HIDDEN,
'position' => 130,
'default' => '',
'group' => 'ied_plugin_prefs',
),
'ied_plugin_output_order' => array(
'html' => 'radioset',
'type' => PREF_HIDDEN,
'position' => 140,
'content' => array('0' => gTxt('ied_plugin_lbl_op_code_first'), '1' => gTxt('ied_plugin_lbl_op_help_first')),
'default' => '0',
'group' => 'ied_plugin_prefs',
),
'ied_plugin_output_sfile' => array(
'html' => 'text_input',
'type' => PREF_HIDDEN,
'position' => 150,
'default' => '{name}_v{version}.txt',
'group' => 'ied_plugin_prefs',
),
'ied_plugin_output_sfilec' => array(
'html' => 'text_input',
'type' => PREF_HIDDEN,
'position' => 160,
'default' => '{name}_v{version}_zip.txt',
'group' => 'ied_plugin_prefs',
),
'ied_plugin_output_sfilep' => array(
'html' => 'text_input',
'type' => PREF_HIDDEN,
'position' => 170,
'default' => '{name}_v{version}.php',
'group' => 'ied_plugin_prefs',
),
'ied_plugin_output_sfilet' => array(
'html' => 'text_input',
'type' => PREF_HIDDEN,
'position' => 180,
'default' => '{name}_v{version}_{lang}_textpack.txt',
'group' => 'ied_plugin_prefs',
),
'ied_plugin_output_tmpcache' => array(
'html' => 'text_input',
'type' => PREF_HIDDEN,
'position' => 190,
'default' => $tempdir,
'group' => 'ied_plugin_prefs',
),
);
return $ied_pc_prefs;
}
}
/**
* Add prefs callbacks to global scope, since create_pref() can't take
* array/object syntax.
*
* @see http://forum.textpattern.com/viewtopic.php?pid=298188#p298188
*/
if (txpinterface === 'admin') {
/**
* List of supported javascript syntax highlighter / code editors.
*
* NB: no i18n since these are the names of the projects.
*
* @param string $name Preference name
* @param string $val Current preference value
* @return string HTML
*/
function ied_plugin_code_editors($name, $val = '')
{
$eds['none'] = gTxt('none');
$eds['edit_area'] = 'EditArea';
$eds['codemirror'] = 'CodeMirror';
$eds['codepress'] = 'CodePress';
return selectInput($name, $eds, $val, false);
}
/**
* List of supported javascript help editors.
*
* NB: no i18n since these are the names of the projects.
*
* @param string $name Preference name
* @param string $val Current preference value
* @return string HTML
*/
function ied_plugin_help_editors($name, $val = '')
{
$eds['textilee'] = 'Textile';
$eds['tiny_mce'] = 'TinyMCE';
return selectInput($name, $eds, $val, false);
}
/**
* List of language options.
*
* @param string $name Preference name
* @param string $val Current preference value
* @return string HTML
*/
function ied_plugin_lang_options($name, $val = '')
{
$lngs['installed'] = gTxt('ied_plugin_langs_installed');
$lngs['all'] = gTxt('ied_plugin_langs_all');
return selectInput($name, $lngs, $val, false);
}
/**
* Complete list of supported languages.
*
* @param string $name Preference name
* @param string $val Current preference value
* @return string HTML
*/
function ied_plugin_lang_default($name, $val = '')
{
$langs = array_merge(array('' => gTxt('ied_plugin_any')), ied_pc::lang_list('all'));
return selectInput($name, $langs, $val, false);
}
}