Version Description
- Initial release on WordPress.org.
Download this release
Release Info
Developer | soflyy |
Plugin | Import any XML or CSV File to WordPress |
Version | 2.12 |
Comparing to | |
See all releases |
Version 2.12
- actions/admin_menu.php +22 -0
- actions/admin_notices.php +28 -0
- actions/delete_post.php +6 -0
- actions/wp_loaded.php +9 -0
- classes/config.php +91 -0
- classes/helper.php +136 -0
- classes/input.php +72 -0
- config/options.php +17 -0
- controllers/admin/help.php +12 -0
- controllers/admin/home.php +12 -0
- controllers/admin/import.php +1305 -0
- controllers/admin/manage.php +108 -0
- controllers/admin/settings.php +56 -0
- controllers/controller.php +102 -0
- controllers/controller/admin.php +91 -0
- helpers/backward.php +40 -0
- helpers/get_taxonomies_by_object_type.php +25 -0
- helpers/str_getcsv.php +17 -0
- helpers/wp_delete_attachments.php +10 -0
- helpers/wp_redirect_or_javascript.php +17 -0
- libraries/XmlImportConfig.php +88 -0
- libraries/XmlImportCsvParse.php +2427 -0
- libraries/XmlImportException.php +10 -0
- libraries/XmlImportParser.php +106 -0
- libraries/XmlImportReaderInterface.php +21 -0
- libraries/XmlImportStringReader.php +67 -0
- libraries/XmlImportTemplate.php +78 -0
- libraries/XmlImportTemplateCodeGenerator.php +341 -0
- libraries/XmlImportTemplateParser.php +393 -0
- libraries/XmlImportTemplateScanner.php +403 -0
- libraries/XmlImportToken.php +167 -0
- libraries/ast/XmlImportAstElseif.php +76 -0
- libraries/ast/XmlImportAstExpression.php +15 -0
- libraries/ast/XmlImportAstFloat.php +15 -0
- libraries/ast/XmlImportAstForeach.php +14 -0
- libraries/ast/XmlImportAstFunction.php +90 -0
- libraries/ast/XmlImportAstIf.php +161 -0
- libraries/ast/XmlImportAstInteger.php +15 -0
- libraries/ast/XmlImportAstLiteral.php +52 -0
- libraries/ast/XmlImportAstMath.php +73 -0
- libraries/ast/XmlImportAstPrint.php +60 -0
- libraries/ast/XmlImportAstSequence.php +161 -0
- libraries/ast/XmlImportAstStatement.php +15 -0
- libraries/ast/XmlImportAstString.php +15 -0
- libraries/ast/XmlImportAstText.php +59 -0
- libraries/ast/XmlImportAstWith.php +15 -0
- libraries/ast/XmlImportAstXPath.php +15 -0
- libraries/ast/XmlImportAstXpathClause.php +83 -0
- models/file/list.php +32 -0
- models/file/record.php +78 -0
- models/import/list.php +8 -0
- models/import/record.php +728 -0
- models/model.php +198 -0
- models/model/list.php +149 -0
- models/model/record.php +176 -0
- models/post/list.php +10 -0
- models/post/record.php +15 -0
- models/template/list.php +8 -0
- models/template/record.php +13 -0
- plugin.php +479 -0
- readme.txt +69 -0
- schema.php +64 -0
- screenshot-1.png +0 -0
- screenshot-2.png +0 -0
- screenshot-3.png +0 -0
- screenshot-4.png +0 -0
- static/css/admin-ie.css +13 -0
- static/css/admin.css +469 -0
- static/img/date-picker.gif +0 -0
- static/img/down.gif +0 -0
- static/img/help.png +0 -0
- static/img/loading.gif +0 -0
- static/img/screen-options-right-up.gif +0 -0
- static/img/screen-options-right.gif +0 -0
- static/img/xmlicon.png +0 -0
- static/js/admin.js +421 -0
- static/js/jquery/css/smoothness/images/tipsy.gif +0 -0
- static/js/jquery/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- static/js/jquery/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- static/js/jquery/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- static/js/jquery/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- static/js/jquery/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- static/js/jquery/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- static/js/jquery/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- static/js/jquery/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- static/js/jquery/css/smoothness/images/ui-icons_222222_256x240.png +0 -0
- static/js/jquery/css/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
- static/js/jquery/css/smoothness/images/ui-icons_454545_256x240.png +0 -0
- static/js/jquery/css/smoothness/images/ui-icons_888888_256x240.png +0 -0
- static/js/jquery/css/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
- static/js/jquery/css/smoothness/jquery-ui.css +405 -0
- static/js/jquery/css/smoothness/jquery.tipsy.css +11 -0
- static/js/jquery/jquery.tipsy.js +198 -0
- static/js/jquery/ui.autocomplete.js +606 -0
- static/js/jquery/ui.datepicker.js +1636 -0
- static/js/pmxi.js +14 -0
- views/admin/help/index.php +2 -0
- views/admin/home/index.php +16 -0
- views/admin/import/element.php +1 -0
- views/admin/import/element_after.php +41 -0
- views/admin/import/error.php +3 -0
- views/admin/import/evaluate.php +15 -0
- views/admin/import/index.php +106 -0
- views/admin/import/options.php +309 -0
- views/admin/import/preview.php +12 -0
- views/admin/import/process-complete.php +0 -0
- views/admin/import/process.php +6 -0
- views/admin/import/tag.php +12 -0
- views/admin/import/template.php +81 -0
- views/admin/manage/bulk.php +18 -0
- views/admin/manage/delete.php +12 -0
- views/admin/manage/index.php +198 -0
- views/admin/manage/update.php +26 -0
- views/admin/settings/index.php +49 -0
- views/controller/error.php +3 -0
actions/admin_menu.php
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Register plugin specific admin menu
|
4 |
+
*/
|
5 |
+
|
6 |
+
function pmxi_admin_menu() {
|
7 |
+
global $menu, $submenu;
|
8 |
+
|
9 |
+
if (current_user_can('manage_options')) { // admin management options
|
10 |
+
|
11 |
+
add_menu_page(__('WP All Import', 'pmxi_plugin'), __('All Import', 'pmxi_plugin'), 'manage_options', 'pmxi-admin-home', array(PMXI_Plugin::getInstance(), 'adminDispatcher'), PMXI_Plugin::ROOT_URL . '/static/img/xmlicon.png');
|
12 |
+
// workaround to rename 1st option to `Home`
|
13 |
+
$submenu['pmxi-admin-home'] = array();
|
14 |
+
add_submenu_page('pmxi-admin-home', __('Import XML', 'pmxi_plugin') . ' ‹ ' . __('WP All Import', 'pmxi_plugin'), __('New Import', 'pmxi_plugin'), 'manage_options', 'pmxi-admin-import', array(PMXI_Plugin::getInstance(), 'adminDispatcher'));
|
15 |
+
add_submenu_page('pmxi-admin-home', __('Manage Previous Imports', 'pmxi_plugin') . ' ‹ ' . __('WP All Import', 'pmxi_plugin'), __('Manage Imports', 'pmxi_plugin'), 'manage_options', 'pmxi-admin-manage', array(PMXI_Plugin::getInstance(), 'adminDispatcher'));
|
16 |
+
add_submenu_page('pmxi-admin-home', __('Settings', 'pmxi_plugin') . ' ‹ ' . __('WP All Import', 'pmxi_plugin'), __('Settings', 'pmxi_plugin'), 'manage_options', 'pmxi-admin-settings', array(PMXI_Plugin::getInstance(), 'adminDispatcher'));
|
17 |
+
//add_submenu_page('pmxi-admin-home', __('WP All Import', 'pmxi_plugin'), __('About', 'pmxi_plugin'), 'manage_options', 'pmxi-admin-home', array(PMXI_Plugin::getInstance(), 'adminDispatcher'));
|
18 |
+
// add_submenu_page('pmxi-admin-home', __('Help', 'pmxi_plugin') . ' ‹ ' . __('WP All Import', 'pmxi_plugin'), __('Help', 'pmxi_plugin'), 'manage_options', 'pmxi-admin-help', array(PMXI_Plugin::getInstance(), 'adminDispatcher'));
|
19 |
+
|
20 |
+
}
|
21 |
+
}
|
22 |
+
|
actions/admin_notices.php
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
function pmxi_admin_notices() {
|
4 |
+
// notify user if history folder is not writable
|
5 |
+
if ( ! is_dir(PMXI_Plugin::ROOT_DIR . '/history') or ! is_writable(PMXI_Plugin::ROOT_DIR . '/history')) {
|
6 |
+
?>
|
7 |
+
<div class="error"><p>
|
8 |
+
<?php printf(
|
9 |
+
__('<b>%s Plugin</b>: History folder %s must be writable for the plugin to function properly. Please deactivate the plugin, set proper permissions to the folder and activate the plugin again.', 'pmxi_plugin'),
|
10 |
+
PMXI_Plugin::getInstance()->getName(),
|
11 |
+
PMXI_Plugin::ROOT_DIR . '/history'
|
12 |
+
) ?>
|
13 |
+
</p></div>
|
14 |
+
<?php
|
15 |
+
}
|
16 |
+
|
17 |
+
$input = new PMXI_Input();
|
18 |
+
$messages = $input->get('pmxi_nt', array());
|
19 |
+
if ($messages) {
|
20 |
+
is_array($messages) or $messages = array($messages);
|
21 |
+
foreach ($messages as $type => $m) {
|
22 |
+
in_array((string)$type, array('updated', 'error')) or $type = 'updated';
|
23 |
+
?>
|
24 |
+
<div class="<?php echo $type ?>"><p><?php echo $m ?></p></div>
|
25 |
+
<?php
|
26 |
+
}
|
27 |
+
}
|
28 |
+
}
|
actions/delete_post.php
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
function pmxi_delete_post($post_id) {
|
4 |
+
$post = new PMXI_Post_Record();
|
5 |
+
$post->get_by_post_id($post_id)->isEmpty() or $post->delete();
|
6 |
+
}
|
actions/wp_loaded.php
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
function pmxi_wp_loaded() {
|
4 |
+
|
5 |
+
ini_set("max_input_time", PMXI_Plugin::getInstance()->getOption('max_input_time'));
|
6 |
+
ini_set("max_execution_time", PMXI_Plugin::getInstance()->getOption('max_execution_time'));
|
7 |
+
|
8 |
+
wp_enqueue_script('pmxi-script', PMXI_Plugin::getInstance()->getRelativePath() . '/static/js/pmxi.js', array('jquery'));
|
9 |
+
}
|
classes/config.php
ADDED
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Class to load config files
|
4 |
+
*
|
5 |
+
* @author Pavel Kulbakin <p.kulbakin@gmail.com>
|
6 |
+
*/
|
7 |
+
class PMXI_Config implements IteratorAggregate {
|
8 |
+
/**
|
9 |
+
* Config variables stored
|
10 |
+
* @var array
|
11 |
+
*/
|
12 |
+
protected $config = array();
|
13 |
+
/**
|
14 |
+
* List of loaded files in order to avoid loading same file several times
|
15 |
+
* @var array
|
16 |
+
*/
|
17 |
+
protected $loaded = array();
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Static method to create config instance from file on disc
|
21 |
+
* @param string $filePath
|
22 |
+
* @param string[optional] $section
|
23 |
+
* @return PMXI_Config
|
24 |
+
*/
|
25 |
+
public static function createFromFile($filePath, $section = NULL) {
|
26 |
+
$config = new self();
|
27 |
+
return $config->loadFromFile($filePath, $section);
|
28 |
+
}
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Load config file
|
32 |
+
* @param string $filePath
|
33 |
+
* @param string[optional] $section
|
34 |
+
* @return PMXI_Config
|
35 |
+
*/
|
36 |
+
public function loadFromFile($filePath, $section = NULL) {
|
37 |
+
if ( ! is_null($section)) {
|
38 |
+
$this->config[$section] = self::createFromFile($filePath);
|
39 |
+
} else {
|
40 |
+
$filePath = realpath($filePath);
|
41 |
+
if ($filePath and ! in_array($filePath, $this->loaded)) {
|
42 |
+
require $filePath;
|
43 |
+
|
44 |
+
$sandbox = create_function('', "require '$filePath'; if(array_keys(get_defined_vars()) != array('config')) return array(); return \$config;");
|
45 |
+
$config = $sandbox();
|
46 |
+
$this->loaded[] = $filePath;
|
47 |
+
$this->config = array_merge($this->config, $config);
|
48 |
+
}
|
49 |
+
}
|
50 |
+
return $this;
|
51 |
+
}
|
52 |
+
/**
|
53 |
+
* Return value of setting with specified name
|
54 |
+
* @param string $field Setting name
|
55 |
+
* @param string[optional] $section Section name to look setting in
|
56 |
+
* @return mixed
|
57 |
+
*/
|
58 |
+
public function get($field, $section = NULL) {
|
59 |
+
return ! is_null($section) ? $this->config[$section]->get($field) : $this->config[$field];
|
60 |
+
}
|
61 |
+
|
62 |
+
/**
|
63 |
+
* Magic method for checking whether some config option are set
|
64 |
+
* @param string $field
|
65 |
+
* @return bool
|
66 |
+
*/
|
67 |
+
public function __isset($field) {
|
68 |
+
return isset($this->config[$field]);
|
69 |
+
}
|
70 |
+
/**
|
71 |
+
* Magic method to implement object-like access to config parameters
|
72 |
+
* @param string $field
|
73 |
+
* @return mixed
|
74 |
+
*/
|
75 |
+
public function __get($field) {
|
76 |
+
return $this->config[$field];
|
77 |
+
}
|
78 |
+
|
79 |
+
/**
|
80 |
+
* Return all config options as array
|
81 |
+
* @return array
|
82 |
+
*/
|
83 |
+
public function toArray($section = NULL) {
|
84 |
+
return ! is_null($section) ? $this->config[$section]->toArray() : $this->config;
|
85 |
+
}
|
86 |
+
|
87 |
+
public function getIterator() {
|
88 |
+
return new ArrayIterator($this->config);
|
89 |
+
}
|
90 |
+
|
91 |
+
}
|
classes/helper.php
ADDED
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Helper class which defnes a namespace for some commonly used functions
|
4 |
+
*
|
5 |
+
* @author Pavel Kulbakin <p.kulbakin@gmail.com>
|
6 |
+
*/
|
7 |
+
class PMXI_Helper {
|
8 |
+
const GLOB_MARK = 1;
|
9 |
+
const GLOB_NOSORT = 2;
|
10 |
+
const GLOB_ONLYDIR = 4;
|
11 |
+
|
12 |
+
const GLOB_NODIR = 256;
|
13 |
+
const GLOB_PATH = 512;
|
14 |
+
const GLOB_NODOTS = 1024;
|
15 |
+
const GLOB_RECURSE = 2048;
|
16 |
+
|
17 |
+
/**
|
18 |
+
* A safe empowered glob().
|
19 |
+
*
|
20 |
+
* Function glob() is prohibited on some server (probably in safe mode)
|
21 |
+
* (Message "Warning: glob() has been disabled for security reasons in
|
22 |
+
* (script) on line (line)") for security reasons as stated on:
|
23 |
+
* http://seclists.org/fulldisclosure/2005/Sep/0001.html
|
24 |
+
*
|
25 |
+
* safe_glob() intends to replace glob() using readdir() & fnmatch() instead.
|
26 |
+
* Supported flags: self::GLOB_MARK, self::GLOB_NOSORT, self::GLOB_ONLYDIR
|
27 |
+
* Additional flags: self::GLOB_NODIR, self::GLOB_PATH, self::GLOB_NODOTS, self::GLOB_RECURSE
|
28 |
+
* (not original glob() flags)
|
29 |
+
* @author BigueNique AT yahoo DOT ca
|
30 |
+
* @updates
|
31 |
+
* - 080324 Added support for additional flags: self::GLOB_NODIR, self::GLOB_PATH,
|
32 |
+
* self::GLOB_NODOTS, self::GLOB_RECURSE
|
33 |
+
* - 100607 Recurse is_dir check fixed by Pavel Kulbakin <p.kulbakin@gmail.com>
|
34 |
+
*/
|
35 |
+
public static function safe_glob($pattern, $flags=0) {
|
36 |
+
$split = explode('/', str_replace('\\', '/', $pattern));
|
37 |
+
$mask = array_pop($split);
|
38 |
+
$path = implode('/', $split);
|
39 |
+
if (($dir = opendir($path)) !== false) {
|
40 |
+
$glob = array();
|
41 |
+
while(($file = readdir($dir)) !== false) {
|
42 |
+
// Recurse subdirectories (self::GLOB_RECURSE)
|
43 |
+
if (($flags & self::GLOB_RECURSE) && is_dir($path . '/' . $file) && ( ! in_array($file, array('.', '..')))) {
|
44 |
+
$glob = array_merge($glob, self::array_prepend(self::safe_glob($path . '/' . $file . '/' . $mask, $flags), ($flags & self::GLOB_PATH ? '' : $file . '/')));
|
45 |
+
}
|
46 |
+
// Match file mask
|
47 |
+
if (self::fnmatch($mask, $file)) {
|
48 |
+
if ((( ! ($flags & self::GLOB_ONLYDIR)) || is_dir("$path/$file"))
|
49 |
+
&& (( ! ($flags & self::GLOB_NODIR)) || ( ! is_dir($path . '/' . $file)))
|
50 |
+
&& (( ! ($flags & self::GLOB_NODOTS)) || ( ! in_array($file, array('.', '..'))))
|
51 |
+
) {
|
52 |
+
$glob[] = ($flags & self::GLOB_PATH ? $path . '/' : '') . $file . ($flags & self::GLOB_MARK ? '/' : '');
|
53 |
+
}
|
54 |
+
}
|
55 |
+
}
|
56 |
+
closedir($dir);
|
57 |
+
if ( ! ($flags & self::GLOB_NOSORT)) sort($glob);
|
58 |
+
return $glob;
|
59 |
+
} else {
|
60 |
+
return false;
|
61 |
+
}
|
62 |
+
}
|
63 |
+
|
64 |
+
/**
|
65 |
+
* Prepends $string to each element of $array
|
66 |
+
* If $deep is true, will indeed also apply to sub-arrays
|
67 |
+
* @author BigueNique AT yahoo DOT ca
|
68 |
+
* @since 080324
|
69 |
+
*/
|
70 |
+
public static function array_prepend($array, $string, $deep=false) {
|
71 |
+
if(empty($array)||empty($string)) {
|
72 |
+
return $array;
|
73 |
+
}
|
74 |
+
foreach ($array as $key => $element) {
|
75 |
+
if (is_array($element)) {
|
76 |
+
if ($deep) {
|
77 |
+
$array[$key] = self::array_prepend($element,$string,$deep);
|
78 |
+
} else {
|
79 |
+
trigger_error(__METHOD__ . ': array element', E_USER_WARNING);
|
80 |
+
}
|
81 |
+
} else {
|
82 |
+
$array[$key] = $string.$element;
|
83 |
+
}
|
84 |
+
}
|
85 |
+
return $array;
|
86 |
+
|
87 |
+
}
|
88 |
+
|
89 |
+
const FNM_PATHNAME = 1;
|
90 |
+
const FNM_NOESCAPE = 2;
|
91 |
+
const FNM_PERIOD = 4;
|
92 |
+
const FNM_CASEFOLD = 16;
|
93 |
+
|
94 |
+
/**
|
95 |
+
* non-POSIX complient remplacement for the fnmatch
|
96 |
+
*/
|
97 |
+
public static function fnmatch($pattern, $string, $flags = 0) {
|
98 |
+
$modifiers = null;
|
99 |
+
$transforms = array(
|
100 |
+
'\*' => '.*',
|
101 |
+
'\?' => '.',
|
102 |
+
'\[\!' => '[^',
|
103 |
+
'\[' => '[',
|
104 |
+
'\]' => ']',
|
105 |
+
'\.' => '\.',
|
106 |
+
'\\' => '\\\\'
|
107 |
+
);
|
108 |
+
|
109 |
+
// Forward slash in string must be in pattern:
|
110 |
+
if ($flags & FNM_PATHNAME) {
|
111 |
+
$transforms['\*'] = '[^/]*';
|
112 |
+
}
|
113 |
+
|
114 |
+
// Back slash should not be escaped:
|
115 |
+
if ($flags & FNM_NOESCAPE) {
|
116 |
+
unset($transforms['\\']);
|
117 |
+
}
|
118 |
+
|
119 |
+
// Perform case insensitive match:
|
120 |
+
if ($flags & FNM_CASEFOLD) {
|
121 |
+
$modifiers .= 'i';
|
122 |
+
}
|
123 |
+
|
124 |
+
// Period at start must be the same as pattern:
|
125 |
+
if ($flags & FNM_PERIOD) {
|
126 |
+
if (strpos($string, '.') === 0 && strpos($pattern, '.') !== 0) return false;
|
127 |
+
}
|
128 |
+
|
129 |
+
$pattern = '#^'
|
130 |
+
.strtr(preg_quote($pattern, '#'), $transforms)
|
131 |
+
.'$#'
|
132 |
+
.$modifiers;
|
133 |
+
|
134 |
+
return (boolean)preg_match($pattern, $string);
|
135 |
+
}
|
136 |
+
}
|
classes/input.php
ADDED
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
class PMXI_Input {
|
3 |
+
protected $filters = array('stripslashes');
|
4 |
+
|
5 |
+
public function read($inputArray, $paramName, $default = NULL) {
|
6 |
+
if (is_array($paramName) and ! is_null($default)) {
|
7 |
+
throw new Exception('Either array of parameter names with default values as the only argument or param name and default value as seperate arguments are expected.');
|
8 |
+
}
|
9 |
+
if (is_array($paramName)) {
|
10 |
+
foreach ($paramName as $param => $def) {
|
11 |
+
if (isset($inputArray[$param])) {
|
12 |
+
$paramName[$param] = $this->applyFilters($inputArray[$param]);
|
13 |
+
}
|
14 |
+
}
|
15 |
+
return $paramName;
|
16 |
+
} else {
|
17 |
+
return isset($inputArray[$paramName]) ? $this->applyFilters($inputArray[$paramName]) : $default;
|
18 |
+
}
|
19 |
+
}
|
20 |
+
|
21 |
+
public function get($paramName, $default = NULL) {
|
22 |
+
return $this->read($_GET, $paramName, $default);
|
23 |
+
}
|
24 |
+
|
25 |
+
public function post($paramName, $default = NULL) {
|
26 |
+
return $this->read($_POST, $paramName, $default);
|
27 |
+
}
|
28 |
+
|
29 |
+
public function cookie($paramName, $default = NULL) {
|
30 |
+
return $this->read($_COOKIE, $paramName, $default);
|
31 |
+
}
|
32 |
+
|
33 |
+
public function request($paramName, $default = NULL) {
|
34 |
+
return $this->read($_GET + $_POST + $_COOKIE, $paramName, $default);
|
35 |
+
}
|
36 |
+
|
37 |
+
public function getpost($paramName, $default = NULL) {
|
38 |
+
return $this->read($_GET + $_POST, $paramName, $default);
|
39 |
+
}
|
40 |
+
|
41 |
+
public function server($paramName, $default = NULL) {
|
42 |
+
return $this->read($_SERVER, $paramName, $default);
|
43 |
+
}
|
44 |
+
|
45 |
+
public function addFilter($callback) {
|
46 |
+
if ( ! is_callable($callback)) {
|
47 |
+
throw new Exception(get_class($this) . '::' . __METHOD__ . ' parameter must be a proper callback function reference.');
|
48 |
+
}
|
49 |
+
if ( ! in_array($callback, $this->filters)) {
|
50 |
+
$this->filters[] = $callback;
|
51 |
+
}
|
52 |
+
return $this;
|
53 |
+
}
|
54 |
+
|
55 |
+
public function removeFilter($callback) {
|
56 |
+
$this->filters = array_diff($this->filters, array($callback));
|
57 |
+
return $this;
|
58 |
+
}
|
59 |
+
|
60 |
+
protected function applyFilters($val) {
|
61 |
+
if (is_array($val)) {
|
62 |
+
foreach ($val as $k => $v) {
|
63 |
+
$val[$k] = $this->applyFilters($v);
|
64 |
+
}
|
65 |
+
} else {
|
66 |
+
foreach ($this->filters as $filter) {
|
67 |
+
$val = call_user_func($filter, $val);
|
68 |
+
}
|
69 |
+
}
|
70 |
+
return $val;
|
71 |
+
}
|
72 |
+
}
|
config/options.php
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* List of plugin optins, contains only default values, actual values are stored in database
|
4 |
+
* and can be changed by corresponding wordpress function calls
|
5 |
+
*/
|
6 |
+
$config = array(
|
7 |
+
"info_api_url" => "http://www.wpallimport.com/adminpanel/update/info.php",
|
8 |
+
"history_file_count" => 10000,
|
9 |
+
"history_file_age" => 365,
|
10 |
+
"highlight_limit" => 10000,
|
11 |
+
"upload_max_filesize" => 2048,
|
12 |
+
"post_max_size" => 2048,
|
13 |
+
"max_input_time" => -1,
|
14 |
+
"max_execution_time" => -1,
|
15 |
+
"dismiss" => 0,
|
16 |
+
"html_entities" => 0
|
17 |
+
);
|
controllers/admin/help.php
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Admin Help page
|
4 |
+
*
|
5 |
+
* @author Pavel Kulbakin <p.kulbakin@gmail.com>
|
6 |
+
*/
|
7 |
+
class PMXI_Admin_Help extends PMXI_Controller_Admin {
|
8 |
+
|
9 |
+
public function index() {
|
10 |
+
$this->render();
|
11 |
+
}
|
12 |
+
}
|
controllers/admin/home.php
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Admin Home page
|
4 |
+
*
|
5 |
+
* @author Pavel Kulbakin <p.kulbakin@gmail.com>
|
6 |
+
*/
|
7 |
+
class PMXI_Admin_Home extends PMXI_Controller_Admin {
|
8 |
+
|
9 |
+
public function index() {
|
10 |
+
$this->render();
|
11 |
+
}
|
12 |
+
}
|
controllers/admin/import.php
ADDED
@@ -0,0 +1,1305 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Import configuration wizard
|
4 |
+
*
|
5 |
+
* @author Pavel Kulbakin <p.kulbakin@gmail.com>
|
6 |
+
*/
|
7 |
+
|
8 |
+
class PMXI_Admin_Import extends PMXI_Controller_Admin {
|
9 |
+
protected $isWizard = true; // indicates whether controller is in wizard mode (otherwize it called to be deligated an edit action)
|
10 |
+
protected $isTemplateEdit = false; // indicates whether controlled is deligated by manage imports controller
|
11 |
+
protected $csv_mimes = array('text/comma-separated-values','text/csv','application/csv','application/excel','application/vnd.ms-excel','application/vnd.msexcel','text/anytext');
|
12 |
+
|
13 |
+
protected function init() {
|
14 |
+
parent::init();
|
15 |
+
|
16 |
+
// enable sessions
|
17 |
+
if ( ! session_id()) session_start();
|
18 |
+
|
19 |
+
if ('PMXI_Admin_Manage' == PMXI_Plugin::getInstance()->getAdminCurrentScreen()->base) { // prereqisites are not checked when flow control is deligated
|
20 |
+
$id = $this->input->get('id');
|
21 |
+
$this->data['import'] = $import = new PMXI_Import_Record();
|
22 |
+
if ( ! $id or $import->getById($id)->isEmpty()) { // specified import is not found
|
23 |
+
wp_redirect(add_query_arg('page', 'pmxi-admin-manage', admin_url('admin.php'))); die();
|
24 |
+
}
|
25 |
+
$this->isWizard = false;
|
26 |
+
|
27 |
+
} else {
|
28 |
+
$action = PMXI_Plugin::getInstance()->getAdminCurrentScreen()->action;
|
29 |
+
$this->_step_ready($action);
|
30 |
+
$this->isInline = 'process' == $action;
|
31 |
+
}
|
32 |
+
|
33 |
+
XmlImportConfig::getInstance()->setCacheDirectory(sys_get_temp_dir());
|
34 |
+
|
35 |
+
// preserve id parameter as part of baseUrl
|
36 |
+
$id = $this->input->get('id') and $this->baseUrl = add_query_arg('id', $id, $this->baseUrl);
|
37 |
+
}
|
38 |
+
|
39 |
+
public function set($var, $val)
|
40 |
+
{
|
41 |
+
$this->{$var} = $val;
|
42 |
+
}
|
43 |
+
public function get($var)
|
44 |
+
{
|
45 |
+
return $this->{$var};
|
46 |
+
}
|
47 |
+
|
48 |
+
/**
|
49 |
+
* Checks whether corresponding step of wizard is complete
|
50 |
+
* @param string $action
|
51 |
+
*/
|
52 |
+
protected function _step_ready($action) {
|
53 |
+
// step #1: xml selction - has no prerequisites
|
54 |
+
if ('index' == $action) return true;
|
55 |
+
|
56 |
+
// step #2: element selection
|
57 |
+
$this->data['dom'] = $dom = new DOMDocument();
|
58 |
+
$this->data['update_previous'] = $update_previous = new PMXI_Import_Record();
|
59 |
+
$old = libxml_use_internal_errors(true);
|
60 |
+
if (empty($_SESSION['pmxi_import'])
|
61 |
+
or ! $dom->loadXML(preg_replace('%xmlns\s*=\s*([\'"]).*\1%sU', '', $_SESSION['pmxi_import']['xml'])) // FIX: libxml xpath doesn't handle default namespace properly, so remove it upon XML load
|
62 |
+
or empty($_SESSION['pmxi_import']['source'])
|
63 |
+
or ! empty($_SESSION['pmxi_import']['update_previous']) and $update_previous->getById($_SESSION['pmxi_import']['update_previous'])->isEmpty()
|
64 |
+
) {
|
65 |
+
wp_redirect_or_javascript($this->baseUrl); die();
|
66 |
+
}
|
67 |
+
libxml_use_internal_errors($old);
|
68 |
+
if ('element' == $action) return true;
|
69 |
+
if ('evaluate' == $action) return true;
|
70 |
+
|
71 |
+
// step #3: template
|
72 |
+
$xpath = new DOMXPath($dom);
|
73 |
+
if (empty($_SESSION['pmxi_import']['xpath']) or ! ($this->data['elements'] = $elements = $xpath->query($_SESSION['pmxi_import']['xpath'])) or ! $elements->length) {
|
74 |
+
wp_redirect_or_javascript(add_query_arg('action', 'element', $this->baseUrl)); die();
|
75 |
+
}
|
76 |
+
if ('template' == $action or 'preview' == $action or 'tag' == $action) return true;
|
77 |
+
|
78 |
+
// step #4: options
|
79 |
+
if (empty($_SESSION['pmxi_import']['template']) or empty($_SESSION['pmxi_import']['template']['title']) or empty($_SESSION['pmxi_import']['template']['title'])) {
|
80 |
+
wp_redirect_or_javascript(add_query_arg('action', 'template', $this->baseUrl)); die();
|
81 |
+
}
|
82 |
+
if ('options' == $action) return true;
|
83 |
+
|
84 |
+
if (empty($_SESSION['pmxi_import']['options'])) {
|
85 |
+
wp_redirect(add_query_arg('action', 'options', $this->baseUrl)); die();
|
86 |
+
}
|
87 |
+
}
|
88 |
+
|
89 |
+
/**
|
90 |
+
* Step #1: Choose File
|
91 |
+
*/
|
92 |
+
public function index() {
|
93 |
+
|
94 |
+
$import = new PMXI_Import_Record();
|
95 |
+
$this->data['id'] = $id = $this->input->get('id');
|
96 |
+
if ($id and $import->getById($id)->isEmpty()) { // update requested but corresponding import is not found
|
97 |
+
wp_redirect(remove_query_arg('id', $this->baseUrl)); die();
|
98 |
+
}
|
99 |
+
|
100 |
+
$this->data['post'] = $post = $this->input->post(array(
|
101 |
+
'type' => 'upload',
|
102 |
+
'url' => 'http://',
|
103 |
+
'ftp' => array('url' => 'ftp://'),
|
104 |
+
'file' => '',
|
105 |
+
'reimport' => '',
|
106 |
+
'is_update_previous' => $id ? 1 : 0,
|
107 |
+
'update_previous' => $id,
|
108 |
+
));
|
109 |
+
|
110 |
+
$this->data['imports'] = $imports = new PMXI_Import_List();
|
111 |
+
$imports->setColumns('id', 'name', 'registered_on', 'path')->getBy(NULL, 'name ASC, registered_on DESC');
|
112 |
+
|
113 |
+
$this->data['history'] = $history = new PMXI_File_List();
|
114 |
+
$history->setColumns('id', 'name', 'registered_on', 'path')->getBy(NULL, 'id DESC');
|
115 |
+
|
116 |
+
if ($this->input->post('is_submitted_continue')) {
|
117 |
+
if ( ! empty($_SESSION['pmxi_import']['xml'])) {
|
118 |
+
wp_redirect(add_query_arg('action', 'element', $this->baseUrl)); die();
|
119 |
+
}
|
120 |
+
} elseif ('upload' == $this->input->post('type')) {
|
121 |
+
if (empty($_FILES['upload']) or empty($_FILES['upload']['name'])) {
|
122 |
+
$this->errors->add('form-validation', __('XML/CSV file must be specified', 'pmxi_plugin'));
|
123 |
+
} elseif (empty($_FILES['upload']['size'])) {
|
124 |
+
$this->errors->add('form-validation', __('Uploaded file is empty', 'pmxi_plugin'));
|
125 |
+
} elseif ( ! preg_match('%\W(xml|gzip|csv)$%i', trim($_FILES['upload']['name'])) and !$this->detect_csv($_FILES['upload']['type'])) {
|
126 |
+
$this->errors->add('form-validation', __('Uploaded file must be XML, CSV or GZIP', 'pmxi_plugin'));
|
127 |
+
} elseif ( preg_match('%\W(csv)$%i', trim($_FILES['upload']['name'])) or $this->detect_csv($_FILES['upload']['type'])) {
|
128 |
+
$uploads = wp_upload_dir();
|
129 |
+
if($uploads['error']){
|
130 |
+
$this->errors->add('form-validation', __('Can not create upload folder. Permision denied', 'pmxi_plugin'));
|
131 |
+
}
|
132 |
+
// copy file in temporary folder
|
133 |
+
$fdata = file_get_contents($_FILES['upload']['tmp_name']);
|
134 |
+
$fdata = utf8_encode($fdata);
|
135 |
+
file_put_contents($uploads['path'] . '/' . trim(basename($_FILES['upload']['name'])), $fdata);
|
136 |
+
chmod($uploads['path'] . '/'. trim(basename($_FILES['upload']['name'])), "0777");
|
137 |
+
// end file convertion
|
138 |
+
$xml = $this->csv_to_xml($uploads['path'] . '/' . trim(basename($_FILES['upload']['name'])));
|
139 |
+
if( is_array($xml) && isset($xml['error'])){
|
140 |
+
$this->errors->add('form-validation', __($xml['error'], 'pmxi_plugin'));
|
141 |
+
}
|
142 |
+
else {
|
143 |
+
// delete file in temporary folder
|
144 |
+
unlink( $uploads['path'] .'/'. trim(basename($_FILES['upload']['name']) ));
|
145 |
+
$filename = $_FILES['upload']['tmp_name'];
|
146 |
+
|
147 |
+
// Let's make sure the file exists and is writable first.
|
148 |
+
if (is_writable($filename)) {
|
149 |
+
|
150 |
+
if (!$handle = fopen($filename, 'w')) {
|
151 |
+
$this->errors->add('form-validation', __('Cannot open file ' . $filename, 'pmxi_plugin'));
|
152 |
+
}
|
153 |
+
|
154 |
+
// Write $somecontent to our opened file.
|
155 |
+
if (fwrite($handle, $xml) === FALSE) {
|
156 |
+
$this->errors->add('form-validation', __('Cannot write to file ' . $filename, 'pmxi_plugin'));
|
157 |
+
}
|
158 |
+
|
159 |
+
fclose($handle);
|
160 |
+
|
161 |
+
} else {
|
162 |
+
$this->errors->add('form-validation', __('The file' . $filename . 'is not writable', 'pmxi_plugin'));
|
163 |
+
}
|
164 |
+
$filePath = $_FILES['upload']['tmp_name'];
|
165 |
+
$source = array(
|
166 |
+
'name' => $_FILES['upload']['name'],
|
167 |
+
'type' => 'upload',
|
168 |
+
'path' => '',
|
169 |
+
);
|
170 |
+
}
|
171 |
+
} else {
|
172 |
+
$filePath = $_FILES['upload']['tmp_name'];
|
173 |
+
$source = array(
|
174 |
+
'name' => $_FILES['upload']['name'],
|
175 |
+
'type' => 'upload',
|
176 |
+
'path' => '',
|
177 |
+
);
|
178 |
+
}
|
179 |
+
} elseif ('url' == $this->input->post('type')) {
|
180 |
+
if (empty($post['url'])) {
|
181 |
+
$this->errors->add('form-validation', __('XML/CSV file must be specified', 'pmxi_plugin'));
|
182 |
+
} elseif ( ! preg_match('%^https?://%i', $post['url'])) {
|
183 |
+
$this->errors->add('form-validation', __('Specified URL has wrong format'), 'pmxi_plugin');
|
184 |
+
} elseif ( preg_match('%\W(csv)$%i', trim($post['url'])) or strpos(file_get_contents($post['url']), '<?xml') === false) {
|
185 |
+
$uploads = wp_upload_dir();
|
186 |
+
$fdata = file_get_contents($post['url']);
|
187 |
+
$fdata = utf8_encode($fdata);
|
188 |
+
$tmpname = md5(time()).'.csv';
|
189 |
+
file_put_contents($uploads['path'] .'/'. $tmpname, $fdata);
|
190 |
+
$xml = $this->csv_to_xml($uploads['path'] .'/'. $tmpname);
|
191 |
+
if( is_array($xml) && isset($xml['error'])){
|
192 |
+
$this->errors->add('form-validation', __($xml['error'], 'pmxi_plugin'));
|
193 |
+
}
|
194 |
+
else {
|
195 |
+
$filename = tempnam(XmlImportConfig::getInstance()->getCacheDirectory(), 'xim');
|
196 |
+
|
197 |
+
// Let's make sure the file exists and is writable first.
|
198 |
+
if (is_writable($filename)) {
|
199 |
+
|
200 |
+
if (!$handle = fopen($filename, 'w')) {
|
201 |
+
$this->errors->add('form-validation', __('Cannot open file ' . $filename, 'pmxi_plugin'));
|
202 |
+
}
|
203 |
+
|
204 |
+
// Write $somecontent to our opened file.
|
205 |
+
if (fwrite($handle, $xml) === FALSE) {
|
206 |
+
$this->errors->add('form-validation', __('Cannot write to file ' . $filename, 'pmxi_plugin'));
|
207 |
+
}
|
208 |
+
|
209 |
+
fclose($handle);
|
210 |
+
|
211 |
+
} else {
|
212 |
+
$this->errors->add('form-validation', __('The file' . $filename . 'is not writable', 'pmxi_plugin'));
|
213 |
+
}
|
214 |
+
$filePath = $filename;
|
215 |
+
$source = array(
|
216 |
+
'name' => basename(parse_url($post['url'], PHP_URL_PATH)),
|
217 |
+
'type' => 'url',
|
218 |
+
'path' => $post['url'],
|
219 |
+
);
|
220 |
+
}
|
221 |
+
}else {
|
222 |
+
$filePath = $post['url'];
|
223 |
+
$source = array(
|
224 |
+
'name' => basename(parse_url($filePath, PHP_URL_PATH)),
|
225 |
+
'type' => 'url',
|
226 |
+
'path' => $filePath,
|
227 |
+
);
|
228 |
+
|
229 |
+
}
|
230 |
+
} elseif ('ftp' == $this->input->post('type')) {
|
231 |
+
if (empty($post['ftp']['url'])) {
|
232 |
+
$this->errors->add('form-validation', __('XML/CSV file must be specified', 'pmxi_plugin'));
|
233 |
+
} elseif ( ! preg_match('%^ftps?://%i', $post['ftp']['url'])) {
|
234 |
+
$this->errors->add('form-validation', __('Specified FTP resource has wrong format'), 'pmxi_plugin');
|
235 |
+
} elseif ( preg_match('%\W(csv)$%i', trim($post['ftp']['url']))) {
|
236 |
+
// path to remote file
|
237 |
+
$remote_file = $post['ftp']['url'];
|
238 |
+
$local_file = tempnam(XmlImportConfig::getInstance()->getCacheDirectory(), 'xim');
|
239 |
+
|
240 |
+
// open some file to write to
|
241 |
+
$handle = fopen($local_file, 'w');
|
242 |
+
|
243 |
+
// set up basic connection
|
244 |
+
$ftp_url = $post['ftp']['url'];
|
245 |
+
$parsed_url = parse_url($ftp_url);
|
246 |
+
$ftp_server = $parsed_url['host'] ;
|
247 |
+
$conn_id = ftp_connect( $ftp_server );
|
248 |
+
$is_ftp_ok = TRUE;
|
249 |
+
|
250 |
+
// login with username and password
|
251 |
+
$ftp_user_name = $post['ftp']['user'];
|
252 |
+
$ftp_user_pass = $post['ftp']['pass'];
|
253 |
+
|
254 |
+
// hide warning message
|
255 |
+
echo '<span style="display:none">';
|
256 |
+
if ( !ftp_login($conn_id, $ftp_user_name, $ftp_user_pass) ){
|
257 |
+
$this->errors->add('form-validation', __('Login authentication failed', 'pmxi_plugin'));
|
258 |
+
$is_ftp_ok = false;
|
259 |
+
}
|
260 |
+
echo '</span>';
|
261 |
+
|
262 |
+
|
263 |
+
if ( $is_ftp_ok ){
|
264 |
+
// try to download $remote_file and save it to $handle
|
265 |
+
if (!ftp_fget($conn_id, $handle, $parsed_url['path'], FTP_ASCII, 0)) {
|
266 |
+
$this->errors->add('form-validation', __('There was a problem while downloading' . $remote_file . 'to' . $local_file, 'pmxi_plugin'));
|
267 |
+
}
|
268 |
+
|
269 |
+
// close the connection and the file handler
|
270 |
+
ftp_close($conn_id);
|
271 |
+
fclose($handle);
|
272 |
+
|
273 |
+
// copy file in temporary folder
|
274 |
+
$uploads = wp_upload_dir();
|
275 |
+
if($uploads['error']){
|
276 |
+
$this->errors->add('form-validation', __('Can not create upload folder. Permision denied', 'pmxi_plugin'));
|
277 |
+
}
|
278 |
+
copy( $local_file, $uploads['path'] . basename($local_file));
|
279 |
+
$url = $uploads['url'] . basename($local_file);
|
280 |
+
// convert file to utf8
|
281 |
+
chmod($uploads['path'] . basename($local_file), '0755');
|
282 |
+
$fdata = file_get_contents($url);
|
283 |
+
$fdata = utf8_encode($fdata);
|
284 |
+
file_put_contents($uploads['path'] . basename($local_file), $fdata);
|
285 |
+
// end file convertion
|
286 |
+
$xml = $this->csv_to_xml($uploads['path'] . basename($local_file));
|
287 |
+
if( is_array($xml) && isset($xml['error'])){
|
288 |
+
$this->errors->add('form-validation', __($xml['error'], 'pmxi_plugin'));
|
289 |
+
}
|
290 |
+
else {
|
291 |
+
unlink( $uploads['path'] . basename($local_file) );
|
292 |
+
$filename = $local_file;
|
293 |
+
|
294 |
+
// Let's make sure the file exists and is writable first.
|
295 |
+
if (is_writable($filename)) {
|
296 |
+
|
297 |
+
if (!$handle = fopen($filename, 'w')) {
|
298 |
+
$this->errors->add('form-validation', __('Cannot open file ' . $filename, 'pmxi_plugin'));
|
299 |
+
}
|
300 |
+
|
301 |
+
// Write $somecontent to our opened file.
|
302 |
+
if (fwrite($handle, $xml) === FALSE) {
|
303 |
+
$this->errors->add('form-validation', __('Cannot write to file ' . $filename, 'pmxi_plugin'));
|
304 |
+
}
|
305 |
+
|
306 |
+
fclose($handle);
|
307 |
+
|
308 |
+
} else {
|
309 |
+
$this->errors->add('form-validation', __('The file' . $filename . 'is not writable', 'pmxi_plugin'));
|
310 |
+
}
|
311 |
+
$filePath = $local_file;
|
312 |
+
$source = array(
|
313 |
+
'name' => basename($local_file),
|
314 |
+
'type' => 'ftp',
|
315 |
+
'path' => $filePath,
|
316 |
+
);
|
317 |
+
}
|
318 |
+
}
|
319 |
+
} else {
|
320 |
+
$filePath = $post['ftp']['url'];
|
321 |
+
if (isset($post['ftp']['user']) and $post['ftp']['user'] !== '') {
|
322 |
+
$filePath = preg_replace('%://([^@/]*@)?%', '://' . urlencode($post['ftp']['user']) . ':' . urlencode($post['ftp']['pass']) . '@', $filePath, 1);
|
323 |
+
}
|
324 |
+
$source = array(
|
325 |
+
'name' => basename(parse_url($filePath, PHP_URL_PATH)),
|
326 |
+
'type' => 'ftp',
|
327 |
+
'path' => $filePath,
|
328 |
+
);
|
329 |
+
}
|
330 |
+
} elseif ('file' == $this->input->post('type')) {
|
331 |
+
if (empty($post['file'])) {
|
332 |
+
$this->errors->add('form-validation', __('XML/CSV file must be specified', 'pmxi_plugin'));
|
333 |
+
} elseif (preg_match('%\W(csv)$%i', trim($post['file']))) {
|
334 |
+
$uploads = PMXI_Plugin::ROOT_DIR . '/upload/';
|
335 |
+
$wp_uploads = wp_upload_dir();
|
336 |
+
if($wp_uploads['error']){
|
337 |
+
$this->errors->add('form-validation', __('Can not create upload folder. Permision denied', 'pmxi_plugin'));
|
338 |
+
}
|
339 |
+
// copy file in temporary folder
|
340 |
+
// hide warning message
|
341 |
+
echo '<span style="display:none">';
|
342 |
+
copy( $uploads . $post['file'], $wp_uploads['path'] . basename($post['file']));
|
343 |
+
echo '</span>';
|
344 |
+
$url = $wp_uploads['url'] . basename($post['file']);
|
345 |
+
// convert file to utf8
|
346 |
+
chmod($wp_uploads['path'] . basename($post['file']), '0755');
|
347 |
+
$fdata = file_get_contents($url);
|
348 |
+
$fdata = utf8_encode($fdata);
|
349 |
+
file_put_contents($wp_uploads['path'] . basename($post['file']), $fdata);
|
350 |
+
// end file convertion
|
351 |
+
$xml = $this->csv_to_xml($wp_uploads['path'] . basename($post['file']));
|
352 |
+
if( is_array($xml) && isset($xml['error'])){
|
353 |
+
$this->errors->add('form-validation', __($xml['error'], 'pmxi_plugin'));
|
354 |
+
}
|
355 |
+
else {
|
356 |
+
$filename = $wp_uploads['path'] . basename($post['file']);
|
357 |
+
|
358 |
+
// Let's make sure the file exists and is writable first.
|
359 |
+
if (is_writable($filename)) {
|
360 |
+
|
361 |
+
if (!$handle = fopen($filename, 'w')) {
|
362 |
+
$this->errors->add('form-validation', __('Cannot open file ' . $filename, 'pmxi_plugin'));
|
363 |
+
}
|
364 |
+
|
365 |
+
// Write $somecontent to our opened file.
|
366 |
+
if (fwrite($handle, $xml) === FALSE) {
|
367 |
+
$this->errors->add('form-validation', __('Cannot write to file ' . $filename, 'pmxi_plugin'));
|
368 |
+
}
|
369 |
+
|
370 |
+
fclose($handle);
|
371 |
+
|
372 |
+
} else {
|
373 |
+
$this->errors->add('form-validation', __('The file ' . $filename . ' is not writable or you use wildcard for CSV file', 'pmxi_plugin'));
|
374 |
+
}
|
375 |
+
$filePath = $wp_uploads['path'] . basename($post['file']);
|
376 |
+
$source = array(
|
377 |
+
'name' => basename(parse_url($filePath, PHP_URL_PATH)),
|
378 |
+
'type' => 'file',
|
379 |
+
'path' => $filePath,
|
380 |
+
);
|
381 |
+
}
|
382 |
+
}
|
383 |
+
else {
|
384 |
+
$filePath = PMXI_Plugin::ROOT_DIR . '/upload/' . $post['file'];
|
385 |
+
$source = array(
|
386 |
+
'name' => basename(parse_url($filePath, PHP_URL_PATH)),
|
387 |
+
'type' => 'file',
|
388 |
+
'path' => $filePath,
|
389 |
+
);
|
390 |
+
}
|
391 |
+
} elseif ('reimport' == $this->input->post('type')) {
|
392 |
+
if (empty($post['reimport'])) {
|
393 |
+
$this->errors->add('form-validation', __('XML/CSV file must be specified', 'pmxi_plugin'));
|
394 |
+
}
|
395 |
+
}
|
396 |
+
|
397 |
+
if ($post['is_update_previous'] and empty($post['update_previous'])) {
|
398 |
+
$this->errors->add('form-validation', __('Previous import for update must be selected or uncheck `Update Previous Import` option to proceed with a new one', 'pmxi_plugin'));
|
399 |
+
}
|
400 |
+
|
401 |
+
if ($this->input->post('is_submitted') and ! $this->errors->get_error_codes()) {
|
402 |
+
|
403 |
+
check_admin_referer('choose-file', '_wpnonce_choose-file');
|
404 |
+
|
405 |
+
if ('reimport' == $this->input->post('type')) { // get file content from database
|
406 |
+
preg_match('%^#(\d+):%', $post['reimport'], $mtch) and $reimport_id = $mtch[1] or $reimport_id = 0;
|
407 |
+
$file = new PMXI_File_Record();
|
408 |
+
if ( ! $reimport_id or $file->getById($reimport_id)->isEmpty()) {
|
409 |
+
$xml = FALSE;
|
410 |
+
} else {
|
411 |
+
$xml = $file->contents;
|
412 |
+
$source = array(
|
413 |
+
'name' => $file->name,
|
414 |
+
'type' => 'reimport',
|
415 |
+
'path' => $file->path,
|
416 |
+
);
|
417 |
+
}
|
418 |
+
} else {
|
419 |
+
if (in_array($this->input->post('type'), array('ftp', 'file'))) { // file may be specified by pattern
|
420 |
+
$file_path_array = @PMXI_Helper::safe_glob($filePath, PMXI_Helper::GLOB_NODIR | PMXI_Helper::GLOB_PATH);
|
421 |
+
if ($file_path_array) {
|
422 |
+
$filePath = array_shift($file_path_array); // take only 1st matching one
|
423 |
+
} else {
|
424 |
+
$filePath = FALSE;
|
425 |
+
}
|
426 |
+
}
|
427 |
+
|
428 |
+
ob_start();
|
429 |
+
$filePath && @readgzfile($filePath);
|
430 |
+
|
431 |
+
$xml = ob_get_clean();
|
432 |
+
|
433 |
+
}
|
434 |
+
|
435 |
+
if (PMXI_Import_Record::validateXml($xml, $this->errors)) {
|
436 |
+
// xml is valid
|
437 |
+
|
438 |
+
$_SESSION['pmxi_import'] = array(
|
439 |
+
'xml' => $xml,
|
440 |
+
'source' => $source,
|
441 |
+
);
|
442 |
+
|
443 |
+
$update_previous = new PMXI_Import_Record();
|
444 |
+
if ($post['is_update_previous'] and ! $update_previous->getById($post['update_previous'])->isEmpty()) {
|
445 |
+
$_SESSION['pmxi_import'] += array(
|
446 |
+
'update_previous' => $update_previous->id,
|
447 |
+
'xpath' => $update_previous->xpath,
|
448 |
+
'template' => $update_previous->template,
|
449 |
+
'options' => $update_previous->options,
|
450 |
+
);
|
451 |
+
} else {
|
452 |
+
$_SESSION['pmxi_import']['update_previous'] = '';
|
453 |
+
}
|
454 |
+
|
455 |
+
wp_redirect(add_query_arg('action', 'element', $this->baseUrl)); die();
|
456 |
+
}
|
457 |
+
}
|
458 |
+
|
459 |
+
$this->render();
|
460 |
+
}
|
461 |
+
|
462 |
+
/**
|
463 |
+
* Step #2: Choose elements
|
464 |
+
*/
|
465 |
+
public function element()
|
466 |
+
{
|
467 |
+
|
468 |
+
|
469 |
+
$xpath = new DOMXPath($this->data['dom']);
|
470 |
+
$post = $this->input->post(array('xpath' => ''));
|
471 |
+
$this->data['post'] =& $post;
|
472 |
+
|
473 |
+
if ($this->input->post('is_submitted')) {
|
474 |
+
check_admin_referer('choose-elements', '_wpnonce_choose-elements');
|
475 |
+
if ('' == $post['xpath']) {
|
476 |
+
$this->errors->add('form-validation', __('No elements selected', 'pmxi_plugin'));
|
477 |
+
} else {
|
478 |
+
$node_list = @ $xpath->query($post['xpath']); // make sure only element selection is allowed; prevent parsing warning to be displayed
|
479 |
+
|
480 |
+
if (FALSE === $node_list) {
|
481 |
+
$this->errors->add('form-validation', __('Invalid XPath expression', 'pmxi_plugin'));
|
482 |
+
} elseif ( ! $node_list->length) {
|
483 |
+
$this->errors->add('form-validation', __('No matching elements found for XPath expression specified', 'pmxi_plugin'));
|
484 |
+
} else {
|
485 |
+
foreach ($node_list as $el) {
|
486 |
+
if ( ! $el instanceof DOMElement) {
|
487 |
+
$this->errors->add('form-validation', __('XPath must match only elements', 'pmxi_plugin'));
|
488 |
+
break;
|
489 |
+
};
|
490 |
+
}
|
491 |
+
}
|
492 |
+
}
|
493 |
+
if ( ! $this->errors->get_error_codes()) {
|
494 |
+
$_SESSION['pmxi_import']['xpath'] = $post['xpath'];
|
495 |
+
wp_redirect(add_query_arg('action', 'template', $this->baseUrl)); die();
|
496 |
+
}
|
497 |
+
} else {
|
498 |
+
|
499 |
+
$this->shrink_xml_element($this->data['dom']->documentElement);
|
500 |
+
|
501 |
+
if (isset($_SESSION['pmxi_import']['xpath'])) {
|
502 |
+
$post['xpath'] = $_SESSION['pmxi_import']['xpath'];
|
503 |
+
if ( ! $xpath->query($post['xpath'])->length and ! empty($_SESSION['pmxi_import']['update_previous'])) {
|
504 |
+
$_GET['pmxi_nt'] = __('<b>Warning</b>: No matching elements found for XPath expression from the import being updated. It probably means that new XML file has different format. Though you can update XPath, procceed only if you sure about update operation being valid.', 'pmxi_plugin');
|
505 |
+
}
|
506 |
+
} else {
|
507 |
+
// suggest 1st repeating element as default selection
|
508 |
+
$post['xpath'] = $this->xml_find_repeating($this->data['dom']->documentElement);
|
509 |
+
}
|
510 |
+
}
|
511 |
+
|
512 |
+
// workaround to prevent rendered XML representation to eat memory since it has to be stored in momory when output is bufferred
|
513 |
+
$this->render();
|
514 |
+
add_action('pmxi_action_after', array($this, 'element_after'));
|
515 |
+
}
|
516 |
+
public function element_after()
|
517 |
+
{
|
518 |
+
$this->render();
|
519 |
+
}
|
520 |
+
|
521 |
+
/**
|
522 |
+
* Helper to evaluate xpath and return matching elements as direct paths for javascript side to highlight them
|
523 |
+
*/
|
524 |
+
public function evaluate()
|
525 |
+
{
|
526 |
+
if ( ! PMXI_Plugin::getInstance()->getAdminCurrentScreen()->is_ajax) { // call is only valid when send with ajax
|
527 |
+
wp_redirect(add_query_arg('action', 'element', $this->baseUrl)); die();
|
528 |
+
}
|
529 |
+
|
530 |
+
$xpath = new DOMXPath($this->data['dom']);
|
531 |
+
$post = $this->input->post(array('xpath' => ''));
|
532 |
+
if ('' == $post['xpath']) {
|
533 |
+
$this->errors->add('form-validation', __('No elements selected', 'pmxi_plugin'));
|
534 |
+
} else {
|
535 |
+
$node_list = @ $xpath->query($post['xpath']); // prevent parsing warning to be displayed
|
536 |
+
$this->data['node_list_count'] = $node_list->length;
|
537 |
+
if (FALSE === $node_list) {
|
538 |
+
$this->errors->add('form-validation', __('Invalid XPath expression', 'pmxi_plugin'));
|
539 |
+
} elseif ( ! $node_list->length) {
|
540 |
+
$this->errors->add('form-validation', __('No matching elements found for XPath expression specified', 'pmxi_plugin'));
|
541 |
+
} else {
|
542 |
+
foreach ($node_list as $el) {
|
543 |
+
if ( ! $el instanceof DOMElement) {
|
544 |
+
$this->errors->add('form-validation', __('XPath must match only elements', 'pmxi_plugin'));
|
545 |
+
break;
|
546 |
+
};
|
547 |
+
}
|
548 |
+
}
|
549 |
+
}
|
550 |
+
if ( ! $this->errors->get_error_codes()) {
|
551 |
+
$this->shrink_xml_element($this->data['dom']->documentElement);
|
552 |
+
$xpath = new DOMXPath($this->data['dom']);
|
553 |
+
$this->data['node_list'] = $node_list = @ $xpath->query($post['xpath']); // prevent parsing warning to be displayed
|
554 |
+
|
555 |
+
$paths = array(); $this->data['paths'] =& $paths;
|
556 |
+
if (PMXI_Plugin::getInstance()->getOption('highlight_limit') and $node_list->length <= PMXI_Plugin::getInstance()->getOption('highlight_limit')) {
|
557 |
+
foreach ($node_list as $el) {
|
558 |
+
if ( ! $el instanceof DOMElement) continue;
|
559 |
+
|
560 |
+
$p = $this->get_xml_path($el, $xpath) and $paths[] = $p;
|
561 |
+
}
|
562 |
+
}
|
563 |
+
$this->render();
|
564 |
+
} else {
|
565 |
+
$this->error();
|
566 |
+
}
|
567 |
+
}
|
568 |
+
|
569 |
+
/**
|
570 |
+
* Step #3: Choose template
|
571 |
+
*/
|
572 |
+
public function template()
|
573 |
+
{
|
574 |
+
|
575 |
+
$template = new PMXI_Template_Record();
|
576 |
+
$default = array(
|
577 |
+
'title' => '',
|
578 |
+
'content' => '',
|
579 |
+
'name' => '',
|
580 |
+
'is_keep_linebreaks' => 0,
|
581 |
+
);
|
582 |
+
if ($this->isWizard) {
|
583 |
+
$this->data['post'] = $post = $this->input->post(
|
584 |
+
(isset($_SESSION['pmxi_import']['template']) ? $_SESSION['pmxi_import']['template'] : array())
|
585 |
+
+ $default
|
586 |
+
);
|
587 |
+
} else {
|
588 |
+
$this->data['post'] = $post = $this->input->post(
|
589 |
+
$this->data['import']->template
|
590 |
+
+ $default
|
591 |
+
);
|
592 |
+
}
|
593 |
+
|
594 |
+
if (($load_template = $this->input->post('load_template'))) { // init form with template selected
|
595 |
+
if ( ! $template->getById($load_template)->isEmpty()) {
|
596 |
+
$this->data['post'] = array(
|
597 |
+
'title' => $template->title,
|
598 |
+
'content' => $template->content,
|
599 |
+
'is_keep_linebreaks' => $template->is_keep_linebreaks,
|
600 |
+
'name' => '', // template is always empty
|
601 |
+
);
|
602 |
+
$_SESSION['pmxi_import']['is_loaded_template'] = $load_template;
|
603 |
+
}
|
604 |
+
|
605 |
+
} elseif ($this->input->post('is_submitted')) { // save template submission
|
606 |
+
check_admin_referer('template', '_wpnonce_template');
|
607 |
+
|
608 |
+
if (empty($post['title'])) {
|
609 |
+
$this->errors->add('form-validation', __('Post title is empty', 'pmxi_plugin'));
|
610 |
+
} else {
|
611 |
+
$this->_validate_template($post['title'], 'Post title');
|
612 |
+
}
|
613 |
+
if (empty($post['content'])) {
|
614 |
+
$this->errors->add('form-validation', __('Post content is empty', 'pmxi_plugin'));
|
615 |
+
} else {
|
616 |
+
$this->_validate_template($post['content'], 'Post content');
|
617 |
+
}
|
618 |
+
|
619 |
+
|
620 |
+
if ( ! $this->errors->get_error_codes()) {
|
621 |
+
if ( ! empty($post['name'])) { // save template in database
|
622 |
+
$template->getByName($post['name'])->set($post)->save();
|
623 |
+
$_SESSION['pmxi_import']['saved_template'] = $template->id;
|
624 |
+
}
|
625 |
+
if ($this->isWizard) {
|
626 |
+
$_SESSION['pmxi_import']['template'] = $post;
|
627 |
+
wp_redirect(add_query_arg('action', 'options', $this->baseUrl)); die();
|
628 |
+
} else {
|
629 |
+
$this->data['import']->set('template', $post)->save();
|
630 |
+
wp_redirect(add_query_arg(array('page' => 'pmxi-admin-manage', 'pmlc_nt' => urlencode(__('Template updated', 'pmxi_plugin'))) + array_intersect_key($_GET, array_flip($this->baseUrlParamNames)), admin_url('admin.php'))); die();
|
631 |
+
}
|
632 |
+
}
|
633 |
+
}
|
634 |
+
|
635 |
+
if (user_can_richedit()) {
|
636 |
+
wp_enqueue_script('editor');
|
637 |
+
}
|
638 |
+
wp_enqueue_script('word-count');
|
639 |
+
add_thickbox();
|
640 |
+
wp_enqueue_script('media-upload');
|
641 |
+
add_action('admin_print_footer_scripts', 'wp_tiny_mce', 25);
|
642 |
+
wp_enqueue_script('quicktags');
|
643 |
+
$this->render();
|
644 |
+
}
|
645 |
+
protected function _validate_template($text, $field_title)
|
646 |
+
{
|
647 |
+
try {
|
648 |
+
$scanner = new XmlImportTemplateScanner();
|
649 |
+
$tokens = $scanner->scan(new XmlImportStringReader($text));
|
650 |
+
$parser = new XmlImportTemplateParser($tokens);
|
651 |
+
$tree = $parser->parse();
|
652 |
+
} catch (XmlImportException $e) {
|
653 |
+
$this->errors->add('form-validation', sprintf(__('%s template is invalid: %s', 'pmxi_plugin'), $field_title, $e->getMessage()));
|
654 |
+
}
|
655 |
+
}
|
656 |
+
|
657 |
+
/**
|
658 |
+
* Preview selected xml tag (called with ajax from `template` step)
|
659 |
+
*/
|
660 |
+
public function tag()
|
661 |
+
{
|
662 |
+
if (empty($this->data['elements']))
|
663 |
+
{
|
664 |
+
|
665 |
+
$update_previous = new PMXI_Import_Record();
|
666 |
+
if ($update_previous->getById($this->input->get('id'))) {
|
667 |
+
$_SESSION['pmxi_import'] = array(
|
668 |
+
'update_previous' => $update_previous->id,
|
669 |
+
'xpath' => $update_previous->xpath,
|
670 |
+
'template' => $update_previous->template,
|
671 |
+
'options' => $update_previous->options,
|
672 |
+
);
|
673 |
+
$history_file = new PMXI_File_Record();
|
674 |
+
$history_file->getBy('import_id', $update_previous->id);
|
675 |
+
$history_file->__get('contents');
|
676 |
+
$_SESSION['pmxi_import']['xml'] = $history_file->contents;
|
677 |
+
} else {
|
678 |
+
$_SESSION['pmxi_import']['update_previous'] = '';
|
679 |
+
}
|
680 |
+
if (!empty($_SESSION['pmxi_import']['xml']))
|
681 |
+
{
|
682 |
+
$dom = new DOMDocument();
|
683 |
+
$dom->loadXML(preg_replace('%xmlns\s*=\s*([\'"]).*\1%sU', '', $_SESSION['pmxi_import']['xml']));
|
684 |
+
$xpath = new DOMXPath($dom);
|
685 |
+
|
686 |
+
$this->data['elements'] = $elements = $xpath->query($_SESSION['pmxi_import']['xpath']);
|
687 |
+
}
|
688 |
+
}
|
689 |
+
|
690 |
+
$this->data['tagno'] = min(max(intval($this->input->getpost('tagno', 1)), 1), $this->data['elements']->length);
|
691 |
+
$this->render();
|
692 |
+
}
|
693 |
+
|
694 |
+
/**
|
695 |
+
* Preview future post based on current template and tag (called with ajax from `template` step)
|
696 |
+
*/
|
697 |
+
public function preview()
|
698 |
+
{
|
699 |
+
$post = $this->input->post(array(
|
700 |
+
'title' => '',
|
701 |
+
'content' => '',
|
702 |
+
'is_keep_linebreaks' => 0,
|
703 |
+
));
|
704 |
+
$tagno = min(max(intval($this->input->getpost('tagno', 1)), 1), $this->data['elements']->length);
|
705 |
+
$xpath = "(" . $_SESSION['pmxi_import']['xpath'] . ")[$tagno]";
|
706 |
+
// validate
|
707 |
+
try {
|
708 |
+
|
709 |
+
if (empty($post['title'])) {
|
710 |
+
$this->errors->add('form-validation', __('Post title is empty', 'pmxi_plugin'));
|
711 |
+
} else {
|
712 |
+
list($this->data['title']) = XmlImportParser::factory($_SESSION['pmxi_import']['xml'], $xpath, $post['title'], $file)->parse(); unlink($file);
|
713 |
+
if ( ! isset($this->data['title']) or '' == strval(trim(strip_tags($this->data['title'], '<img><input><textarea><iframe><object><embed>')))) {
|
714 |
+
$this->errors->add('xml-parsing', __('<strong>Warning</strong>: resulting post title is empty', 'pmxi_plugin'));
|
715 |
+
}
|
716 |
+
}
|
717 |
+
} catch (XmlImportException $e) {
|
718 |
+
$this->errors->add('form-validation', sprintf(__('Error parsing title: %s', 'pmxi_plugin'), $e->getMessage()));
|
719 |
+
}
|
720 |
+
try {
|
721 |
+
if (empty($post['content'])) {
|
722 |
+
$this->errors->add('form-validation', __('Post content is empty', 'pmxi_plugin'));
|
723 |
+
} else {
|
724 |
+
list($this->data['content']) = XmlImportParser::factory($post['is_keep_linebreaks'] ? $_SESSION['pmxi_import']['xml'] : preg_replace('%\r\n?|\n%', ' ', $_SESSION['pmxi_import']['xml']), $xpath, $post['content'], $file)->parse(); unlink($file);
|
725 |
+
if ( ! isset($this->data['content']) or '' == strval(trim(strip_tags($this->data['content'], '<img><input><textarea><iframe><object><embed>')))) {
|
726 |
+
$this->errors->add('xml-parsing', __('<strong>Warning</strong>: resulting post content is empty', 'pmxi_plugin'));
|
727 |
+
}
|
728 |
+
}
|
729 |
+
} catch (XmlImportException $e) {
|
730 |
+
$this->errors->add('form-validation', sprintf(__('Error parsing content: %s', 'pmxi_plugin'), $e->getMessage()));
|
731 |
+
}
|
732 |
+
|
733 |
+
$this->render();
|
734 |
+
}
|
735 |
+
|
736 |
+
/**
|
737 |
+
* Step #4: Options
|
738 |
+
*/
|
739 |
+
public function options()
|
740 |
+
{
|
741 |
+
include_once(PMXI_Plugin::ROOT_DIR.'/libraries/XmlImportCsvParse.php');
|
742 |
+
|
743 |
+
$default = PMXI_Plugin::get_default_import_options();
|
744 |
+
|
745 |
+
if ($this->isWizard) {
|
746 |
+
$this->data['source_type'] = $_SESSION['pmxi_import']['source']['type'];
|
747 |
+
$default['unique_key'] = $_SESSION['pmxi_import']['template']['title'];
|
748 |
+
$post = $this->input->post(
|
749 |
+
(isset($_SESSION['pmxi_import']['options']) ? $_SESSION['pmxi_import']['options'] : array())
|
750 |
+
+ $default
|
751 |
+
);
|
752 |
+
|
753 |
+
$scheduled = $this->input->post(array(
|
754 |
+
'is_scheduled' => ! empty($post['scheduled']),
|
755 |
+
'scheduled_period' => ! empty($post['scheduled']) ? $post['scheduled'] : '0 0 * * *', // daily by default
|
756 |
+
));
|
757 |
+
|
758 |
+
} else {
|
759 |
+
$this->data['source_type'] = $this->data['import']->type;
|
760 |
+
$post = $this->input->post(
|
761 |
+
$this->data['import']->options
|
762 |
+
+ $default
|
763 |
+
);
|
764 |
+
$scheduled = $this->input->post(array(
|
765 |
+
'is_scheduled' => ! empty($this->data['import']->scheduled),
|
766 |
+
'scheduled_period' => ! empty($this->data['import']->scheduled) ? $this->data['import']->scheduled : '0 0 * * *', // daily by default
|
767 |
+
));
|
768 |
+
}
|
769 |
+
$this->data['post'] =& $post;
|
770 |
+
$this->data['scheduled'] =& $scheduled;
|
771 |
+
$this->data['is_loaded_template'] = $_SESSION['pmxi_import']['is_loaded_template'];
|
772 |
+
|
773 |
+
if (($load_options = $this->input->post('load_options'))) { // init form with template selected
|
774 |
+
$this->data['load_options'] = true;
|
775 |
+
$template = new PMXI_Template_Record();
|
776 |
+
if ( ! $template->getById($this->data['is_loaded_template'])->isEmpty()) {
|
777 |
+
$post = $template->options + $default;
|
778 |
+
$scheduled = array(
|
779 |
+
'is_scheduled' => ! empty($template->scheduled),
|
780 |
+
'scheduled_period' => ! empty($template->scheduled) ? $template->scheduled : '0 0 * * *', // daily by default
|
781 |
+
);
|
782 |
+
}
|
783 |
+
|
784 |
+
} elseif (($reset_options = $this->input->post('reset_options'))){
|
785 |
+
$post = $default;
|
786 |
+
$scheduled = $this->input->post(array(
|
787 |
+
'is_scheduled' => ! empty($post['scheduled']),
|
788 |
+
'scheduled_period' => ! empty($post['scheduled']) ? $post['scheduled'] : '0 0 * * *', // daily by default
|
789 |
+
));
|
790 |
+
} elseif ($this->input->post('is_submitted')) {
|
791 |
+
check_admin_referer('options', '_wpnonce_options');
|
792 |
+
// remove entires where both custom_name and custom_value are empty
|
793 |
+
$not_empty = array_flip(array_values(array_merge(array_keys(array_filter($post['custom_name'])), array_keys(array_filter($post['custom_value'])))));
|
794 |
+
$post['custom_name'] = array_intersect_key($post['custom_name'], $not_empty);
|
795 |
+
$post['custom_value'] = array_intersect_key($post['custom_value'], $not_empty);
|
796 |
+
// validate
|
797 |
+
if (array_keys(array_filter($post['custom_name'])) != array_keys(array_filter($post['custom_value']))) {
|
798 |
+
$this->errors->add('form-validation', __('Both name and value must be set for all custom parameters', 'pmxi_plugin'));
|
799 |
+
} else {
|
800 |
+
foreach ($post['custom_name'] as $custom_name) {
|
801 |
+
$this->_validate_template($custom_name, __('Custom Field Name', 'pmxi_plugin'));
|
802 |
+
}
|
803 |
+
foreach ($post['custom_value'] as $custom_value) {
|
804 |
+
$this->_validate_template($custom_value, __('Custom Field Value', 'pmxi_plugin'));
|
805 |
+
}
|
806 |
+
}
|
807 |
+
if ('page' == $post['type'] and ! preg_match('%^(-?\d+)?$%', $post['order'])) {
|
808 |
+
$this->errors->add('form-validation', __('Order must be an integer number', 'pmxi_plugin'));
|
809 |
+
}
|
810 |
+
if ('post' == $post['type']) {
|
811 |
+
'' == $post['categories'] or $this->_validate_template($post['categories'], __('Categories', 'pmxi_plugin'));
|
812 |
+
'' == $post['tags'] or $this->_validate_template($post['tags'], __('Tags', 'pmxi_plugin'));
|
813 |
+
}
|
814 |
+
if ('specific' == $post['date_type']) {
|
815 |
+
'' == $post['date'] or $this->_validate_template($post['date'], __('Date', 'pmxi_plugin'));
|
816 |
+
} else {
|
817 |
+
'' == $post['date_start'] or $this->_validate_template($post['date_start'], __('Start Date', 'pmxi_plugin'));
|
818 |
+
'' == $post['date_end'] or $this->_validate_template($post['date_end'], __('Start Date', 'pmxi_plugin'));
|
819 |
+
}
|
820 |
+
if ('' == $post['categories_delim']) {
|
821 |
+
$this->errors->add('form-validation', __('Category list delimiter cannot be empty', 'pmxi_plugin'));
|
822 |
+
}
|
823 |
+
if ('' == $post['tags_delim']) {
|
824 |
+
$this->errors->add('form-validation', __('Tag list delimiter must cannot be empty', 'pmxi_plugin'));
|
825 |
+
}
|
826 |
+
if ($post['is_import_specified']) {
|
827 |
+
if (empty($post['import_specified'])) {
|
828 |
+
$this->errors->add('form-validation', __('Records to import must be specified or uncheck `Import only specified records` option to process all records', 'pmxi_plugin'));
|
829 |
+
} else {
|
830 |
+
$chanks = preg_split('% *, *%', $post['import_specified']);
|
831 |
+
foreach ($chanks as $chank) {
|
832 |
+
if ( ! preg_match('%^([1-9]\d*)( *- *([1-9]\d*))?$%', $chank, $mtch)) {
|
833 |
+
$this->errors->add('form-validation', __('Wrong format of `Import only specified records` value', 'pmxi_plugin'));
|
834 |
+
break;
|
835 |
+
}
|
836 |
+
}
|
837 |
+
}
|
838 |
+
}
|
839 |
+
if ('' == $post['unique_key']) {
|
840 |
+
$this->errors->add('form-validation', __('Expression for `Post Unique Key` must be set, use the same expression as specified for post title if you are not sure what to put there', 'pmxi_plugin'));
|
841 |
+
} else {
|
842 |
+
$this->_validate_template($post['unique_key'], __('Post Unique Key', 'pmxi_plugin'));
|
843 |
+
}
|
844 |
+
|
845 |
+
if ( ! $this->errors->get_error_codes()) { // no validation errors found
|
846 |
+
// assign some defaults
|
847 |
+
'' !== $post['date'] or $post['date'] = 'now';
|
848 |
+
'' !== $post['date_start'] or $post['date_start'] = 'now';
|
849 |
+
'' !== $post['date_end'] or $post['date_end'] = 'now';
|
850 |
+
|
851 |
+
if ($this->isWizard) {
|
852 |
+
$_SESSION['pmxi_import']['options'] = $post;
|
853 |
+
$_SESSION['pmxi_import']['scheduled'] = $scheduled['is_scheduled'] ? $scheduled['scheduled_period'] : '';
|
854 |
+
|
855 |
+
// Update template options
|
856 |
+
if (!empty($_SESSION['pmxi_import']['saved_template'])) {
|
857 |
+
$template = new PMXI_Template_Record();
|
858 |
+
$template->getById($_SESSION['pmxi_import']['saved_template'])->set(array(
|
859 |
+
'options' => $_SESSION['pmxi_import']['options'],
|
860 |
+
'scheduled' => $_SESSION['pmxi_import']['scheduled']))->update();
|
861 |
+
}
|
862 |
+
elseif (!empty($_SESSION['pmxi_import']['is_loaded_template']))
|
863 |
+
{
|
864 |
+
$template = new PMXI_Template_Record();
|
865 |
+
$template->getById($_SESSION['pmxi_import']['is_loaded_template'])->set(array(
|
866 |
+
'options' => $_SESSION['pmxi_import']['options'],
|
867 |
+
'scheduled' => $_SESSION['pmxi_import']['scheduled']))->update();
|
868 |
+
}
|
869 |
+
|
870 |
+
if ( ! $this->input->post('save_only')) {
|
871 |
+
$this->process();die();
|
872 |
+
} else {
|
873 |
+
$import = $this->data['update_previous'];
|
874 |
+
$is_update = ! $import->isEmpty();
|
875 |
+
$import->set(
|
876 |
+
$_SESSION['pmxi_import']['source']
|
877 |
+
+ array(
|
878 |
+
'xpath' => $_SESSION['pmxi_import']['xpath'],
|
879 |
+
'template' => $_SESSION['pmxi_import']['template'],
|
880 |
+
'options' => $_SESSION['pmxi_import']['options'],
|
881 |
+
'scheduled' => $_SESSION['pmxi_import']['scheduled'],
|
882 |
+
)
|
883 |
+
)->save();
|
884 |
+
|
885 |
+
$history_file = new PMXI_File_Record();
|
886 |
+
$history_file->set(array(
|
887 |
+
'name' => $import->name,
|
888 |
+
'import_id' => $import->id,
|
889 |
+
'path' => $import->path,
|
890 |
+
'contents' => $_SESSION['pmxi_import']['xml'],
|
891 |
+
'registered_on' => date('Y-m-d H:i:s'),
|
892 |
+
))->save();
|
893 |
+
unset($_SESSION['pmxi_import']); // clear session data
|
894 |
+
wp_redirect(add_query_arg(array('page' => 'pmxi-admin-manage', 'pmlc_nt' => urlencode($is_update ? __('Import updated', 'pmxi_plugin') : __('Import created', 'pmxi_plugin'))), admin_url('admin.php'))); die();
|
895 |
+
}
|
896 |
+
} else {
|
897 |
+
$this->data['import']->set('options', $post)->set('scheduled', $scheduled['is_scheduled'] ? $scheduled['scheduled_period'] : '')->save();
|
898 |
+
wp_redirect(add_query_arg(array('page' => 'pmxi-admin-manage', 'pmlc_nt' => urlencode(__('Options updated', 'pmxi_plugin'))) + array_intersect_key($_GET, array_flip($this->baseUrlParamNames)), admin_url('admin.php'))); die();
|
899 |
+
}
|
900 |
+
}
|
901 |
+
}
|
902 |
+
|
903 |
+
! empty($post['custom_name']) or $post['custom_name'] = array('') and $post['custom_value'] = array('');
|
904 |
+
|
905 |
+
$this->render();
|
906 |
+
}
|
907 |
+
|
908 |
+
/**
|
909 |
+
* Import processing step (status console)
|
910 |
+
*/
|
911 |
+
public function process()
|
912 |
+
{
|
913 |
+
wp_ob_end_flush_all(); flush();
|
914 |
+
|
915 |
+
// store import info in database
|
916 |
+
$import = $this->data['update_previous'];
|
917 |
+
|
918 |
+
if ($_SESSION['pmxi_import']['options']['is_first_chank'] or !$_SESSION['pmxi_import']['options']['is_update_previous'])
|
919 |
+
{
|
920 |
+
$_SESSION['pmxi_import']['step'] = 0;
|
921 |
+
$import->set(
|
922 |
+
$_SESSION['pmxi_import']['source']
|
923 |
+
+ array(
|
924 |
+
'xpath' => $_SESSION['pmxi_import']['xpath'],
|
925 |
+
'template' => $_SESSION['pmxi_import']['template'],
|
926 |
+
'options' => $_SESSION['pmxi_import']['options'],
|
927 |
+
'scheduled' => $_SESSION['pmxi_import']['scheduled']
|
928 |
+
)
|
929 |
+
)->save();
|
930 |
+
|
931 |
+
$history_file = new PMXI_File_Record();
|
932 |
+
$history_file->set(array(
|
933 |
+
'name' => $import->name,
|
934 |
+
'import_id' => $import->id,
|
935 |
+
'path' => $import->path,
|
936 |
+
'contents' => $_SESSION['pmxi_import']['xml'],
|
937 |
+
'registered_on' => date('Y-m-d H:i:s'),
|
938 |
+
))->save();
|
939 |
+
|
940 |
+
$_SESSION['pmxi_import']['import_start'] = date('H:i:s');
|
941 |
+
}
|
942 |
+
else
|
943 |
+
{
|
944 |
+
$_SESSION['pmxi_import']['step']++;
|
945 |
+
$import = new PMXI_Import_Record();
|
946 |
+
$import->getById($_SESSION['pmxi_import']['options']['is_update_previous']);
|
947 |
+
}
|
948 |
+
|
949 |
+
$logger = create_function('$m', 'echo "<div class=\\"progress-msg\\">$m</div>\\n"; flush();');
|
950 |
+
if (in_array($import->type, array('ftp', 'file'))) { // process files by patten
|
951 |
+
$import->execute($logger);
|
952 |
+
} else { // directly process XML
|
953 |
+
set_time_limit(0);
|
954 |
+
$xml = new SimpleXMLElement($_SESSION['pmxi_import']['xml']);
|
955 |
+
$rootNodes = $xml->xpath($_SESSION['pmxi_import']['xpath']);
|
956 |
+
$this->removeNode($xml, $_SESSION['pmxi_import']['xpath'].'[position() >= 10]');
|
957 |
+
|
958 |
+
if (count($xml->xpath($_SESSION['pmxi_import']['xpath'])))
|
959 |
+
{
|
960 |
+
ob_start();
|
961 |
+
$import->process($xml->asXML(), $logger, ((count($rootNodes) < 10) ? true : false));
|
962 |
+
|
963 |
+
if ((count($rootNodes) < 10))
|
964 |
+
{
|
965 |
+
// [indicate in header process is complete]
|
966 |
+
$msg = addcslashes(__('Complete', 'pmxi_plugin'), "'\n\r");
|
967 |
+
echo <<<COMPLETE
|
968 |
+
<script type="text/javascript">
|
969 |
+
//<![CDATA[
|
970 |
+
(function($){
|
971 |
+
$('#status').html('$msg');
|
972 |
+
window.onbeforeunload = false;
|
973 |
+
})(jQuery);
|
974 |
+
//]]>
|
975 |
+
</script>
|
976 |
+
COMPLETE;
|
977 |
+
// [/indicate in header process is complete]
|
978 |
+
}
|
979 |
+
$log = ob_get_clean();
|
980 |
+
}
|
981 |
+
unset($xml);
|
982 |
+
|
983 |
+
$xml_chank = new SimpleXMLElement($_SESSION['pmxi_import']['xml']);
|
984 |
+
$this->removeNode($xml_chank, $_SESSION['pmxi_import']['xpath'].'[position() < 10]');
|
985 |
+
|
986 |
+
$_SESSION['pmxi_import']['xml'] = (count($rootNodes) >= 10) ? $xml_chank->asXML() : NULL;
|
987 |
+
unset($xml_chank);
|
988 |
+
}
|
989 |
+
|
990 |
+
if (!empty($_SESSION['pmxi_import']['xml']))
|
991 |
+
exit(json_encode(array('status' => 'process','update_previous' => $import->__get('id'), 'count' => count($rootNodes), 'is_last_chank' => ((count($rootNodes) < 10) ? true : false), 'is_first_chank' => $_SESSION['pmxi_import']['options']['is_first_chank'], 'log' => $log)));
|
992 |
+
else {
|
993 |
+
$start_time = $_SESSION['pmxi_import']['import_start'];
|
994 |
+
$end_time = date('H:i:s');
|
995 |
+
unset($_SESSION['pmxi_import']); // clear session data (prevent from reimporting the same data on page refresh)
|
996 |
+
exit(json_encode(array('status' => 'stop', 'log' => $log, 'start_time' => $start_time, 'end_time' => $end_time, 'import_time' => date('H:i:s', strtotime($end_time) - strtotime($start_time)))));
|
997 |
+
};
|
998 |
+
|
999 |
+
|
1000 |
+
}
|
1001 |
+
/**
|
1002 |
+
* Remove xml document nodes by xpath expression
|
1003 |
+
*/
|
1004 |
+
function removeNode($xml, $path)
|
1005 |
+
{
|
1006 |
+
$result = $xml->xpath($path);
|
1007 |
+
|
1008 |
+
if (empty($result)) return false;
|
1009 |
+
$errlevel = error_reporting(E_ALL & ~E_WARNING);
|
1010 |
+
foreach ($result as $r) unset ($r[0]);
|
1011 |
+
error_reporting($errlevel);
|
1012 |
+
|
1013 |
+
return true;
|
1014 |
+
}
|
1015 |
+
|
1016 |
+
/*
|
1017 |
+
*
|
1018 |
+
* Get SimpleXML object by xpath extension
|
1019 |
+
*
|
1020 |
+
*/
|
1021 |
+
function get_chank(& $xml, $path){
|
1022 |
+
|
1023 |
+
$result = $xml->xpath($path);
|
1024 |
+
|
1025 |
+
$array = array();
|
1026 |
+
foreach ($result as $el) {
|
1027 |
+
array_push($array, $this->simplexml2array($el));
|
1028 |
+
}
|
1029 |
+
|
1030 |
+
if (empty($array)) return false;
|
1031 |
+
|
1032 |
+
return new SimpleXMLElement(ArrayToXML::toXml($array));
|
1033 |
+
}
|
1034 |
+
|
1035 |
+
/*
|
1036 |
+
*
|
1037 |
+
* Convert SimpleXML object to array
|
1038 |
+
*
|
1039 |
+
*/
|
1040 |
+
function simplexml2array($xml) {
|
1041 |
+
if (@get_class($xml) == 'SimpleXMLElement') {
|
1042 |
+
$attributes = $xml->attributes();
|
1043 |
+
foreach($attributes as $k=>$v) {
|
1044 |
+
if ($v) $a[$k] = (string) $v;
|
1045 |
+
}
|
1046 |
+
$x = $xml;
|
1047 |
+
$xml = get_object_vars($xml);
|
1048 |
+
}
|
1049 |
+
if (is_array($xml)) {
|
1050 |
+
if (count($xml) == 0) return (string) $x; // for CDATA
|
1051 |
+
foreach($xml as $key=>$value) {
|
1052 |
+
$r[$key] = $this->simplexml2array($value);
|
1053 |
+
}
|
1054 |
+
if (isset($a)) $r['@attributes'] = $a; // Attributes
|
1055 |
+
return $r;
|
1056 |
+
}
|
1057 |
+
return (string) $xml;
|
1058 |
+
}
|
1059 |
+
|
1060 |
+
protected $_sibling_limit = 20;
|
1061 |
+
protected function get_xml_path(DOMElement $el, DOMXPath $xpath)
|
1062 |
+
{
|
1063 |
+
for($p = '', $doc = $el; $doc and ! $doc instanceof DOMDocument; $doc = $doc->parentNode) {
|
1064 |
+
if (($ind = $xpath->query('preceding-sibling::' . $doc->nodeName, $doc)->length)) {
|
1065 |
+
$p = '[' . ++$ind . ']' . $p;
|
1066 |
+
} elseif ( ! $doc->parentNode instanceof DOMDocument) {
|
1067 |
+
$p = '[' . ($ind = 1) . ']' . $p;
|
1068 |
+
}
|
1069 |
+
$p = '/' . $doc->nodeName . $p;
|
1070 |
+
}
|
1071 |
+
return $p;
|
1072 |
+
}
|
1073 |
+
|
1074 |
+
protected function shrink_xml_element(DOMElement $el)
|
1075 |
+
{
|
1076 |
+
$prev = null; $sub_ind = null;
|
1077 |
+
for ($i = 0; $i < $el->childNodes->length; $i++) {
|
1078 |
+
$child = $el->childNodes->item($i);
|
1079 |
+
if ($child instanceof DOMText) {
|
1080 |
+
if ('' == trim($child->wholeText)) {
|
1081 |
+
$el->removeChild($child);
|
1082 |
+
$i--;
|
1083 |
+
continue;
|
1084 |
+
}
|
1085 |
+
}
|
1086 |
+
if ($child instanceof DOMComment) {
|
1087 |
+
continue;
|
1088 |
+
}
|
1089 |
+
if ($prev instanceof $child and $prev->nodeName == $child->nodeName) {
|
1090 |
+
$sub_ind++;
|
1091 |
+
} else {
|
1092 |
+
if ($sub_ind > $this->_sibling_limit) {
|
1093 |
+
$el->insertBefore(new DOMComment('[pmxi_more:' . ($sub_ind - $this->_sibling_limit) . ']'), $child);
|
1094 |
+
$i++;
|
1095 |
+
}
|
1096 |
+
$sub_ind = 1;
|
1097 |
+
$prev = null;
|
1098 |
+
}
|
1099 |
+
if ($child instanceof DOMElement) {
|
1100 |
+
$prev = $child;
|
1101 |
+
if ($sub_ind <= $this->_sibling_limit) {
|
1102 |
+
$this->shrink_xml_element($child);
|
1103 |
+
} else {
|
1104 |
+
$el->removeChild($child);
|
1105 |
+
$i--;
|
1106 |
+
}
|
1107 |
+
}
|
1108 |
+
}
|
1109 |
+
if ($sub_ind > $this->_sibling_limit) {
|
1110 |
+
$el->appendChild(new DOMComment('[pmxi_more:' . ($sub_ind - $this->_sibling_limit) . ']'));
|
1111 |
+
}
|
1112 |
+
return $el;
|
1113 |
+
}
|
1114 |
+
protected function render_xml_element(DOMElement $el, $shorten = false, $path = '/', $ind = 1, $lvl = 0)
|
1115 |
+
{
|
1116 |
+
$path .= $el->nodeName;
|
1117 |
+
if ( ! $el->parentNode instanceof DOMDocument and $ind > 0) {
|
1118 |
+
$path .= "[$ind]";
|
1119 |
+
}
|
1120 |
+
|
1121 |
+
echo '<div class="xml-element lvl-' . $lvl . ' lvl-mod4-' . ($lvl % 4) . '" title="' . $path . '">';
|
1122 |
+
if ($el->hasChildNodes()) {
|
1123 |
+
$is_render_collapsed = $ind > 1;
|
1124 |
+
if ($el->childNodes->length > 1 or ! $el->childNodes->item(0) instanceof DOMText or strlen(trim($el->childNodes->item(0)->wholeText)) > 40) {
|
1125 |
+
echo '<div class="xml-expander">' . ($is_render_collapsed ? '+' : '-') . '</div>';
|
1126 |
+
}
|
1127 |
+
echo '<div class="xml-tag opening"><<span class="xml-tag-name">' . $el->nodeName . '</span>'; $this->render_xml_attributes($el, $path . '/'); echo '></div>';
|
1128 |
+
if (1 == $el->childNodes->length and $el->childNodes->item(0) instanceof DOMText) {
|
1129 |
+
$this->render_xml_text(trim($el->childNodes->item(0)->wholeText), $shorten, $is_render_collapsed);
|
1130 |
+
} else {
|
1131 |
+
echo '<div class="xml-content' . ($is_render_collapsed ? ' collapsed' : '') . '">';
|
1132 |
+
$indexes = array();
|
1133 |
+
foreach ($el->childNodes as $child) {
|
1134 |
+
if ($child instanceof DOMElement) {
|
1135 |
+
empty($indexes[$child->nodeName]) and $indexes[$child->nodeName] = 0; $indexes[$child->nodeName]++;
|
1136 |
+
$this->render_xml_element($child, $shorten, $path . '/', $indexes[$child->nodeName], $lvl + 1);
|
1137 |
+
} elseif ($child instanceof DOMText) {
|
1138 |
+
$this->render_xml_text(trim($child->wholeText), $shorten);
|
1139 |
+
} elseif ($child instanceof DOMComment) {
|
1140 |
+
if (preg_match('%\[pmxi_more:(\d+)\]%', $child->nodeValue, $mtch)) {
|
1141 |
+
$no = intval($mtch[1]);
|
1142 |
+
echo '<div class="xml-more">[ ⇓ ' . sprintf(__('<strong>%s</strong> %s more', 'pmxi_plugin'), $no, _n('element', 'elements', $no, 'pmxi_plugin')) . ' ⇓ ]</div>';
|
1143 |
+
}
|
1144 |
+
}
|
1145 |
+
}
|
1146 |
+
echo '</div>';
|
1147 |
+
}
|
1148 |
+
echo '<div class="xml-tag closing"></<span class="xml-tag-name">' . $el->nodeName . '</span>></div>';
|
1149 |
+
} else {
|
1150 |
+
echo '<div class="xml-tag opening empty"><<span class="xml-tag-name">' . $el->nodeName . '</span>'; $this->render_xml_attributes($el); echo '/></div>';
|
1151 |
+
}
|
1152 |
+
echo '</div>';
|
1153 |
+
}
|
1154 |
+
protected function render_xml_text($text, $shorten = false, $is_render_collapsed = false)
|
1155 |
+
{
|
1156 |
+
if (empty($text)) {
|
1157 |
+
return; // do not display empty text nodes
|
1158 |
+
}
|
1159 |
+
if (preg_match('%\[more:(\d+)\]%', $text, $mtch)) {
|
1160 |
+
$no = intval($mtch[1]);
|
1161 |
+
echo '<div class="xml-more">[ ⇓ ' . sprintf(__('<strong>%s</strong> %s more', 'pmxi_plugin'), $no, _n('element', 'elements', $no, 'pmxi_plugin')) . ' ⇓ ]</div>';
|
1162 |
+
return;
|
1163 |
+
}
|
1164 |
+
$more = '';
|
1165 |
+
if ($shorten and preg_match('%^(.*?\s+){20}(?=\S)%', $text, $mtch)) {
|
1166 |
+
$text = $mtch[0];
|
1167 |
+
$more = '<span class="xml-more">[' . __('more', 'pmxi_plugin') . ']</span>';
|
1168 |
+
}
|
1169 |
+
$is_short = strlen($text) <= 40;
|
1170 |
+
$text = esc_html($text);
|
1171 |
+
$text = preg_replace('%(?<!\s)\b(?!\s|\W[\w\s])|\w{20}%', '$0​', $text); // put explicit breaks for xml content to wrap
|
1172 |
+
echo '<div class="xml-content textonly' . ($is_short ? ' short' : '') . ($is_render_collapsed ? ' collapsed' : '') . '">' . $text . $more . '</div>';
|
1173 |
+
}
|
1174 |
+
protected function render_xml_attributes(DOMElement $el, $path = '/')
|
1175 |
+
{
|
1176 |
+
foreach ($el->attributes as $attr) {
|
1177 |
+
echo ' <span class="xml-attr" title="' . $path . '@' . $attr->nodeName . '"><span class="xml-attr-name">' . $attr->nodeName . '</span>=<span class="xml-attr-value">"' . esc_attr($attr->value) . '"</span></span>';
|
1178 |
+
}
|
1179 |
+
}
|
1180 |
+
|
1181 |
+
protected function render_xml_element_table(DOMElement $el, $shorten = false, $path = '/', $ind = 0, $lvl = 0)
|
1182 |
+
{
|
1183 |
+
$path .= $el->nodeName;
|
1184 |
+
if ($ind > 0) {
|
1185 |
+
$path .= "[$ind]";
|
1186 |
+
}
|
1187 |
+
|
1188 |
+
$is_render_collapsed = $ind > 1;
|
1189 |
+
echo '<tr class="xml-element lvl-' . $lvl . ($is_render_collapsed ? ' collapsed' : '') . '" title="' . $path . '">';
|
1190 |
+
echo '<td style="padding-left:' . ($lvl + 1) * 15 . 'px">';
|
1191 |
+
$is_inline = true;
|
1192 |
+
if ( ! (0 == $el->attributes->length and 1 == $el->childNodes->length and $el->childNodes->item(0) instanceof DOMText and strlen($el->childNodes->item(0)->wholeText) <= 40)) {
|
1193 |
+
$is_inline = false;
|
1194 |
+
echo '<div class="xml-expander">' . ($is_render_collapsed ? '+' : '-') . '</div>';
|
1195 |
+
}
|
1196 |
+
echo '<div class="xml-tag opening"><span class="xml-tag-name">' . $el->nodeName . '</span></div>';
|
1197 |
+
echo '</td>';
|
1198 |
+
echo '<td>';
|
1199 |
+
$is_inline and $this->render_xml_text_table(trim($el->childNodes->item(0)->wholeText), $shorten, NULL, NULL, $is_inline = true);
|
1200 |
+
echo '</td>';
|
1201 |
+
echo '</tr>';
|
1202 |
+
if ( ! $is_inline) {
|
1203 |
+
echo '<tr class="xml-content' . ($is_render_collapsed ? ' collapsed' : '') . '">';
|
1204 |
+
echo '<td colspan="2">';
|
1205 |
+
echo '<table>';
|
1206 |
+
$this->render_xml_attributes_table($el, $path . '/', $lvl + 1);
|
1207 |
+
$indexes = array();
|
1208 |
+
foreach ($el->childNodes as $child) {
|
1209 |
+
if ($child instanceof DOMElement) {
|
1210 |
+
empty($indexes[$child->nodeName]) and $indexes[$child->nodeName] = 1;
|
1211 |
+
$this->render_xml_element_table($child, $shorten, $path . '/', $indexes[$child->nodeName]++, $lvl + 1);
|
1212 |
+
} elseif ($child instanceof DOMText) {
|
1213 |
+
$this->render_xml_text_table(trim($child->wholeText), $shorten, $path . '/', $lvl + 1);
|
1214 |
+
}
|
1215 |
+
}
|
1216 |
+
echo '</table>';
|
1217 |
+
echo '</td>';
|
1218 |
+
echo '</tr>';
|
1219 |
+
}
|
1220 |
+
}
|
1221 |
+
protected function render_xml_text_table($text, $shorten = false, $path = '/', $lvl = 0, $is_inline = false)
|
1222 |
+
{
|
1223 |
+
if (empty($text)) {
|
1224 |
+
return; // do not display empty text nodes
|
1225 |
+
}
|
1226 |
+
$more = '';
|
1227 |
+
if ($shorten and preg_match('%^(.*?\s+){20}(?=\S)%', $text, $mtch)) {
|
1228 |
+
$text = $mtch[0];
|
1229 |
+
$more = '<span class="xml-more">[' . __('more', 'pmxi_plugin') . ']</span>';
|
1230 |
+
}
|
1231 |
+
$is_short = strlen($text) <= 40;
|
1232 |
+
$text = esc_html($text);
|
1233 |
+
$text = preg_replace('%(?<!\s)\b(?!\s|\W[\w\s])|\w{20}%', '$0​', $text); // put explicit breaks for xml content to wrap
|
1234 |
+
if ($is_inline) {
|
1235 |
+
echo $text . $more;
|
1236 |
+
} else {
|
1237 |
+
echo '<tr class="xml-content-tr textonly lvl-' . $lvl . ($is_short ? ' short' : '') . '" title="' . $path . 'text()">';
|
1238 |
+
echo '<td style="padding-left:' . ($lvl + 1) * 15 . 'px"><span class="xml-attr-name">text</span></td>';
|
1239 |
+
echo '<td>' . $text . $more . '</td>';
|
1240 |
+
echo '</tr>';
|
1241 |
+
}
|
1242 |
+
}
|
1243 |
+
protected function render_xml_attributes_table(DOMElement $el, $path = '/', $lvl = 0)
|
1244 |
+
{
|
1245 |
+
foreach ($el->attributes as $attr) {
|
1246 |
+
echo '<tr class="xml-attr lvl-' . $lvl . '" title="' . $path . '@' . $attr->nodeName . '">';
|
1247 |
+
echo '<td style="padding-left:' . ($lvl + 1) * 15 . 'px"><span class="xml-attr-name">@' . $attr->nodeName . '</span></td>';
|
1248 |
+
echo '<td><span class="xml-attr-value">' . esc_attr($attr->value) . '</span></td>';
|
1249 |
+
echo '</tr>';
|
1250 |
+
}
|
1251 |
+
}
|
1252 |
+
|
1253 |
+
protected function xml_find_repeating(DOMElement $el, $path = '/')
|
1254 |
+
{
|
1255 |
+
$path .= $el->nodeName;
|
1256 |
+
if ( ! $el->parentNode instanceof DOMDocument) {
|
1257 |
+
$path .= '[1]';
|
1258 |
+
}
|
1259 |
+
$children = array();
|
1260 |
+
foreach ($el->childNodes as $child) {
|
1261 |
+
if ($child instanceof DOMElement) {
|
1262 |
+
if ( ! empty($children[$child->nodeName])) {
|
1263 |
+
return $path . '/' . $child->nodeName;
|
1264 |
+
} else {
|
1265 |
+
$children[$child->nodeName] = true;
|
1266 |
+
}
|
1267 |
+
}
|
1268 |
+
}
|
1269 |
+
// reaching this point means we didn't find anything among current element children, so recursively ask children to find something in them
|
1270 |
+
foreach ($el->childNodes as $child) {
|
1271 |
+
if ($child instanceof DOMElement) {
|
1272 |
+
$result = $this->xml_find_repeating($child, $path . '/');
|
1273 |
+
if ($result) {
|
1274 |
+
return $result;
|
1275 |
+
}
|
1276 |
+
}
|
1277 |
+
}
|
1278 |
+
// reaching this point means we didn't find anything, so return element itself if the function was called for it
|
1279 |
+
if ('/' . $el->nodeName == $path) {
|
1280 |
+
return $path;
|
1281 |
+
}
|
1282 |
+
|
1283 |
+
return NULL;
|
1284 |
+
}
|
1285 |
+
/*
|
1286 |
+
* Convert csv to xml using yahoo API
|
1287 |
+
*/
|
1288 |
+
protected function csv_to_xml($csv_url){
|
1289 |
+
|
1290 |
+
include_once(PMXI_Plugin::ROOT_DIR.'/libraries/XmlImportCsvParse.php');
|
1291 |
+
|
1292 |
+
$csv = new PMXI_CsvParser($csv_url);
|
1293 |
+
|
1294 |
+
return $csv->toXML();
|
1295 |
+
|
1296 |
+
}
|
1297 |
+
/*
|
1298 |
+
*
|
1299 |
+
* Detect CSV file
|
1300 |
+
*
|
1301 |
+
*/
|
1302 |
+
protected function detect_csv($type){
|
1303 |
+
return in_array($type, $this->csv_mimes);
|
1304 |
+
}
|
1305 |
+
}
|
controllers/admin/manage.php
ADDED
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Manage Imports
|
4 |
+
*
|
5 |
+
* @author Pavel Kulbakin <p.kulbakin@gmail.com>
|
6 |
+
*/
|
7 |
+
class PMXI_Admin_Manage extends PMXI_Controller_Admin {
|
8 |
+
|
9 |
+
public function init() {
|
10 |
+
parent::init();
|
11 |
+
|
12 |
+
if ('update' == PMXI_Plugin::getInstance()->getAdminCurrentScreen()->action) {
|
13 |
+
$this->isInline = true;
|
14 |
+
if ( ! session_id()) session_start(); // prevent session initialization throw a notification in inline mode of delegated plugin
|
15 |
+
}
|
16 |
+
}
|
17 |
+
|
18 |
+
/**
|
19 |
+
* Previous Imports list
|
20 |
+
*/
|
21 |
+
public function index() {
|
22 |
+
|
23 |
+
$get = $this->input->get(array(
|
24 |
+
's' => '',
|
25 |
+
'order_by' => 'name',
|
26 |
+
'order' => 'ASC',
|
27 |
+
'pagenum' => 1,
|
28 |
+
'perPage' => 10,
|
29 |
+
));
|
30 |
+
$get['pagenum'] = absint($get['pagenum']);
|
31 |
+
extract($get);
|
32 |
+
$this->data += $get;
|
33 |
+
|
34 |
+
$list = new PMXI_Import_List();
|
35 |
+
$post = new PMXI_Post_Record();
|
36 |
+
$by = NULL;
|
37 |
+
if ('' != $s) {
|
38 |
+
$like = '%' . preg_replace('%\s+%', '%', preg_replace('/[%?]/', '\\\\$0', $s)) . '%';
|
39 |
+
$by[] = array(array('name LIKE' => $like, 'type LIKE' => $like, 'path LIKE' => $like), 'OR');
|
40 |
+
}
|
41 |
+
|
42 |
+
$this->data['list'] = $list->join($post->getTable(), $list->getTable() . '.id = ' . $post->getTable() . '.import_id', 'LEFT')
|
43 |
+
->setColumns(
|
44 |
+
$list->getTable() . '.*',
|
45 |
+
'COUNT(' . $post->getTable() . '.post_id' . ') AS post_count'
|
46 |
+
)
|
47 |
+
->getBy($by, "$order_by $order", $pagenum, $perPage, $list->getTable() . '.id');
|
48 |
+
|
49 |
+
$this->data['page_links'] = paginate_links(array(
|
50 |
+
'base' => add_query_arg('pagenum', '%#%', $this->baseUrl),
|
51 |
+
'format' => '',
|
52 |
+
'prev_text' => __('«', 'pmxi_plugin'),
|
53 |
+
'next_text' => __('»', 'pmxi_plugin'),
|
54 |
+
'total' => ceil($list->total() / $perPage),
|
55 |
+
'current' => $pagenum,
|
56 |
+
));
|
57 |
+
|
58 |
+
$this->render();
|
59 |
+
}
|
60 |
+
|
61 |
+
/**
|
62 |
+
* Delete an import
|
63 |
+
*/
|
64 |
+
public function delete() {
|
65 |
+
$id = $this->input->get('id');
|
66 |
+
$this->data['item'] = $item = new PMXI_Import_Record();
|
67 |
+
if ( ! $id or $item->getById($id)->isEmpty()) {
|
68 |
+
wp_redirect($this->baseUrl); die();
|
69 |
+
}
|
70 |
+
|
71 |
+
if ($this->input->post('is_confirmed')) {
|
72 |
+
check_admin_referer('delete-import', '_wpnonce_delete-import');
|
73 |
+
|
74 |
+
$item->delete( ! $this->input->post('is_delete_posts'));
|
75 |
+
wp_redirect(add_query_arg('pmxi_nt', urlencode(__('Import deleted', 'pmxi_plugin')), $this->baseUrl)); die();
|
76 |
+
}
|
77 |
+
|
78 |
+
$this->render();
|
79 |
+
}
|
80 |
+
|
81 |
+
/**
|
82 |
+
* Bulk actions
|
83 |
+
*/
|
84 |
+
public function bulk() {
|
85 |
+
check_admin_referer('bulk-imports', '_wpnonce_bulk-imports');
|
86 |
+
if ($this->input->post('doaction2')) {
|
87 |
+
$this->data['action'] = $action = $this->input->post('bulk-action2');
|
88 |
+
} else {
|
89 |
+
$this->data['action'] = $action = $this->input->post('bulk-action');
|
90 |
+
}
|
91 |
+
$this->data['ids'] = $ids = $this->input->post('items');
|
92 |
+
$this->data['items'] = $items = new PMXI_Import_List();
|
93 |
+
if (empty($action) or ! in_array($action, array('delete')) or empty($ids) or $items->getBy('id', $ids)->isEmpty()) {
|
94 |
+
wp_redirect($this->baseUrl); die();
|
95 |
+
}
|
96 |
+
|
97 |
+
if ($this->input->post('is_confirmed')) {
|
98 |
+
$is_delete_posts = $this->input->post('is_delete_posts');
|
99 |
+
foreach($items->convertRecords() as $item) {
|
100 |
+
$item->delete( ! $is_delete_posts);
|
101 |
+
}
|
102 |
+
|
103 |
+
wp_redirect(add_query_arg('pmxi_nt', urlencode(sprintf(__('<strong>%d</strong> %s deleted', 'pmxi_plugin'), $items->count(), _n('import', 'imports', $items->count(), 'pmxi_plugin'))), $this->baseUrl)); die();
|
104 |
+
}
|
105 |
+
|
106 |
+
$this->render();
|
107 |
+
}
|
108 |
+
}
|
controllers/admin/settings.php
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Admin Statistics page
|
4 |
+
*
|
5 |
+
* @author Pavel Kulbakin <p.kulbakin@gmail.com>
|
6 |
+
*/
|
7 |
+
class PMXI_Admin_Settings extends PMXI_Controller_Admin {
|
8 |
+
|
9 |
+
public function index() {
|
10 |
+
$this->data['post'] = $post = $this->input->post(PMXI_Plugin::getInstance()->getOption());
|
11 |
+
|
12 |
+
if ($this->input->post('is_settings_submitted')) { // save settings form
|
13 |
+
check_admin_referer('edit-settings', '_wpnonce_edit-settings');
|
14 |
+
|
15 |
+
if ( ! preg_match('%^\d+$%', $post['history_file_count'])) {
|
16 |
+
$this->errors->add('form-validation', __('History File Count must be a non-negative integer', 'pmxi_plugin'));
|
17 |
+
}
|
18 |
+
if ( ! preg_match('%^\d+$%', $post['history_file_age'])) {
|
19 |
+
$this->errors->add('form-validation', __('History Age must be a non-negative integer', 'pmxi_plugin'));
|
20 |
+
}
|
21 |
+
if (empty($post['html_entities'])) $post['html_entities'] = 0;
|
22 |
+
|
23 |
+
if ( ! $this->errors->get_error_codes()) { // no validation errors detected
|
24 |
+
|
25 |
+
PMXI_Plugin::getInstance()->updateOption($post);
|
26 |
+
$files = new PMXI_File_List(); $files->sweepHistory(); // adjust file history to new settings specified
|
27 |
+
|
28 |
+
wp_redirect(add_query_arg('pmxi_nt', urlencode(__('Settings saved', 'pmxi_plugin')), $this->baseUrl)); die();
|
29 |
+
}
|
30 |
+
}
|
31 |
+
|
32 |
+
if ($this->input->post('is_templates_submitted')) { // delete templates form
|
33 |
+
$templates_ids = $this->input->post('templates', array());
|
34 |
+
if (empty($templates_ids)) {
|
35 |
+
$this->errors->add('form-validation', __('Templates must be selected', 'pmxi_plugin'));
|
36 |
+
}
|
37 |
+
if ( ! $this->errors->get_error_codes()) { // no validation errors detected
|
38 |
+
$template = new PMXI_Template_Record();
|
39 |
+
foreach ($templates_ids as $template_id) {
|
40 |
+
$template->clear()->set('id', $template_id)->delete();
|
41 |
+
}
|
42 |
+
wp_redirect(add_query_arg('pmxi_nt', urlencode(sprintf(_n('%d template deleted', '%d templates deleted', count($templates_ids), 'pmxi_plugin'), count($templates_ids))), $this->baseUrl)); die();
|
43 |
+
}
|
44 |
+
}
|
45 |
+
|
46 |
+
$this->render();
|
47 |
+
}
|
48 |
+
|
49 |
+
public function dismiss(){
|
50 |
+
|
51 |
+
PMXI_Plugin::getInstance()->updateOption("dismiss", 1);
|
52 |
+
|
53 |
+
exit('OK');
|
54 |
+
}
|
55 |
+
|
56 |
+
}
|
controllers/controller.php
ADDED
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Common logic for all shortcodes plugin implements
|
4 |
+
*
|
5 |
+
* @author Pavel Kulbakin <p.kulbakin@gmail.com>
|
6 |
+
*/
|
7 |
+
abstract class PMXI_Controller {
|
8 |
+
/**
|
9 |
+
* Input class instance to retrieve parameters submitted during page request
|
10 |
+
* @var PMXI_Input
|
11 |
+
*/
|
12 |
+
protected $input;
|
13 |
+
/**
|
14 |
+
* Error messages
|
15 |
+
* @var WP_Error
|
16 |
+
*/
|
17 |
+
protected $errors;
|
18 |
+
/**
|
19 |
+
* Associative array of data which will be automatically available as variables when template is rendered
|
20 |
+
* @var array
|
21 |
+
*/
|
22 |
+
public $data = array();
|
23 |
+
/**
|
24 |
+
* Constructor
|
25 |
+
*/
|
26 |
+
public function __construct() {
|
27 |
+
$this->input = new PMXI_Input();
|
28 |
+
$this->input->addFilter('trim');
|
29 |
+
|
30 |
+
$this->errors = new WP_Error();
|
31 |
+
|
32 |
+
$this->init();
|
33 |
+
}
|
34 |
+
|
35 |
+
/**
|
36 |
+
* Method to put controller initialization logic to
|
37 |
+
*/
|
38 |
+
protected function init() {}
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Checks wether protocol is HTTPS and redirects user to secure connection if not
|
42 |
+
*/
|
43 |
+
protected function force_ssl() {
|
44 |
+
if (force_ssl_admin() && ! is_ssl()) {
|
45 |
+
if ( 0 === strpos($_SERVER['REQUEST_URI'], 'http') ) {
|
46 |
+
wp_redirect(preg_replace('|^http://|', 'https://', $_SERVER['REQUEST_URI'])); die();
|
47 |
+
} else {
|
48 |
+
wp_redirect('https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']); die();
|
49 |
+
}
|
50 |
+
}
|
51 |
+
}
|
52 |
+
|
53 |
+
/**
|
54 |
+
* Method returning resolved template content
|
55 |
+
*
|
56 |
+
* @param string[optional] $viewPath Template path to render
|
57 |
+
*/
|
58 |
+
protected function render($viewPath = null) {
|
59 |
+
// assume template file name depending on calling function
|
60 |
+
if (is_null($viewPath)) {
|
61 |
+
$trace = debug_backtrace();
|
62 |
+
$viewPath = str_replace('_', '/', preg_replace('%^' . preg_quote(PMXI_Plugin::PREFIX, '%') . '%', '', strtolower($trace[1]['class']))) . '/' . $trace[1]['function'];
|
63 |
+
}
|
64 |
+
// append file extension if not specified
|
65 |
+
if ( ! preg_match('%\.php$%', $viewPath)) {
|
66 |
+
$viewPath .= '.php';
|
67 |
+
}
|
68 |
+
$filePath = PMXI_Plugin::ROOT_DIR . '/views/' . $viewPath;
|
69 |
+
if (is_file($filePath)) {
|
70 |
+
extract($this->data);
|
71 |
+
include $filePath;
|
72 |
+
} else {
|
73 |
+
throw new Exception("Requested template file $filePath is not found.");
|
74 |
+
}
|
75 |
+
}
|
76 |
+
|
77 |
+
/**
|
78 |
+
* Display list of errors
|
79 |
+
*
|
80 |
+
* @param string|array|WP_Error[optional] $msgs
|
81 |
+
*/
|
82 |
+
protected function error($msgs = NULL) {
|
83 |
+
if (is_null($msgs)) {
|
84 |
+
$msgs = $this->errors;
|
85 |
+
}
|
86 |
+
if (is_wp_error($msgs)) {
|
87 |
+
$msgs = $msgs->get_error_messages();
|
88 |
+
}
|
89 |
+
if ( ! is_array($msgs)) {
|
90 |
+
$msgs = array($msgs);
|
91 |
+
}
|
92 |
+
$this->data['errors'] = $msgs;
|
93 |
+
|
94 |
+
$viewPathRel = str_replace('_', '/', preg_replace('%^' . preg_quote(PMXI_Plugin::PREFIX, '%') . '%', '', strtolower(get_class($this)))) . '/error.php';
|
95 |
+
if (is_file(PMXI_Plugin::ROOT_DIR . '/views/' . $viewPathRel)) { // if calling controller class has specific error view
|
96 |
+
$this->render($viewPathRel);
|
97 |
+
} else { // render default error view
|
98 |
+
$this->render('controller/error.php');
|
99 |
+
}
|
100 |
+
}
|
101 |
+
|
102 |
+
}
|
controllers/controller/admin.php
ADDED
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Introduce special type for controllers which render pages inside admin area
|
4 |
+
*
|
5 |
+
* @author Pavel Kulbakin <p.kulbakin@gmail.com>
|
6 |
+
*/
|
7 |
+
abstract class PMXI_Controller_Admin extends PMXI_Controller {
|
8 |
+
/**
|
9 |
+
* Admin page base url (request url without all get parameters but `page`)
|
10 |
+
* @var string
|
11 |
+
*/
|
12 |
+
public $baseUrl;
|
13 |
+
/**
|
14 |
+
* Parameters which is left when baseUrl is detected
|
15 |
+
* @var array
|
16 |
+
*/
|
17 |
+
public $baseUrlParamNames = array('page', 'pagenum', 'order', 'order_by', 'type', 's', 'f');
|
18 |
+
/**
|
19 |
+
* Whether controller is rendered inside wordpress page
|
20 |
+
* @var bool
|
21 |
+
*/
|
22 |
+
public $isInline = false;
|
23 |
+
/**
|
24 |
+
* Constructor
|
25 |
+
*/
|
26 |
+
public function __construct() {
|
27 |
+
$remove = array_diff(array_keys($_GET), $this->baseUrlParamNames);
|
28 |
+
if ($remove) {
|
29 |
+
$this->baseUrl = remove_query_arg($remove);
|
30 |
+
} else {
|
31 |
+
$this->baseUrl = $_SERVER['REQUEST_URI'];
|
32 |
+
}
|
33 |
+
parent::__construct();
|
34 |
+
|
35 |
+
// add special filter for url fields
|
36 |
+
$this->input->addFilter(create_function('$str', 'return "http://" == $str || "ftp://" == $str ? "" : $str;'));
|
37 |
+
|
38 |
+
// enqueue required sripts and styles
|
39 |
+
global $wp_styles;
|
40 |
+
if ( ! is_a($wp_styles, 'WP_Styles'))
|
41 |
+
$wp_styles = new WP_Styles();
|
42 |
+
|
43 |
+
wp_enqueue_style('jquery-ui', PMXI_Plugin::getInstance()->getRelativePath() . '/static/js/jquery/css/smoothness/jquery-ui.css');
|
44 |
+
wp_enqueue_style('jquery-tipsy', PMXI_Plugin::getInstance()->getRelativePath() . '/static/js/jquery/css/smoothness/jquery.tipsy.css');
|
45 |
+
wp_enqueue_style('pmxi-admin-style', PMXI_Plugin::getInstance()->getRelativePath() . '/static/css/admin.css');
|
46 |
+
wp_enqueue_style('pmxi-admin-style-ie', PMXI_Plugin::getInstance()->getRelativePath() . '/static/css/admin-ie.css');
|
47 |
+
$wp_styles->add_data('pmxi-admin-style-ie', 'conditional', 'lte IE 7');
|
48 |
+
|
49 |
+
$scheme_color = get_user_option('admin_color') and is_file(PMXI_Plugin::ROOT_DIR . '/static/css/admin-colors-' . $scheme_color . '.css') or $scheme_color = 'fresh';
|
50 |
+
if (is_file(PMXI_Plugin::ROOT_DIR . '/static/css/admin-colors-' . $scheme_color . '.css')) {
|
51 |
+
wp_enqueue_style('pmxi-admin-style-color', PMXI_Plugin::getInstance()->getRelativePath() . '/static/css/admin-colors-' . $scheme_color . '.css');
|
52 |
+
}
|
53 |
+
|
54 |
+
wp_enqueue_script('jquery-ui-datepicker', PMXI_Plugin::getInstance()->getRelativePath() . '/static/js/jquery/ui.datepicker.js', 'jquery-ui-core');
|
55 |
+
wp_enqueue_script('jquery-ui-autocomplete', PMXI_Plugin::getInstance()->getRelativePath() . '/static/js/jquery/ui.autocomplete.js', array('jquery-ui-core', 'jquery-ui-widget', 'jquery-ui-position'));
|
56 |
+
wp_enqueue_script('jquery-tipsy', PMXI_Plugin::getInstance()->getRelativePath() . '/static/js/jquery/jquery.tipsy.js', 'jquery');
|
57 |
+
|
58 |
+
wp_enqueue_script('pmxi-admin-script', PMXI_Plugin::getInstance()->getRelativePath() . '/static/js/admin.js', array('jquery', 'jquery-ui-dialog', 'jquery-ui-datepicker', 'jquery-ui-draggable', 'jquery-ui-droppable'));
|
59 |
+
|
60 |
+
}
|
61 |
+
|
62 |
+
/**
|
63 |
+
* @see Controller::render()
|
64 |
+
*/
|
65 |
+
protected function render($viewPath = NULL)
|
66 |
+
{
|
67 |
+
// assume template file name depending on calling function
|
68 |
+
if (is_null($viewPath)) {
|
69 |
+
$trace = debug_backtrace();
|
70 |
+
$viewPath = str_replace('_', '/', preg_replace('%^' . preg_quote(PMXI_Plugin::PREFIX, '%') . '%', '', strtolower($trace[1]['class']))) . '/' . $trace[1]['function'];
|
71 |
+
}
|
72 |
+
|
73 |
+
// render contextual help automatically
|
74 |
+
$viewHelpPath = $viewPath;
|
75 |
+
// append file extension if not specified
|
76 |
+
if ( ! preg_match('%\.php$%', $viewHelpPath)) {
|
77 |
+
$viewHelpPath .= '.php';
|
78 |
+
}
|
79 |
+
$viewHelpPath = preg_replace('%\.php$%', '-help.php', $viewHelpPath);
|
80 |
+
$fileHelpPath = PMXI_Plugin::ROOT_DIR . '/views/' . $viewHelpPath;
|
81 |
+
|
82 |
+
if (is_file($fileHelpPath)) { // there is help file defined
|
83 |
+
ob_start();
|
84 |
+
include $fileHelpPath;
|
85 |
+
add_contextual_help(PMXI_Plugin::getInstance()->getAdminCurrentScreen()->id, ob_get_clean());
|
86 |
+
}
|
87 |
+
|
88 |
+
parent::render($viewPath);
|
89 |
+
}
|
90 |
+
|
91 |
+
}
|
helpers/backward.php
ADDED
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Contains function which were introduced in late wordpress versions
|
4 |
+
*/
|
5 |
+
|
6 |
+
if ( ! function_exists('is_network_admin')):
|
7 |
+
/**
|
8 |
+
* Whether the current request is for a network admin screen /wp-admin/network/
|
9 |
+
*
|
10 |
+
* Does not inform on whether the user is a network admin! Use capability checks to
|
11 |
+
* tell if the user should be accessing a section or not.
|
12 |
+
*
|
13 |
+
* @since 3.1.0
|
14 |
+
*
|
15 |
+
* @return bool True if inside WordPress network administration pages.
|
16 |
+
*/
|
17 |
+
function is_network_admin() {
|
18 |
+
if ( defined( 'WP_NETWORK_ADMIN' ) )
|
19 |
+
return WP_NETWORK_ADMIN;
|
20 |
+
return false;
|
21 |
+
}
|
22 |
+
endif;
|
23 |
+
|
24 |
+
if ( ! function_exists('is_user_admin')):
|
25 |
+
/**
|
26 |
+
* Whether the current request is for a user admin screen /wp-admin/user/
|
27 |
+
*
|
28 |
+
* Does not inform on whether the user is an admin! Use capability checks to
|
29 |
+
* tell if the user should be accessing a section or not.
|
30 |
+
*
|
31 |
+
* @since 3.1.0
|
32 |
+
*
|
33 |
+
* @return bool True if inside WordPress user administration pages.
|
34 |
+
*/
|
35 |
+
function is_user_admin() {
|
36 |
+
if ( defined( 'WP_USER_ADMIN' ) )
|
37 |
+
return WP_USER_ADMIN;
|
38 |
+
return false;
|
39 |
+
}
|
40 |
+
endif;
|
helpers/get_taxonomies_by_object_type.php
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( ! function_exists('get_taxonomies_by_object_type')):
|
3 |
+
/**
|
4 |
+
* get_taxnomies doesn't filter propery by object_type, so these function can be used when filtering by object type requied
|
5 |
+
* @param string|array $object_type
|
6 |
+
* @param string[optional] $output
|
7 |
+
*/
|
8 |
+
function get_taxonomies_by_object_type($object_type, $output = 'names') {
|
9 |
+
global $wp_taxonomies;
|
10 |
+
|
11 |
+
is_array($object_type) or $object_type = array($object_type);
|
12 |
+
$field = ('names' == $output) ? 'name' : false;
|
13 |
+
$filtered = array();
|
14 |
+
foreach ($wp_taxonomies as $key => $obj) {
|
15 |
+
if (array_intersect($object_type, $obj->object_type)) {
|
16 |
+
$filtered[$key] = $obj;
|
17 |
+
}
|
18 |
+
}
|
19 |
+
if ($field) {
|
20 |
+
$filtered = wp_list_pluck($filtered, $field);
|
21 |
+
}
|
22 |
+
return $filtered;
|
23 |
+
}
|
24 |
+
|
25 |
+
endif;
|
helpers/str_getcsv.php
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( ! function_exists('str_getcsv')):
|
3 |
+
/**
|
4 |
+
* str_getcsv function for PHP less than 5.3
|
5 |
+
* @see http://php.net/manual/en/function.str-getcsv.php
|
6 |
+
* NOTE: function doesn't support escape paramter (in correspondance to fgetcsv not supporting it prior 5.3)
|
7 |
+
*/
|
8 |
+
function str_getcsv($input, $delimiter=',', $enclosure='"') {
|
9 |
+
$temp = fopen("php://memory", "rw");
|
10 |
+
fwrite($temp, $input);
|
11 |
+
fseek($temp, 0);
|
12 |
+
$r = fgetcsv($temp, strlen($input), $delimiter, $enclosure);
|
13 |
+
fclose($temp);
|
14 |
+
return $r;
|
15 |
+
}
|
16 |
+
|
17 |
+
endif;
|
helpers/wp_delete_attachments.php
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Delete attachments linked to a specified post
|
4 |
+
* @param int $parent_id Parent id of post to delete attachments for
|
5 |
+
*/
|
6 |
+
function wp_delete_attachments($parent_id) {
|
7 |
+
foreach (get_posts(array('post_parent' => $parent_id, 'post_type' => 'attachment', 'numberposts' => -1, 'post_status' => null)) as $attach) {
|
8 |
+
wp_delete_attachment($attach->ID, true);
|
9 |
+
}
|
10 |
+
}
|
helpers/wp_redirect_or_javascript.php
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( ! function_exists('wp_redirect_or_javascript')):
|
3 |
+
/**
|
4 |
+
* For AJAX request outputs javascript specified, otherwise acts like wp_redirect
|
5 |
+
* @param string $location
|
6 |
+
* @param string[optional] $javascript
|
7 |
+
* @param int[optional] $status
|
8 |
+
*/
|
9 |
+
function wp_redirect_or_javascript($location, $javascript = NULL, $status = 302) {
|
10 |
+
if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) and strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') {
|
11 |
+
is_null($javascript) and $javascript = 'location.href="' . addslashes($location) . '";';
|
12 |
+
echo '<script type="text/javascript">' . $javascript . '</script>';
|
13 |
+
} else {
|
14 |
+
return wp_redirect($location, $status);
|
15 |
+
}
|
16 |
+
}
|
17 |
+
endif;
|
libraries/XmlImportConfig.php
ADDED
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
|
4 |
+
* @author Pavel Kulbakin <p.kulbakin@gmail.com>
|
5 |
+
* @package General
|
6 |
+
*/
|
7 |
+
|
8 |
+
/**
|
9 |
+
* This class is used for different XmlImport settings
|
10 |
+
*/
|
11 |
+
if (!class_exists('XmlImportConfig'))
|
12 |
+
{
|
13 |
+
final class XmlImportConfig
|
14 |
+
{
|
15 |
+
/**
|
16 |
+
* Singleton instance
|
17 |
+
* @var XmlImportConfig
|
18 |
+
*/
|
19 |
+
private static $instance = null;
|
20 |
+
/**
|
21 |
+
* Path to cache directory
|
22 |
+
* @var string
|
23 |
+
*/
|
24 |
+
private $cache_dir;
|
25 |
+
/**
|
26 |
+
* String to use when concatenating result of xpath corresponding to multiple elements
|
27 |
+
* @var string
|
28 |
+
*/
|
29 |
+
private $multi_glue;
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Initial settings
|
33 |
+
*/
|
34 |
+
private function init()
|
35 |
+
{
|
36 |
+
$this->setCacheDirectory(dirname(__FILE__) . '/cache');
|
37 |
+
$this->setMultiGlue(', ');
|
38 |
+
}
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Gets instance of a singleton class
|
42 |
+
* @return XmlImportConfig
|
43 |
+
*/
|
44 |
+
public static function getInstance()
|
45 |
+
{
|
46 |
+
if (is_null(self::$instance)) {
|
47 |
+
self::$instance = new self;
|
48 |
+
self::$instance->init();
|
49 |
+
}
|
50 |
+
return self::$instance;
|
51 |
+
}
|
52 |
+
|
53 |
+
/**
|
54 |
+
* Returns path to cache directory
|
55 |
+
* @return string
|
56 |
+
*/
|
57 |
+
public function getCacheDirectory()
|
58 |
+
{
|
59 |
+
return $this->cache_dir;
|
60 |
+
}
|
61 |
+
|
62 |
+
/**
|
63 |
+
* Sets path to cache directory
|
64 |
+
* @param string $cacheDirectoryPath
|
65 |
+
*/
|
66 |
+
public function setCacheDirectory($cacheDirectoryPath)
|
67 |
+
{
|
68 |
+
$this->cache_dir = $cacheDirectoryPath;
|
69 |
+
}
|
70 |
+
|
71 |
+
/**
|
72 |
+
* Returns string glue to use when concatenating multiple elements
|
73 |
+
* @return string
|
74 |
+
*/
|
75 |
+
public function getMultiGlue()
|
76 |
+
{
|
77 |
+
return $this->multi_glue;
|
78 |
+
}
|
79 |
+
/**
|
80 |
+
* Sets string glue to use when concatenating multiple element
|
81 |
+
* @param unknown_type $glue
|
82 |
+
*/
|
83 |
+
public function setMultiGlue($glue)
|
84 |
+
{
|
85 |
+
$this->multi_glue = $glue;
|
86 |
+
}
|
87 |
+
}
|
88 |
+
}
|
libraries/XmlImportCsvParse.php
ADDED
@@ -0,0 +1,2427 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
|
4 |
+
|
5 |
+
/**
|
6 |
+
* contains a few csv file data access tools.
|
7 |
+
*
|
8 |
+
* PHP VERSION 5
|
9 |
+
*
|
10 |
+
* LICENSE: The MIT License
|
11 |
+
*
|
12 |
+
* Copyright (c) <2008> <Kazuyoshi Tlacaelel>
|
13 |
+
*
|
14 |
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
15 |
+
* of this software and associated documentation files (the "Software"), to deal
|
16 |
+
* in the Software without restriction, including without limitation the rights
|
17 |
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
18 |
+
* copies of the Software, and to permit persons to whom the Software is
|
19 |
+
* furnished to do so, subject to the following conditions:
|
20 |
+
*
|
21 |
+
* The above copyright notice and this permission notice shall be included in
|
22 |
+
* all copies or substantial portions of the Software.
|
23 |
+
*
|
24 |
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
25 |
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
26 |
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
27 |
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
28 |
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
29 |
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
30 |
+
* THE SOFTWARE.
|
31 |
+
*
|
32 |
+
* @category File
|
33 |
+
* @package File_CSV_DataSource
|
34 |
+
* @author Kazuyoshi Tlacaelel <kazu.dev@gmail.com>
|
35 |
+
* @copyright 2008 Kazuyoshi Tlacaelel
|
36 |
+
* @license The MIT License
|
37 |
+
* @version SVN: $Id: DataSource.php 285574 2009-03-09 15:22:24Z ktlacaelel $
|
38 |
+
* @link http://code.google.com/p/php-csv-parser/
|
39 |
+
*/
|
40 |
+
|
41 |
+
/**
|
42 |
+
* csv data fetcher
|
43 |
+
*
|
44 |
+
* Sample snippets refer to this csv file for demonstration.
|
45 |
+
* <code>
|
46 |
+
* name,age,skill
|
47 |
+
* john,13,knows magic
|
48 |
+
* tanaka,8,makes sushi
|
49 |
+
* jose,5,dances salsa
|
50 |
+
* </code>
|
51 |
+
*
|
52 |
+
* @category File
|
53 |
+
* @package Csv_parse
|
54 |
+
* @author Kazuyoshi Tlacaelel <kazu.dev@gmail.com>
|
55 |
+
* @copyright 2008 Kazuyoshi Tlacaelel
|
56 |
+
* @license The MIT License
|
57 |
+
* @link http://code.google.com/p/php-csv-parser/
|
58 |
+
*/
|
59 |
+
class PMXI_CsvParser
|
60 |
+
{
|
61 |
+
public
|
62 |
+
|
63 |
+
/**
|
64 |
+
* csv parsing default-settings
|
65 |
+
*
|
66 |
+
* @var array
|
67 |
+
* @access public
|
68 |
+
*/
|
69 |
+
$settings = array(
|
70 |
+
'delimiter' => ',',
|
71 |
+
'eol' => '',
|
72 |
+
'length' => 999999,
|
73 |
+
'escape' => '"'
|
74 |
+
);
|
75 |
+
|
76 |
+
protected
|
77 |
+
|
78 |
+
/**
|
79 |
+
* imported data from csv
|
80 |
+
*
|
81 |
+
* @var array
|
82 |
+
* @access protected
|
83 |
+
*/
|
84 |
+
$rows = array(),
|
85 |
+
|
86 |
+
/**
|
87 |
+
* csv file to parse
|
88 |
+
*
|
89 |
+
* @var string
|
90 |
+
* @access protected
|
91 |
+
*/
|
92 |
+
$_filename = '',
|
93 |
+
|
94 |
+
/**
|
95 |
+
* csv headers to parse
|
96 |
+
*
|
97 |
+
* @var array
|
98 |
+
* @access protected
|
99 |
+
*/
|
100 |
+
$headers = array();
|
101 |
+
|
102 |
+
/**
|
103 |
+
* data load initialize
|
104 |
+
*
|
105 |
+
* @param mixed $filename please look at the load() method
|
106 |
+
*
|
107 |
+
* @access public
|
108 |
+
* @see load()
|
109 |
+
* @return void
|
110 |
+
*/
|
111 |
+
public function __construct($filename = null)
|
112 |
+
{
|
113 |
+
ini_set('auto_detect_line_endings', true);
|
114 |
+
|
115 |
+
$file_params = self::analyse_file($filename, 10);
|
116 |
+
|
117 |
+
$this->set_settings(array('delimiter' => $file_params['delimiter']['value'], 'eol' => $file_params['line_ending']['value']));
|
118 |
+
|
119 |
+
unset($file_params);
|
120 |
+
|
121 |
+
$this->load($filename);
|
122 |
+
}
|
123 |
+
|
124 |
+
/**
|
125 |
+
* csv file loader
|
126 |
+
*
|
127 |
+
* indicates the object which file is to be loaded
|
128 |
+
*
|
129 |
+
* <code>
|
130 |
+
*
|
131 |
+
* require_once 'File/CSV/DataSource.php';
|
132 |
+
*
|
133 |
+
* $csv = new File_CSV_DataSource;
|
134 |
+
* $csv->load('my_cool.csv');
|
135 |
+
* var_export($csv->connect());
|
136 |
+
*
|
137 |
+
* array (
|
138 |
+
* 0 =>
|
139 |
+
* array (
|
140 |
+
* 'name' => 'john',
|
141 |
+
* 'age' => '13',
|
142 |
+
* 'skill' => 'knows magic',
|
143 |
+
* ),
|
144 |
+
* 1 =>
|
145 |
+
* array (
|
146 |
+
* 'name' => 'tanaka',
|
147 |
+
* 'age' => '8',
|
148 |
+
* 'skill' => 'makes sushi',
|
149 |
+
* ),
|
150 |
+
* 2 =>
|
151 |
+
* array (
|
152 |
+
* 'name' => 'jose',
|
153 |
+
* 'age' => '5',
|
154 |
+
* 'skill' => 'dances salsa',
|
155 |
+
* ),
|
156 |
+
* )
|
157 |
+
*
|
158 |
+
* </code>
|
159 |
+
*
|
160 |
+
* @param string $filename the csv filename to load
|
161 |
+
*
|
162 |
+
* @access public
|
163 |
+
* @return boolean true if file was loaded successfully
|
164 |
+
* @see isSymmetric(), getAsymmetricRows(), symmetrize()
|
165 |
+
*/
|
166 |
+
public function load($filename)
|
167 |
+
{
|
168 |
+
$this->_filename = $filename;
|
169 |
+
$this->flush();
|
170 |
+
return $this->parse();
|
171 |
+
}
|
172 |
+
|
173 |
+
/**
|
174 |
+
* settings alterator
|
175 |
+
*
|
176 |
+
* lets you define different settings for scanning
|
177 |
+
*
|
178 |
+
* Given array will override the internal settings
|
179 |
+
*
|
180 |
+
* <code>
|
181 |
+
* $settings = array(
|
182 |
+
* 'delimiter' => ',',
|
183 |
+
* 'eol' => ";",
|
184 |
+
* 'length' => 999999,
|
185 |
+
* 'escape' => '"'
|
186 |
+
* );
|
187 |
+
* </code>
|
188 |
+
*
|
189 |
+
* @param mixed $array containing settings to use
|
190 |
+
*
|
191 |
+
* @access public
|
192 |
+
* @return boolean true if changes where applyed successfully
|
193 |
+
* @see $settings
|
194 |
+
*/
|
195 |
+
public function set_settings($array)
|
196 |
+
{
|
197 |
+
$this->settings = array_merge($this->settings, $array);
|
198 |
+
}
|
199 |
+
|
200 |
+
/**
|
201 |
+
* header fetcher
|
202 |
+
*
|
203 |
+
* gets csv headers into an array
|
204 |
+
*
|
205 |
+
* <code>
|
206 |
+
*
|
207 |
+
* var_export($csv->getHeaders());
|
208 |
+
*
|
209 |
+
* array (
|
210 |
+
* 0 => 'name',
|
211 |
+
* 1 => 'age',
|
212 |
+
* 2 => 'skill',
|
213 |
+
* )
|
214 |
+
*
|
215 |
+
* </code>
|
216 |
+
*
|
217 |
+
* @access public
|
218 |
+
* @return array
|
219 |
+
*/
|
220 |
+
public function getHeaders()
|
221 |
+
{
|
222 |
+
return $this->headers;
|
223 |
+
}
|
224 |
+
|
225 |
+
/**
|
226 |
+
* header counter
|
227 |
+
*
|
228 |
+
* retrives the total number of loaded headers
|
229 |
+
*
|
230 |
+
* @access public
|
231 |
+
* @return integer gets the length of headers
|
232 |
+
*/
|
233 |
+
public function countHeaders()
|
234 |
+
{
|
235 |
+
return count($this->headers);
|
236 |
+
}
|
237 |
+
|
238 |
+
/**
|
239 |
+
* header and row relationship builder
|
240 |
+
*
|
241 |
+
* Attempts to create a relationship for every single cell that
|
242 |
+
* was captured and its corresponding header. The sample below shows
|
243 |
+
* how a connection/relationship is built.
|
244 |
+
*
|
245 |
+
* sample of a csv file "my_cool.csv"
|
246 |
+
*
|
247 |
+
* <code>
|
248 |
+
* name,age,skill
|
249 |
+
* john,13,knows magic
|
250 |
+
* tanaka,8,makes sushi
|
251 |
+
* jose,5,dances salsa
|
252 |
+
* </code>
|
253 |
+
*
|
254 |
+
* php implementation
|
255 |
+
*
|
256 |
+
* <code>
|
257 |
+
*
|
258 |
+
* $csv = new File_CSV_DataSource;
|
259 |
+
* $csv->load('my_cool.csv');
|
260 |
+
*
|
261 |
+
* if (!$csv->isSymmetric()) {
|
262 |
+
* die('file has headers and rows with different lengths
|
263 |
+
* cannot connect');
|
264 |
+
* }
|
265 |
+
*
|
266 |
+
* var_export($csv->connect());
|
267 |
+
*
|
268 |
+
* array (
|
269 |
+
* 0 =>
|
270 |
+
* array (
|
271 |
+
* 'name' => 'john',
|
272 |
+
* 'age' => '13',
|
273 |
+
* 'skill' => 'knows magic',
|
274 |
+
* ),
|
275 |
+
* 1 =>
|
276 |
+
* array (
|
277 |
+
* 'name' => 'tanaka',
|
278 |
+
* 'age' => '8',
|
279 |
+
* 'skill' => 'makes sushi',
|
280 |
+
* ),
|
281 |
+
* 2 =>
|
282 |
+
* array (
|
283 |
+
* 'name' => 'jose',
|
284 |
+
* 'age' => '5',
|
285 |
+
* 'skill' => 'dances salsa',
|
286 |
+
* ),
|
287 |
+
* )
|
288 |
+
*
|
289 |
+
* </code>
|
290 |
+
*
|
291 |
+
*
|
292 |
+
* You can pass a collection of headers in an array to build
|
293 |
+
* a connection for those columns only!
|
294 |
+
*
|
295 |
+
* <code>
|
296 |
+
*
|
297 |
+
* var_export($csv->connect(array('age')));
|
298 |
+
*
|
299 |
+
* array (
|
300 |
+
* 0 =>
|
301 |
+
* array (
|
302 |
+
* 'age' => '13',
|
303 |
+
* ),
|
304 |
+
* 1 =>
|
305 |
+
* array (
|
306 |
+
* 'age' => '8',
|
307 |
+
* ),
|
308 |
+
* 2 =>
|
309 |
+
* array (
|
310 |
+
* 'age' => '5',
|
311 |
+
* ),
|
312 |
+
* )
|
313 |
+
*
|
314 |
+
* </code>
|
315 |
+
*
|
316 |
+
* @param array $columns the columns to connect, if nothing
|
317 |
+
* is given all headers will be used to create a connection
|
318 |
+
*
|
319 |
+
* @access public
|
320 |
+
* @return array If the data is not symmetric an empty array
|
321 |
+
* will be returned instead
|
322 |
+
* @see isSymmetric(), getAsymmetricRows(), symmetrize(), getHeaders()
|
323 |
+
*/
|
324 |
+
public function connect($columns = array())
|
325 |
+
{
|
326 |
+
if (!$this->isSymmetric()) {
|
327 |
+
return array();
|
328 |
+
}
|
329 |
+
if (!is_array($columns)) {
|
330 |
+
return array();
|
331 |
+
}
|
332 |
+
if ($columns === array()) {
|
333 |
+
$columns = $this->headers;
|
334 |
+
}
|
335 |
+
|
336 |
+
$ret_arr = array();
|
337 |
+
|
338 |
+
foreach ($this->rows as $record) {
|
339 |
+
$item_array = array();
|
340 |
+
foreach ($record as $column => $value) {
|
341 |
+
$header = $this->headers[$column];
|
342 |
+
if (in_array($header, $columns)) {
|
343 |
+
$item_array[$header] = $value;
|
344 |
+
}
|
345 |
+
}
|
346 |
+
|
347 |
+
// do not append empty results
|
348 |
+
if ($item_array !== array()) {
|
349 |
+
array_push($ret_arr, $item_array);
|
350 |
+
}
|
351 |
+
}
|
352 |
+
|
353 |
+
return $ret_arr;
|
354 |
+
}
|
355 |
+
|
356 |
+
/**
|
357 |
+
* data length/symmetry checker
|
358 |
+
*
|
359 |
+
* tells if the headers and all of the contents length match.
|
360 |
+
* Note: there is a lot of methods that won't work if data is not
|
361 |
+
* symmetric this method is very important!
|
362 |
+
*
|
363 |
+
* @access public
|
364 |
+
* @return boolean
|
365 |
+
* @see symmetrize(), getAsymmetricRows(), isSymmetric()
|
366 |
+
*/
|
367 |
+
public function isSymmetric()
|
368 |
+
{
|
369 |
+
$hc = count($this->headers);
|
370 |
+
foreach ($this->rows as $row) {
|
371 |
+
if (count($row) != $hc) {
|
372 |
+
return false;
|
373 |
+
}
|
374 |
+
}
|
375 |
+
return true;
|
376 |
+
}
|
377 |
+
|
378 |
+
/**
|
379 |
+
* asymmetric data fetcher
|
380 |
+
*
|
381 |
+
* finds the rows that do not match the headers length
|
382 |
+
*
|
383 |
+
* lets assume that we add one more row to our csv file.
|
384 |
+
* that has only two values. Something like
|
385 |
+
*
|
386 |
+
* <code>
|
387 |
+
* name,age,skill
|
388 |
+
* john,13,knows magic
|
389 |
+
* tanaka,8,makes sushi
|
390 |
+
* jose,5,dances salsa
|
391 |
+
* niki,6
|
392 |
+
* </code>
|
393 |
+
*
|
394 |
+
* Then in our php code
|
395 |
+
*
|
396 |
+
* <code>
|
397 |
+
* $csv->load('my_cool.csv');
|
398 |
+
* var_export($csv->getAsymmetricRows());
|
399 |
+
* </code>
|
400 |
+
*
|
401 |
+
* The result
|
402 |
+
*
|
403 |
+
* <code>
|
404 |
+
*
|
405 |
+
* array (
|
406 |
+
* 0 =>
|
407 |
+
* array (
|
408 |
+
* 0 => 'niki',
|
409 |
+
* 1 => '6',
|
410 |
+
* ),
|
411 |
+
* )
|
412 |
+
*
|
413 |
+
* </code>
|
414 |
+
*
|
415 |
+
* @access public
|
416 |
+
* @return array filled with rows that do not match headers
|
417 |
+
* @see getHeaders(), symmetrize(), isSymmetric(),
|
418 |
+
* getAsymmetricRows()
|
419 |
+
*/
|
420 |
+
public function getAsymmetricRows()
|
421 |
+
{
|
422 |
+
$ret_arr = array();
|
423 |
+
$hc = count($this->headers);
|
424 |
+
foreach ($this->rows as $row) {
|
425 |
+
if (count($row) != $hc) {
|
426 |
+
$ret_arr[] = $row;
|
427 |
+
}
|
428 |
+
}
|
429 |
+
return $ret_arr;
|
430 |
+
}
|
431 |
+
|
432 |
+
/**
|
433 |
+
* all rows length equalizer
|
434 |
+
*
|
435 |
+
* makes the length of all rows and headers the same. If no $value is given
|
436 |
+
* all unexistent cells will be filled with empty spaces
|
437 |
+
*
|
438 |
+
* @param mixed $value the value to fill the unexistent cells
|
439 |
+
*
|
440 |
+
* @access public
|
441 |
+
* @return array
|
442 |
+
* @see isSymmetric(), getAsymmetricRows(), symmetrize()
|
443 |
+
*/
|
444 |
+
public function symmetrize($value = '')
|
445 |
+
{
|
446 |
+
$max_length = 0;
|
447 |
+
$headers_length = count($this->headers);
|
448 |
+
|
449 |
+
foreach ($this->rows as $row) {
|
450 |
+
$row_length = count($row);
|
451 |
+
if ($max_length < $row_length) {
|
452 |
+
$max_length = $row_length;
|
453 |
+
}
|
454 |
+
}
|
455 |
+
|
456 |
+
if ($max_length < $headers_length) {
|
457 |
+
$max_length = $headers_length;
|
458 |
+
}
|
459 |
+
|
460 |
+
foreach ($this->rows as $key => $row) {
|
461 |
+
$this->rows[$key] = array_pad($row, $max_length, $value);
|
462 |
+
}
|
463 |
+
|
464 |
+
$this->headers = array_pad($this->headers, $max_length, $value);
|
465 |
+
}
|
466 |
+
|
467 |
+
/**
|
468 |
+
* grid walker
|
469 |
+
*
|
470 |
+
* travels through the whole dataset executing a callback per each
|
471 |
+
* cell
|
472 |
+
*
|
473 |
+
* Note: callback functions get the value of the cell as an
|
474 |
+
* argument, and whatever that callback returns will be used to
|
475 |
+
* replace the current value of that cell.
|
476 |
+
*
|
477 |
+
* @param string $callback the callback function to be called per
|
478 |
+
* each cell in the dataset.
|
479 |
+
*
|
480 |
+
* @access public
|
481 |
+
* @return void
|
482 |
+
* @see walkColumn(), walkRow(), fillColumn(), fillRow(), fillCell()
|
483 |
+
*/
|
484 |
+
public function walkGrid($callback)
|
485 |
+
{
|
486 |
+
foreach (array_keys($this->getRows()) as $key) {
|
487 |
+
if (!$this->walkRow($key, $callback)) {
|
488 |
+
return false;
|
489 |
+
}
|
490 |
+
}
|
491 |
+
return true;
|
492 |
+
}
|
493 |
+
|
494 |
+
/**
|
495 |
+
* column fetcher
|
496 |
+
*
|
497 |
+
* gets all the data for a specific column identified by $name
|
498 |
+
*
|
499 |
+
* Note $name is the same as the items returned by getHeaders()
|
500 |
+
*
|
501 |
+
* sample of a csv file "my_cool.csv"
|
502 |
+
*
|
503 |
+
* <code>
|
504 |
+
* name,age,skill
|
505 |
+
* john,13,knows magic
|
506 |
+
* tanaka,8,makes sushi
|
507 |
+
* jose,5,dances salsa
|
508 |
+
* </code>
|
509 |
+
*
|
510 |
+
* php implementation
|
511 |
+
*
|
512 |
+
* <code>
|
513 |
+
* $csv = new File_CSV_DataSource;
|
514 |
+
* $csv->load('my_cool.csv');
|
515 |
+
* var_export($csv->getColumn('name'));
|
516 |
+
* </code>
|
517 |
+
*
|
518 |
+
* the above example outputs something like
|
519 |
+
*
|
520 |
+
* <code>
|
521 |
+
*
|
522 |
+
* array (
|
523 |
+
* 0 => 'john',
|
524 |
+
* 1 => 'tanaka',
|
525 |
+
* 2 => 'jose',
|
526 |
+
* )
|
527 |
+
*
|
528 |
+
* </code>
|
529 |
+
*
|
530 |
+
* @param string $name the name of the column to fetch
|
531 |
+
*
|
532 |
+
* @access public
|
533 |
+
* @return array filled with values of a column
|
534 |
+
* @see getHeaders(), fillColumn(), appendColumn(), getCell(), getRows(),
|
535 |
+
* getRow(), hasColumn()
|
536 |
+
*/
|
537 |
+
public function getColumn($name)
|
538 |
+
{
|
539 |
+
if (!in_array($name, $this->headers)) {
|
540 |
+
return array();
|
541 |
+
}
|
542 |
+
$ret_arr = array();
|
543 |
+
$key = array_search($name, $this->headers, true);
|
544 |
+
foreach ($this->rows as $data) {
|
545 |
+
$ret_arr[] = $data[$key];
|
546 |
+
}
|
547 |
+
return $ret_arr;
|
548 |
+
}
|
549 |
+
|
550 |
+
/**
|
551 |
+
* column existance checker
|
552 |
+
*
|
553 |
+
* checks if a column exists, columns are identified by their
|
554 |
+
* header name.
|
555 |
+
*
|
556 |
+
* sample of a csv file "my_cool.csv"
|
557 |
+
*
|
558 |
+
* <code>
|
559 |
+
* name,age,skill
|
560 |
+
* john,13,knows magic
|
561 |
+
* tanaka,8,makes sushi
|
562 |
+
* jose,5,dances salsa
|
563 |
+
* </code>
|
564 |
+
*
|
565 |
+
* php implementation
|
566 |
+
*
|
567 |
+
* <code>
|
568 |
+
* $csv = new File_CSV_DataSource;
|
569 |
+
* $csv->load('my_cool.csv');
|
570 |
+
* $headers = $csv->getHeaders();
|
571 |
+
* </code>
|
572 |
+
*
|
573 |
+
* now lets check if the columns exist
|
574 |
+
*
|
575 |
+
* <code>
|
576 |
+
* var_export($csv->hasColumn($headers[0])); // true
|
577 |
+
* var_export($csv->hasColumn('age')); // true
|
578 |
+
* var_export($csv->hasColumn('I dont exist')); // false
|
579 |
+
* </code>
|
580 |
+
*
|
581 |
+
* @param string $string an item returned by getHeaders()
|
582 |
+
*
|
583 |
+
* @access public
|
584 |
+
* @return boolean
|
585 |
+
* @see getHeaders()
|
586 |
+
*/
|
587 |
+
public function hasColumn($string)
|
588 |
+
{
|
589 |
+
return in_array($string, $this->headers);
|
590 |
+
}
|
591 |
+
|
592 |
+
/**
|
593 |
+
* column appender
|
594 |
+
*
|
595 |
+
* Appends a column and each or all values in it can be
|
596 |
+
* dinamically filled. Only when the $values argument is given.
|
597 |
+
* <code>
|
598 |
+
*
|
599 |
+
*
|
600 |
+
* var_export($csv->fillColumn('age', 99));
|
601 |
+
* true
|
602 |
+
*
|
603 |
+
* var_export($csv->appendColumn('candy_ownership', array(99, 44, 65)));
|
604 |
+
* true
|
605 |
+
*
|
606 |
+
* var_export($csv->appendColumn('import_id', 111111111));
|
607 |
+
* true
|
608 |
+
*
|
609 |
+
* var_export($csv->connect());
|
610 |
+
*
|
611 |
+
* array (
|
612 |
+
* 0 =>
|
613 |
+
* array (
|
614 |
+
* 'name' => 'john',
|
615 |
+
* 'age' => 99,
|
616 |
+
* 'skill' => 'knows magic',
|
617 |
+
* 'candy_ownership' => 99,
|
618 |
+
* 'import_id' => 111111111,
|
619 |
+
* ),
|
620 |
+
* 1 =>
|
621 |
+
* array (
|
622 |
+
* 'name' => 'tanaka',
|
623 |
+
* 'age' => 99,
|
624 |
+
* 'skill' => 'makes sushi',
|
625 |
+
* 'candy_ownership' => 44,
|
626 |
+
* 'import_id' => 111111111,
|
627 |
+
* ),
|
628 |
+
* 2 =>
|
629 |
+
* array (
|
630 |
+
* 'name' => 'jose',
|
631 |
+
* 'age' => 99,
|
632 |
+
* 'skill' => 'dances salsa',
|
633 |
+
* 'candy_ownership' => 65,
|
634 |
+
* 'import_id' => 111111111,
|
635 |
+
* ),
|
636 |
+
* )
|
637 |
+
*
|
638 |
+
* </code>
|
639 |
+
*
|
640 |
+
* @param string $column an item returned by getHeaders()
|
641 |
+
* @param mixed $values same as fillColumn()
|
642 |
+
*
|
643 |
+
* @access public
|
644 |
+
* @return boolean
|
645 |
+
* @see getHeaders(), fillColumn(), fillCell(), createHeaders(),
|
646 |
+
* setHeaders()
|
647 |
+
*/
|
648 |
+
public function appendColumn($column, $values = null)
|
649 |
+
{
|
650 |
+
if ($this->hasColumn($column)) {
|
651 |
+
return false;
|
652 |
+
}
|
653 |
+
$this->headers[] = $column;
|
654 |
+
$length = $this->countHeaders();
|
655 |
+
$rows = array();
|
656 |
+
|
657 |
+
foreach ($this->rows as $row) {
|
658 |
+
$rows[] = array_pad($row, $length, '');
|
659 |
+
}
|
660 |
+
|
661 |
+
$this->rows = $rows;
|
662 |
+
|
663 |
+
if ($values === null) {
|
664 |
+
$values = '';
|
665 |
+
}
|
666 |
+
|
667 |
+
return $this->fillColumn($column, $values);
|
668 |
+
}
|
669 |
+
|
670 |
+
/**
|
671 |
+
* collumn data injector
|
672 |
+
*
|
673 |
+
* fills alll the data in the given column with $values
|
674 |
+
*
|
675 |
+
* sample of a csv file "my_cool.csv"
|
676 |
+
*
|
677 |
+
* <code>
|
678 |
+
* name,age,skill
|
679 |
+
* john,13,knows magic
|
680 |
+
* tanaka,8,makes sushi
|
681 |
+
* jose,5,dances salsa
|
682 |
+
* </code>
|
683 |
+
*
|
684 |
+
* php implementation
|
685 |
+
*
|
686 |
+
* <code>
|
687 |
+
* $csv = new File_CSV_DataSource;
|
688 |
+
* $csv->load('my_cool.csv');
|
689 |
+
*
|
690 |
+
* // if the csv file loads
|
691 |
+
* if ($csv->load('my_cool.csv')) {
|
692 |
+
*
|
693 |
+
* // grab all data within the age column
|
694 |
+
* var_export($csv->getColumn('age'));
|
695 |
+
*
|
696 |
+
* // rename all values in it with the number 99
|
697 |
+
* var_export($csv->fillColumn('age', 99));
|
698 |
+
*
|
699 |
+
* // grab all data within the age column
|
700 |
+
* var_export($csv->getColumn('age'));
|
701 |
+
*
|
702 |
+
* // rename each value in a column independently
|
703 |
+
* $data = array(1, 2, 3);
|
704 |
+
* $csv->fillColumn('age', $data);
|
705 |
+
*
|
706 |
+
* var_export($csv->getColumn('age'));
|
707 |
+
* }
|
708 |
+
* </code>
|
709 |
+
*
|
710 |
+
* standard output
|
711 |
+
*
|
712 |
+
* <code>
|
713 |
+
* array (
|
714 |
+
* 0 => '13',
|
715 |
+
* 1 => '8',
|
716 |
+
* 2 => '5',
|
717 |
+
* )
|
718 |
+
* </code>
|
719 |
+
*
|
720 |
+
* <code>
|
721 |
+
* true
|
722 |
+
* </code>
|
723 |
+
*
|
724 |
+
* <code>
|
725 |
+
* array (
|
726 |
+
* 0 => 99,
|
727 |
+
* 1 => 99,
|
728 |
+
* 2 => 99,
|
729 |
+
* )
|
730 |
+
* </code>
|
731 |
+
*
|
732 |
+
* <code>
|
733 |
+
* array (
|
734 |
+
* 0 => 1,
|
735 |
+
* 1 => 2,
|
736 |
+
* 2 => 3,
|
737 |
+
* )
|
738 |
+
* </code>
|
739 |
+
*
|
740 |
+
* @param mixed $column the column identified by a string
|
741 |
+
* @param mixed $values ither one of the following
|
742 |
+
* - (Number) will fill the whole column with the value of number
|
743 |
+
* - (String) will fill the whole column with the value of string
|
744 |
+
* - (Array) will fill the while column with the values of array
|
745 |
+
* the array gets ignored if it does not match the length of rows
|
746 |
+
*
|
747 |
+
* @access public
|
748 |
+
* @return void
|
749 |
+
*/
|
750 |
+
public function fillColumn($column, $values = null)
|
751 |
+
{
|
752 |
+
if (!$this->hasColumn($column)) {
|
753 |
+
return false;
|
754 |
+
}
|
755 |
+
|
756 |
+
if ($values === null) {
|
757 |
+
return false;
|
758 |
+
}
|
759 |
+
|
760 |
+
if (!$this->isSymmetric()) {
|
761 |
+
return false;
|
762 |
+
}
|
763 |
+
|
764 |
+
$y = array_search($column, $this->headers);
|
765 |
+
|
766 |
+
if (is_numeric($values) || is_string($values)) {
|
767 |
+
foreach (range(0, $this->countRows() -1) as $x) {
|
768 |
+
$this->fillCell($x, $y, $values);
|
769 |
+
}
|
770 |
+
return true;
|
771 |
+
}
|
772 |
+
|
773 |
+
if ($values === array()) {
|
774 |
+
return false;
|
775 |
+
}
|
776 |
+
|
777 |
+
$length = $this->countRows();
|
778 |
+
if (is_array($values) && $length == count($values)) {
|
779 |
+
for ($x = 0; $x < $length; $x++) {
|
780 |
+
$this->fillCell($x, $y, $values[$x]);
|
781 |
+
}
|
782 |
+
return true;
|
783 |
+
}
|
784 |
+
|
785 |
+
return false;
|
786 |
+
}
|
787 |
+
|
788 |
+
/**
|
789 |
+
* column remover
|
790 |
+
*
|
791 |
+
* Completly removes a whole column identified by $name
|
792 |
+
* Note: that this function will only work if data is symmetric.
|
793 |
+
*
|
794 |
+
* sample of a csv file "my_cool.csv"
|
795 |
+
*
|
796 |
+
* <code>
|
797 |
+
* name,age,skill
|
798 |
+
* john,13,knows magic
|
799 |
+
* tanaka,8,makes sushi
|
800 |
+
* jose,5,dances salsa
|
801 |
+
* </code>
|
802 |
+
*
|
803 |
+
* load the library and csv file
|
804 |
+
*
|
805 |
+
* <code>
|
806 |
+
* require_once 'File/CSV/DataSource.php';
|
807 |
+
* $csv = new File_CSV_DataSource;
|
808 |
+
* $csv->load('my_cool.csv');
|
809 |
+
* </code>
|
810 |
+
*
|
811 |
+
* lets dump currently loaded data
|
812 |
+
* <code>
|
813 |
+
* var_export($csv->connect());
|
814 |
+
* </code>
|
815 |
+
*
|
816 |
+
* output
|
817 |
+
*
|
818 |
+
* <code>
|
819 |
+
* array (
|
820 |
+
* 0 =>
|
821 |
+
* array (
|
822 |
+
* 'name' => 'john',
|
823 |
+
* 'age' => '13',
|
824 |
+
* 'skill' => 'knows magic',
|
825 |
+
* ),
|
826 |
+
* 1 =>
|
827 |
+
* array (
|
828 |
+
* 'name' => 'tanaka',
|
829 |
+
* 'age' => '8',
|
830 |
+
* 'skill' => 'makes sushi',
|
831 |
+
* ),
|
832 |
+
* 2 =>
|
833 |
+
* array (
|
834 |
+
* 'name' => 'jose',
|
835 |
+
* 'age' => '5',
|
836 |
+
* 'skill' => 'dances salsa',
|
837 |
+
* ),
|
838 |
+
* )
|
839 |
+
* </code>
|
840 |
+
*
|
841 |
+
* and now let's remove the second column
|
842 |
+
*
|
843 |
+
* <code>
|
844 |
+
* var_export($csv->removeColumn('age'));
|
845 |
+
* </code>
|
846 |
+
*
|
847 |
+
* output
|
848 |
+
*
|
849 |
+
* <code>
|
850 |
+
* true
|
851 |
+
* </code>
|
852 |
+
*
|
853 |
+
* those changes made let's dump the data again and see what we got
|
854 |
+
*
|
855 |
+
* <code>
|
856 |
+
* array (
|
857 |
+
* 0 =>
|
858 |
+
* array (
|
859 |
+
* 'name' => 'john',
|
860 |
+
* 'skill' => 'knows magic',
|
861 |
+
* ),
|
862 |
+
* 1 =>
|
863 |
+
* array (
|
864 |
+
* 'name' => 'tanaka',
|
865 |
+
* 'skill' => 'makes sushi',
|
866 |
+
* ),
|
867 |
+
* 2 =>
|
868 |
+
* array (
|
869 |
+
* 'name' => 'jose',
|
870 |
+
* 'skill' => 'dances salsa',
|
871 |
+
* ),
|
872 |
+
* )
|
873 |
+
* </code>
|
874 |
+
*
|
875 |
+
* @param string $name same as the ones returned by getHeaders();
|
876 |
+
*
|
877 |
+
* @access public
|
878 |
+
* @return boolean
|
879 |
+
* @see hasColumn(), getHeaders(), createHeaders(), setHeaders(),
|
880 |
+
* isSymmetric(), getAsymmetricRows()
|
881 |
+
*/
|
882 |
+
public function removeColumn($name)
|
883 |
+
{
|
884 |
+
if (!in_array($name, $this->headers)) {
|
885 |
+
return false;
|
886 |
+
}
|
887 |
+
|
888 |
+
if (!$this->isSymmetric()) {
|
889 |
+
return false;
|
890 |
+
}
|
891 |
+
|
892 |
+
$key = array_search($name, $this->headers);
|
893 |
+
unset($this->headers[$key]);
|
894 |
+
$this->resetKeys($this->headers);
|
895 |
+
|
896 |
+
foreach ($this->rows as $target => $row) {
|
897 |
+
unset($this->rows[$target][$key]);
|
898 |
+
$this->resetKeys($this->rows[$target]);
|
899 |
+
}
|
900 |
+
|
901 |
+
return $this->isSymmetric();
|
902 |
+
}
|
903 |
+
|
904 |
+
/**
|
905 |
+
* column walker
|
906 |
+
*
|
907 |
+
* goes through the whole column and executes a callback for each
|
908 |
+
* one of the cells in it.
|
909 |
+
*
|
910 |
+
* Note: callback functions get the value of the cell as an
|
911 |
+
* argument, and whatever that callback returns will be used to
|
912 |
+
* replace the current value of that cell.
|
913 |
+
*
|
914 |
+
* @param string $name the header name used to identify the column
|
915 |
+
* @param string $callback the callback function to be called per
|
916 |
+
* each cell value
|
917 |
+
*
|
918 |
+
* @access public
|
919 |
+
* @return boolean
|
920 |
+
* @see getHeaders(), fillColumn(), appendColumn()
|
921 |
+
*/
|
922 |
+
public function walkColumn($name, $callback)
|
923 |
+
{
|
924 |
+
if (!$this->isSymmetric()) {
|
925 |
+
return false;
|
926 |
+
}
|
927 |
+
|
928 |
+
if (!$this->hasColumn($name)) {
|
929 |
+
return false;
|
930 |
+
}
|
931 |
+
|
932 |
+
if (!function_exists($callback)) {
|
933 |
+
return false;
|
934 |
+
}
|
935 |
+
|
936 |
+
$column = $this->getColumn($name);
|
937 |
+
foreach ($column as $key => $cell) {
|
938 |
+
$column[$key] = $callback($cell);
|
939 |
+
}
|
940 |
+
return $this->fillColumn($name, $column);
|
941 |
+
}
|
942 |
+
|
943 |
+
/**
|
944 |
+
* cell fetcher
|
945 |
+
*
|
946 |
+
* gets the value of a specific cell by given coordinates
|
947 |
+
*
|
948 |
+
* Note: That indexes start with zero, and headers are not
|
949 |
+
* searched!
|
950 |
+
*
|
951 |
+
* For example if we are trying to grab the cell that is in the
|
952 |
+
* second row and the third column
|
953 |
+
*
|
954 |
+
* <code>
|
955 |
+
* name,age,skill
|
956 |
+
* john,13,knows magic
|
957 |
+
* tanaka,8,makes sushi
|
958 |
+
* jose,5,dances salsa
|
959 |
+
* </code>
|
960 |
+
*
|
961 |
+
* we would do something like
|
962 |
+
* <code>
|
963 |
+
* var_export($csv->getCell(1, 2));
|
964 |
+
* </code>
|
965 |
+
*
|
966 |
+
* and get the following results
|
967 |
+
* <code>
|
968 |
+
* 'makes sushi'
|
969 |
+
* </code>
|
970 |
+
*
|
971 |
+
* @param integer $x the row to fetch
|
972 |
+
* @param integer $y the column to fetch
|
973 |
+
*
|
974 |
+
* @access public
|
975 |
+
* @return mixed|false the value of the cell or false if the cell does
|
976 |
+
* not exist
|
977 |
+
* @see getHeaders(), hasCell(), getRow(), getRows(), getColumn()
|
978 |
+
*/
|
979 |
+
public function getCell($x, $y)
|
980 |
+
{
|
981 |
+
if ($this->hasCell($x, $y)) {
|
982 |
+
$row = $this->getRow($x);
|
983 |
+
return $row[$y];
|
984 |
+
}
|
985 |
+
return false;
|
986 |
+
}
|
987 |
+
|
988 |
+
/**
|
989 |
+
* cell value filler
|
990 |
+
*
|
991 |
+
* replaces the value of a specific cell
|
992 |
+
*
|
993 |
+
* sample of a csv file "my_cool.csv"
|
994 |
+
*
|
995 |
+
* <code>
|
996 |
+
* name,age,skill
|
997 |
+
* john,13,knows magic
|
998 |
+
* tanaka,8,makes sushi
|
999 |
+
* jose,5,dances salsa
|
1000 |
+
* </code>
|
1001 |
+
*
|
1002 |
+
* php implementation
|
1003 |
+
*
|
1004 |
+
* <code>
|
1005 |
+
*
|
1006 |
+
* $csv = new File_CSV_DataSource;
|
1007 |
+
*
|
1008 |
+
* // load the csv file
|
1009 |
+
* $csv->load('my_cool.csv');
|
1010 |
+
*
|
1011 |
+
* // find out if the given coordinate is valid
|
1012 |
+
* if($csv->hasCell(1, 1)) {
|
1013 |
+
*
|
1014 |
+
* // if so grab that cell and dump it
|
1015 |
+
* var_export($csv->getCell(1, 1)); // '8'
|
1016 |
+
*
|
1017 |
+
* // replace the value of that cell
|
1018 |
+
* $csv->fillCell(1, 1, 'new value'); // true
|
1019 |
+
*
|
1020 |
+
* // output the new value of the cell
|
1021 |
+
* var_export($csv->getCell(1, 1)); // 'new value'
|
1022 |
+
*
|
1023 |
+
* }
|
1024 |
+
* </code>
|
1025 |
+
*
|
1026 |
+
* now lets try to grab the whole row
|
1027 |
+
*
|
1028 |
+
* <code>
|
1029 |
+
* // show the whole row
|
1030 |
+
* var_export($csv->getRow(1));
|
1031 |
+
* </code>
|
1032 |
+
*
|
1033 |
+
* standard output
|
1034 |
+
*
|
1035 |
+
* <code>
|
1036 |
+
* array (
|
1037 |
+
* 0 => 'tanaka',
|
1038 |
+
* 1 => 'new value',
|
1039 |
+
* 2 => 'makes sushi',
|
1040 |
+
* )
|
1041 |
+
* </code>
|
1042 |
+
*
|
1043 |
+
* @param integer $x the row to fetch
|
1044 |
+
* @param integer $y the column to fetch
|
1045 |
+
* @param mixed $value the value to fill the cell with
|
1046 |
+
*
|
1047 |
+
* @access public
|
1048 |
+
* @return boolean
|
1049 |
+
* @see hasCell(), getRow(), getRows(), getColumn()
|
1050 |
+
*/
|
1051 |
+
public function fillCell($x, $y, $value)
|
1052 |
+
{
|
1053 |
+
if (!$this->hasCell($x, $y)) {
|
1054 |
+
return false;
|
1055 |
+
}
|
1056 |
+
$row = $this->getRow($x);
|
1057 |
+
$row[$y] = $value;
|
1058 |
+
$this->rows[$x] = $row;
|
1059 |
+
return true;
|
1060 |
+
}
|
1061 |
+
|
1062 |
+
/**
|
1063 |
+
* checks if a coordinate is valid
|
1064 |
+
*
|
1065 |
+
* sample of a csv file "my_cool.csv"
|
1066 |
+
*
|
1067 |
+
* <code>
|
1068 |
+
* name,age,skill
|
1069 |
+
* john,13,knows magic
|
1070 |
+
* tanaka,8,makes sushi
|
1071 |
+
* jose,5,dances salsa
|
1072 |
+
* </code>
|
1073 |
+
*
|
1074 |
+
* load the csv file
|
1075 |
+
*
|
1076 |
+
* <code>
|
1077 |
+
* $csv = new File_CSV_DataSource;
|
1078 |
+
* var_export($csv->load('my_cool.csv')); // true if file is
|
1079 |
+
* // loaded
|
1080 |
+
* </code>
|
1081 |
+
*
|
1082 |
+
* find out if a coordinate is valid
|
1083 |
+
*
|
1084 |
+
* <code>
|
1085 |
+
* var_export($csv->hasCell(99, 3)); // false
|
1086 |
+
* </code>
|
1087 |
+
*
|
1088 |
+
* check again for a know valid coordinate and grab that cell
|
1089 |
+
*
|
1090 |
+
* <code>
|
1091 |
+
* var_export($csv->hasCell(1, 1)); // true
|
1092 |
+
* var_export($csv->getCell(1, 1)); // '8'
|
1093 |
+
* </code>
|
1094 |
+
*
|
1095 |
+
* @param mixed $x the row to fetch
|
1096 |
+
* @param mixed $y the column to fetch
|
1097 |
+
*
|
1098 |
+
* @access public
|
1099 |
+
* @return void
|
1100 |
+
*/
|
1101 |
+
public function hasCell($x, $y)
|
1102 |
+
{
|
1103 |
+
$has_x = array_key_exists($x, $this->rows);
|
1104 |
+
$has_y = array_key_exists($y, $this->headers);
|
1105 |
+
return ($has_x && $has_y);
|
1106 |
+
}
|
1107 |
+
|
1108 |
+
/**
|
1109 |
+
* row fetcher
|
1110 |
+
*
|
1111 |
+
* Note: first row is zero
|
1112 |
+
*
|
1113 |
+
* sample of a csv file "my_cool.csv"
|
1114 |
+
*
|
1115 |
+
* <code>
|
1116 |
+
* name,age,skill
|
1117 |
+
* john,13,knows magic
|
1118 |
+
* tanaka,8,makes sushi
|
1119 |
+
* jose,5,dances salsa
|
1120 |
+
* </code>
|
1121 |
+
*
|
1122 |
+
* load the library and csv file
|
1123 |
+
*
|
1124 |
+
* <code>
|
1125 |
+
* require_once 'File/CSV/DataSource.php';
|
1126 |
+
* $csv = new File_CSV_DataSource;
|
1127 |
+
* $csv->load('my_cool.csv');
|
1128 |
+
* </code>
|
1129 |
+
*
|
1130 |
+
* lets dump currently loaded data
|
1131 |
+
* <code>
|
1132 |
+
* var_export($csv->connect());
|
1133 |
+
* </code>
|
1134 |
+
*
|
1135 |
+
* output
|
1136 |
+
*
|
1137 |
+
* <code>
|
1138 |
+
* array (
|
1139 |
+
* 0 =>
|
1140 |
+
* array (
|
1141 |
+
* 'name' => 'john',
|
1142 |
+
* 'age' => '13',
|
1143 |
+
* 'skill' => 'knows magic',
|
1144 |
+
* ),
|
1145 |
+
* 1 =>
|
1146 |
+
* array (
|
1147 |
+
* 'name' => 'tanaka',
|
1148 |
+
* 'age' => '8',
|
1149 |
+
* 'skill' => 'makes sushi',
|
1150 |
+
* ),
|
1151 |
+
* 2 =>
|
1152 |
+
* array (
|
1153 |
+
* 'name' => 'jose',
|
1154 |
+
* 'age' => '5',
|
1155 |
+
* 'skill' => 'dances salsa',
|
1156 |
+
* ),
|
1157 |
+
* )
|
1158 |
+
* </code>
|
1159 |
+
*
|
1160 |
+
* Now let's fetch the second row
|
1161 |
+
*
|
1162 |
+
* <code>
|
1163 |
+
* var_export($csv->getRow(1));
|
1164 |
+
* </code>
|
1165 |
+
*
|
1166 |
+
* output
|
1167 |
+
*
|
1168 |
+
* <code>
|
1169 |
+
* array (
|
1170 |
+
* 0 => 'tanaka',
|
1171 |
+
* 1 => '8',
|
1172 |
+
* 2 => 'makes sushi',
|
1173 |
+
* )
|
1174 |
+
* </code>
|
1175 |
+
*
|
1176 |
+
* @param integer $number the row number to fetch
|
1177 |
+
*
|
1178 |
+
* @access public
|
1179 |
+
* @return array the row identified by number, if $number does
|
1180 |
+
* not exist an empty array is returned instead
|
1181 |
+
*/
|
1182 |
+
public function getRow($number)
|
1183 |
+
{
|
1184 |
+
$raw = $this->rows;
|
1185 |
+
if (array_key_exists($number, $raw)) {
|
1186 |
+
return $raw[$number];
|
1187 |
+
}
|
1188 |
+
return array();
|
1189 |
+
}
|
1190 |
+
|
1191 |
+
/**
|
1192 |
+
* multiple row fetcher
|
1193 |
+
*
|
1194 |
+
* Extracts a rows in the following fashion
|
1195 |
+
* - all rows if no $range argument is given
|
1196 |
+
* - a range of rows identified by their key
|
1197 |
+
* - if rows in range are not found nothing is retrived instead
|
1198 |
+
* - if no rows were found an empty array is returned
|
1199 |
+
*
|
1200 |
+
* sample of a csv file "my_cool.csv"
|
1201 |
+
*
|
1202 |
+
* <code>
|
1203 |
+
* name,age,skill
|
1204 |
+
* john,13,knows magic
|
1205 |
+
* tanaka,8,makes sushi
|
1206 |
+
* jose,5,dances salsa
|
1207 |
+
* </code>
|
1208 |
+
*
|
1209 |
+
* load the library and csv file
|
1210 |
+
*
|
1211 |
+
* <code>
|
1212 |
+
* require_once 'File/CSV/DataSource.php';
|
1213 |
+
* $csv = new File_CSV_DataSource;
|
1214 |
+
* $csv->load('my_cool.csv');
|
1215 |
+
* </code>
|
1216 |
+
*
|
1217 |
+
* lets dump currently loaded data
|
1218 |
+
* <code>
|
1219 |
+
* var_export($csv->connect());
|
1220 |
+
* </code>
|
1221 |
+
*
|
1222 |
+
* output
|
1223 |
+
*
|
1224 |
+
* <code>
|
1225 |
+
* array (
|
1226 |
+
* 0 =>
|
1227 |
+
* array (
|
1228 |
+
* 'name' => 'john',
|
1229 |
+
* 'age' => '13',
|
1230 |
+
* 'skill' => 'knows magic',
|
1231 |
+
* ),
|
1232 |
+
* 1 =>
|
1233 |
+
* array (
|
1234 |
+
* 'name' => 'tanaka',
|
1235 |
+
* 'age' => '8',
|
1236 |
+
* 'skill' => 'makes sushi',
|
1237 |
+
* ),
|
1238 |
+
* 2 =>
|
1239 |
+
* array (
|
1240 |
+
* 'name' => 'jose',
|
1241 |
+
* 'age' => '5',
|
1242 |
+
* 'skill' => 'dances salsa',
|
1243 |
+
* ),
|
1244 |
+
* )
|
1245 |
+
* </code>
|
1246 |
+
*
|
1247 |
+
* now get the second and thirdh row
|
1248 |
+
*
|
1249 |
+
* <code>
|
1250 |
+
* var_export($csv->getRows(array(1, 2)));
|
1251 |
+
* </code>
|
1252 |
+
*
|
1253 |
+
* output
|
1254 |
+
*
|
1255 |
+
* <code>
|
1256 |
+
* array (
|
1257 |
+
* 0 =>
|
1258 |
+
* array (
|
1259 |
+
* 0 => 'tanaka',
|
1260 |
+
* 1 => '8',
|
1261 |
+
* 2 => 'makes sushi',
|
1262 |
+
* ),
|
1263 |
+
* 1 =>
|
1264 |
+
* array (
|
1265 |
+
* 0 => 'jose',
|
1266 |
+
* 1 => '5',
|
1267 |
+
* 2 => 'dances salsa',
|
1268 |
+
* ),
|
1269 |
+
* )
|
1270 |
+
* </code>
|
1271 |
+
*
|
1272 |
+
* now lets try something odd and the goodie third row
|
1273 |
+
*
|
1274 |
+
* <code>
|
1275 |
+
* var_export($csv->getRows(array(9, 2)));
|
1276 |
+
* </code>
|
1277 |
+
*
|
1278 |
+
* output
|
1279 |
+
*
|
1280 |
+
* <code>
|
1281 |
+
* array (
|
1282 |
+
* 0 =>
|
1283 |
+
* array (
|
1284 |
+
* 0 => 'jose',
|
1285 |
+
* 1 => '5',
|
1286 |
+
* 2 => 'dances salsa',
|
1287 |
+
* ),
|
1288 |
+
* )
|
1289 |
+
* </code>
|
1290 |
+
*
|
1291 |
+
* @param array $range a list of rows to retrive
|
1292 |
+
*
|
1293 |
+
* @access public
|
1294 |
+
* @return array
|
1295 |
+
*/
|
1296 |
+
public function getRows($range = array())
|
1297 |
+
{
|
1298 |
+
if (is_array($range) && ($range === array())) {
|
1299 |
+
return $this->rows;
|
1300 |
+
}
|
1301 |
+
|
1302 |
+
if (!is_array($range)) {
|
1303 |
+
return $this->rows;
|
1304 |
+
}
|
1305 |
+
|
1306 |
+
$ret_arr = array();
|
1307 |
+
foreach ($this->rows as $key => $row) {
|
1308 |
+
if (in_array($key, $range)) {
|
1309 |
+
$ret_arr[] = $row;
|
1310 |
+
}
|
1311 |
+
}
|
1312 |
+
return $ret_arr;
|
1313 |
+
}
|
1314 |
+
|
1315 |
+
/**
|
1316 |
+
* row counter
|
1317 |
+
*
|
1318 |
+
* This function will exclude the headers
|
1319 |
+
*
|
1320 |
+
* sample of a csv file "my_cool.csv"
|
1321 |
+
*
|
1322 |
+
* <code>
|
1323 |
+
* name,age,skill
|
1324 |
+
* john,13,knows magic
|
1325 |
+
* tanaka,8,makes sushi
|
1326 |
+
* jose,5,dances salsa
|
1327 |
+
* </code>
|
1328 |
+
*
|
1329 |
+
* php implementation
|
1330 |
+
*
|
1331 |
+
* <code>
|
1332 |
+
* $csv = new File_CSV_DataSource;
|
1333 |
+
* $csv->load('my_cool.csv');
|
1334 |
+
* var_export($csv->countRows()); // returns 3
|
1335 |
+
* </code>
|
1336 |
+
*
|
1337 |
+
* @access public
|
1338 |
+
* @return integer
|
1339 |
+
*/
|
1340 |
+
public function countRows()
|
1341 |
+
{
|
1342 |
+
return count($this->rows);
|
1343 |
+
}
|
1344 |
+
|
1345 |
+
/**
|
1346 |
+
* row appender
|
1347 |
+
*
|
1348 |
+
* Aggregates one more row to the currently loaded dataset
|
1349 |
+
*
|
1350 |
+
* sample of a csv file "my_cool.csv"
|
1351 |
+
*
|
1352 |
+
* <code>
|
1353 |
+
* name,age,skill
|
1354 |
+
* john,13,knows magic
|
1355 |
+
* tanaka,8,makes sushi
|
1356 |
+
* jose,5,dances salsa
|
1357 |
+
* </code>
|
1358 |
+
*
|
1359 |
+
*
|
1360 |
+
* first let's load the file and output whatever was retrived.
|
1361 |
+
*
|
1362 |
+
* <code>
|
1363 |
+
* require_once 'File/CSV/DataSource.php';
|
1364 |
+
* $csv = new File_CSV_DataSource;
|
1365 |
+
* $csv->load('my_cool.csv');
|
1366 |
+
* var_export($csv->connect());
|
1367 |
+
* </code>
|
1368 |
+
*
|
1369 |
+
* output
|
1370 |
+
*
|
1371 |
+
* <code>
|
1372 |
+
*
|
1373 |
+
* array (
|
1374 |
+
* 0 =>
|
1375 |
+
* array (
|
1376 |
+
* 'name' => 'john',
|
1377 |
+
* 'age' => '13',
|
1378 |
+
* 'skill' => 'knows magic',
|
1379 |
+
* ),
|
1380 |
+
* 1 =>
|
1381 |
+
* array (
|
1382 |
+
* 'name' => 'tanaka',
|
1383 |
+
* 'age' => '8',
|
1384 |
+
* 'skill' => 'makes sushi',
|
1385 |
+
* ),
|
1386 |
+
* 2 =>
|
1387 |
+
* array (
|
1388 |
+
* 'name' => 'jose',
|
1389 |
+
* 'age' => '5',
|
1390 |
+
* 'skill' => 'dances salsa',
|
1391 |
+
* ),
|
1392 |
+
* )
|
1393 |
+
* </code>
|
1394 |
+
*
|
1395 |
+
* now lets do some modifications, let's try adding three rows.
|
1396 |
+
*
|
1397 |
+
* <code>
|
1398 |
+
* var_export($csv->appendRow(1));
|
1399 |
+
* var_export($csv->appendRow('2'));
|
1400 |
+
* var_export($csv->appendRow(array(3, 3, 3)));
|
1401 |
+
* </code>
|
1402 |
+
*
|
1403 |
+
* output
|
1404 |
+
*
|
1405 |
+
* <code>
|
1406 |
+
* true
|
1407 |
+
* true
|
1408 |
+
* true
|
1409 |
+
* </code>
|
1410 |
+
*
|
1411 |
+
* and now let's try to see what has changed
|
1412 |
+
*
|
1413 |
+
* <code>
|
1414 |
+
* var_export($csv->connect());
|
1415 |
+
* </code>
|
1416 |
+
*
|
1417 |
+
* output
|
1418 |
+
*
|
1419 |
+
* <code>
|
1420 |
+
* array (
|
1421 |
+
* 0 =>
|
1422 |
+
* array (
|
1423 |
+
* 'name' => 'john',
|
1424 |
+
* 'age' => '13',
|
1425 |
+
* 'skill' => 'knows magic',
|
1426 |
+
* ),
|
1427 |
+
* 1 =>
|
1428 |
+
* array (
|
1429 |
+
* 'name' => 'tanaka',
|
1430 |
+
* 'age' => '8',
|
1431 |
+
* 'skill' => 'makes sushi',
|
1432 |
+
* ),
|
1433 |
+
* 2 =>
|
1434 |
+
* array (
|
1435 |
+
* 'name' => 'jose',
|
1436 |
+
* 'age' => '5',
|
1437 |
+
* 'skill' => 'dances salsa',
|
1438 |
+
* ),
|
1439 |
+
* 3 =>
|
1440 |
+
* array (
|
1441 |
+
* 'name' => 1,
|
1442 |
+
* 'age' => 1,
|
1443 |
+
* 'skill' => 1,
|
1444 |
+
* ),
|
1445 |
+
* 4 =>
|
1446 |
+
* array (
|
1447 |
+
* 'name' => '2',
|
1448 |
+
* 'age' => '2',
|
1449 |
+
* 'skill' => '2',
|
1450 |
+
* ),
|
1451 |
+
* 5 =>
|
1452 |
+
* array (
|
1453 |
+
* 'name' => 3,
|
1454 |
+
* 'age' => 3,
|
1455 |
+
* 'skill' => 3,
|
1456 |
+
* ),
|
1457 |
+
* )
|
1458 |
+
* </code>
|
1459 |
+
*
|
1460 |
+
* @param array $values the values to be appended to the row
|
1461 |
+
*
|
1462 |
+
* @access public
|
1463 |
+
* @return boolean
|
1464 |
+
*/
|
1465 |
+
public function appendRow($values)
|
1466 |
+
{
|
1467 |
+
$this->rows[] = array();
|
1468 |
+
$this->symmetrize();
|
1469 |
+
return $this->fillRow($this->countRows() - 1, $values);
|
1470 |
+
}
|
1471 |
+
|
1472 |
+
/**
|
1473 |
+
* fillRow
|
1474 |
+
*
|
1475 |
+
* Replaces the contents of cells in one given row with $values.
|
1476 |
+
*
|
1477 |
+
* sample of a csv file "my_cool.csv"
|
1478 |
+
*
|
1479 |
+
* <code>
|
1480 |
+
* name,age,skill
|
1481 |
+
* john,13,knows magic
|
1482 |
+
* tanaka,8,makes sushi
|
1483 |
+
* jose,5,dances salsa
|
1484 |
+
* </code>
|
1485 |
+
*
|
1486 |
+
* if we load the csv file and fill the second row with new data?
|
1487 |
+
*
|
1488 |
+
* <code>
|
1489 |
+
* // load the library
|
1490 |
+
* require_once 'File/CSV/DataSource.php';
|
1491 |
+
* $csv = new File_CSV_DataSource;
|
1492 |
+
*
|
1493 |
+
* // load csv file
|
1494 |
+
* $csv->load('my_cool.csv');
|
1495 |
+
*
|
1496 |
+
* // fill exitent row
|
1497 |
+
* var_export($csv->fillRow(1, 'x'));
|
1498 |
+
* </code>
|
1499 |
+
*
|
1500 |
+
* output
|
1501 |
+
*
|
1502 |
+
* <code>
|
1503 |
+
* true
|
1504 |
+
* </code>
|
1505 |
+
*
|
1506 |
+
* now let's dump whatever we have changed
|
1507 |
+
*
|
1508 |
+
* <code>
|
1509 |
+
* var_export($csv->connect());
|
1510 |
+
* </code>
|
1511 |
+
*
|
1512 |
+
* output
|
1513 |
+
*
|
1514 |
+
* <code>
|
1515 |
+
* array (
|
1516 |
+
* 0 =>
|
1517 |
+
* array (
|
1518 |
+
* 'name' => 'john',
|
1519 |
+
* 'age' => '13',
|
1520 |
+
* 'skill' => 'knows magic',
|
1521 |
+
* ),
|
1522 |
+
* 1 =>
|
1523 |
+
* array (
|
1524 |
+
* 'name' => 'x',
|
1525 |
+
* 'age' => 'x',
|
1526 |
+
* 'skill' => 'x',
|
1527 |
+
* ),
|
1528 |
+
* 2 =>
|
1529 |
+
* array (
|
1530 |
+
* 'name' => 'jose',
|
1531 |
+
* 'age' => '5',
|
1532 |
+
* 'skill' => 'dances salsa',
|
1533 |
+
* ),
|
1534 |
+
* )
|
1535 |
+
* </code>
|
1536 |
+
*
|
1537 |
+
* now lets try to fill the row with specific data for each cell
|
1538 |
+
*
|
1539 |
+
* <code>
|
1540 |
+
* var_export($csv->fillRow(1, array(1, 2, 3)));
|
1541 |
+
* </code>
|
1542 |
+
*
|
1543 |
+
* output
|
1544 |
+
*
|
1545 |
+
* <code>
|
1546 |
+
* true
|
1547 |
+
* </code>
|
1548 |
+
*
|
1549 |
+
* and dump the results
|
1550 |
+
*
|
1551 |
+
* <code>
|
1552 |
+
* var_export($csv->connect());
|
1553 |
+
* </code>
|
1554 |
+
*
|
1555 |
+
* output
|
1556 |
+
*
|
1557 |
+
* <code>
|
1558 |
+
*
|
1559 |
+
* array (
|
1560 |
+
* 0 =>
|
1561 |
+
* array (
|
1562 |
+
* 'name' => 'john',
|
1563 |
+
* 'age' => '13',
|
1564 |
+
* 'skill' => 'knows magic',
|
1565 |
+
* ),
|
1566 |
+
* 1 =>
|
1567 |
+
* array (
|
1568 |
+
* 'name' => 1,
|
1569 |
+
* 'age' => 2,
|
1570 |
+
* 'skill' => 3,
|
1571 |
+
* ),
|
1572 |
+
* 2 =>
|
1573 |
+
* array (
|
1574 |
+
* 'name' => 'jose',
|
1575 |
+
* 'age' => '5',
|
1576 |
+
* 'skill' => 'dances salsa',
|
1577 |
+
* ),
|
1578 |
+
* )
|
1579 |
+
* </code>
|
1580 |
+
*
|
1581 |
+
* @param integer $row the row to fill identified by its key
|
1582 |
+
* @param mixed $values the value to use, if a string or number
|
1583 |
+
* is given the whole row will be replaced with this value.
|
1584 |
+
* if an array is given instead the values will be used to fill
|
1585 |
+
* the row. Only when the currently loaded dataset is symmetric
|
1586 |
+
*
|
1587 |
+
* @access public
|
1588 |
+
* @return boolean
|
1589 |
+
* @see isSymmetric(), getAsymmetricRows(), symmetrize(), fillColumn(),
|
1590 |
+
* fillCell(), appendRow()
|
1591 |
+
*/
|
1592 |
+
public function fillRow($row, $values)
|
1593 |
+
{
|
1594 |
+
if (!$this->hasRow($row)) {
|
1595 |
+
return false;
|
1596 |
+
}
|
1597 |
+
|
1598 |
+
if (is_string($values) || is_numeric($values)) {
|
1599 |
+
foreach ($this->rows[$row] as $key => $cell) {
|
1600 |
+
$this->rows[$row][$key] = $values;
|
1601 |
+
}
|
1602 |
+
return true;
|
1603 |
+
}
|
1604 |
+
|
1605 |
+
$eql_to_headers = ($this->countHeaders() == count($values));
|
1606 |
+
if (is_array($values) && $this->isSymmetric() && $eql_to_headers) {
|
1607 |
+
$this->rows[$row] = $values;
|
1608 |
+
return true;
|
1609 |
+
}
|
1610 |
+
|
1611 |
+
return false;
|
1612 |
+
}
|
1613 |
+
|
1614 |
+
/**
|
1615 |
+
* row existance checker
|
1616 |
+
*
|
1617 |
+
* Scans currently loaded dataset and
|
1618 |
+
* checks if a given row identified by $number exists
|
1619 |
+
*
|
1620 |
+
* sample of a csv file "my_cool.csv"
|
1621 |
+
*
|
1622 |
+
* <code>
|
1623 |
+
* name,age,skill
|
1624 |
+
* john,13,knows magic
|
1625 |
+
* tanaka,8,makes sushi
|
1626 |
+
* jose,5,dances salsa
|
1627 |
+
* </code>
|
1628 |
+
*
|
1629 |
+
* load library and csv file
|
1630 |
+
*
|
1631 |
+
* <code>
|
1632 |
+
* require_once 'File/CSV/DataSource.php';
|
1633 |
+
* $csv = new File_CSV_DataSource;
|
1634 |
+
* $csv->load('my_cool.csv');
|
1635 |
+
* </code>
|
1636 |
+
*
|
1637 |
+
* build a relationship and dump it so we can see the rows we will
|
1638 |
+
* be working with
|
1639 |
+
*
|
1640 |
+
* <code>
|
1641 |
+
* var_export($csv->connect());
|
1642 |
+
* </code>
|
1643 |
+
*
|
1644 |
+
* output
|
1645 |
+
*
|
1646 |
+
* <code>
|
1647 |
+
* array (
|
1648 |
+
* 0 =>
|
1649 |
+
* array (
|
1650 |
+
* 'name' => 'john',
|
1651 |
+
* 'age' => '13',
|
1652 |
+
* 'skill' => 'knows magic',
|
1653 |
+
* ),
|
1654 |
+
* 1 => // THIS ROW EXISTS!!!
|
1655 |
+
* array (
|
1656 |
+
* 'name' => 'tanaka',
|
1657 |
+
* 'age' => '8',
|
1658 |
+
* 'skill' => 'makes sushi',
|
1659 |
+
* ),
|
1660 |
+
* 2 =>
|
1661 |
+
* array (
|
1662 |
+
* 'name' => 'jose',
|
1663 |
+
* 'age' => '5',
|
1664 |
+
* 'skill' => 'dances salsa',
|
1665 |
+
* ),
|
1666 |
+
* )
|
1667 |
+
* </code>
|
1668 |
+
*
|
1669 |
+
* now lets check for row existance
|
1670 |
+
*
|
1671 |
+
* <code>
|
1672 |
+
* var_export($csv->hasRow(1));
|
1673 |
+
* var_export($csv->hasRow(-1));
|
1674 |
+
* var_export($csv->hasRow(9999));
|
1675 |
+
* </code>
|
1676 |
+
*
|
1677 |
+
* output
|
1678 |
+
*
|
1679 |
+
* <code>
|
1680 |
+
* true
|
1681 |
+
* false
|
1682 |
+
* false
|
1683 |
+
* </code>
|
1684 |
+
*
|
1685 |
+
* @param mixed $number a numeric value that identifies the row
|
1686 |
+
* you are trying to fetch.
|
1687 |
+
*
|
1688 |
+
* @access public
|
1689 |
+
* @return boolean
|
1690 |
+
* @see getRow(), getRows(), appendRow(), fillRow()
|
1691 |
+
*/
|
1692 |
+
public function hasRow($number)
|
1693 |
+
{
|
1694 |
+
return (in_array($number, array_keys($this->rows)));
|
1695 |
+
}
|
1696 |
+
|
1697 |
+
/**
|
1698 |
+
* row remover
|
1699 |
+
*
|
1700 |
+
* removes one row from the current data set.
|
1701 |
+
*
|
1702 |
+
* sample of a csv file "my_cool.csv"
|
1703 |
+
*
|
1704 |
+
* <code>
|
1705 |
+
* name,age,skill
|
1706 |
+
* john,13,knows magic
|
1707 |
+
* tanaka,8,makes sushi
|
1708 |
+
* jose,5,dances salsa
|
1709 |
+
* </code>
|
1710 |
+
*
|
1711 |
+
* first let's load the file and output whatever was retrived.
|
1712 |
+
*
|
1713 |
+
* <code>
|
1714 |
+
* require_once 'File/CSV/DataSource.php';
|
1715 |
+
* $csv = new File_CSV_DataSource;
|
1716 |
+
* $csv->load('my_cool.csv');
|
1717 |
+
* var_export($csv->connect());
|
1718 |
+
* </code>
|
1719 |
+
*
|
1720 |
+
* output
|
1721 |
+
*
|
1722 |
+
* <code>
|
1723 |
+
*
|
1724 |
+
* array (
|
1725 |
+
* 0 =>
|
1726 |
+
* array (
|
1727 |
+
* 'name' => 'john',
|
1728 |
+
* 'age' => '13',
|
1729 |
+
* 'skill' => 'knows magic',
|
1730 |
+
* ),
|
1731 |
+
* 1 =>
|
1732 |
+
* array (
|
1733 |
+
* 'name' => 'tanaka',
|
1734 |
+
* 'age' => '8',
|
1735 |
+
* 'skill' => 'makes sushi',
|
1736 |
+
* ),
|
1737 |
+
* 2 =>
|
1738 |
+
* array (
|
1739 |
+
* 'name' => 'jose',
|
1740 |
+
* 'age' => '5',
|
1741 |
+
* 'skill' => 'dances salsa',
|
1742 |
+
* ),
|
1743 |
+
* )
|
1744 |
+
* </code>
|
1745 |
+
*
|
1746 |
+
* now lets remove the second row
|
1747 |
+
*
|
1748 |
+
* <code>
|
1749 |
+
* var_export($csv->removeRow(1));
|
1750 |
+
* </code>
|
1751 |
+
*
|
1752 |
+
* output
|
1753 |
+
*
|
1754 |
+
* <code>
|
1755 |
+
* true
|
1756 |
+
* </code>
|
1757 |
+
*
|
1758 |
+
* now lets dump again the data and see what changes have been
|
1759 |
+
* made
|
1760 |
+
*
|
1761 |
+
* <code>
|
1762 |
+
* var_export($csv->connect());
|
1763 |
+
* </code>
|
1764 |
+
*
|
1765 |
+
* output
|
1766 |
+
*
|
1767 |
+
* <code>
|
1768 |
+
* array (
|
1769 |
+
* 0 =>
|
1770 |
+
* array (
|
1771 |
+
* 'name' => 'john',
|
1772 |
+
* 'age' => '13',
|
1773 |
+
* 'skill' => 'knows magic',
|
1774 |
+
* ),
|
1775 |
+
* 1 =>
|
1776 |
+
* array (
|
1777 |
+
* 'name' => 'jose',
|
1778 |
+
* 'age' => '5',
|
1779 |
+
* 'skill' => 'dances salsa',
|
1780 |
+
* ),
|
1781 |
+
* )
|
1782 |
+
* </code>
|
1783 |
+
*
|
1784 |
+
* @param mixed $number the key that identifies that row
|
1785 |
+
*
|
1786 |
+
* @access public
|
1787 |
+
* @return boolean
|
1788 |
+
* @see hasColumn(), getHeaders(), createHeaders(), setHeaders(),
|
1789 |
+
* isSymmetric(), getAsymmetricRows()
|
1790 |
+
*/
|
1791 |
+
public function removeRow($number)
|
1792 |
+
{
|
1793 |
+
$cnt = $this->countRows();
|
1794 |
+
$row = $this->getRow($number);
|
1795 |
+
if (is_array($row) && ($row != array())) {
|
1796 |
+
unset($this->rows[$number]);
|
1797 |
+
} else {
|
1798 |
+
return false;
|
1799 |
+
}
|
1800 |
+
$this->resetKeys($this->rows);
|
1801 |
+
return ($cnt == ($this->countRows() + 1));
|
1802 |
+
}
|
1803 |
+
|
1804 |
+
/**
|
1805 |
+
* row walker
|
1806 |
+
*
|
1807 |
+
* goes through one full row of data and executes a callback
|
1808 |
+
* function per each cell in that row.
|
1809 |
+
*
|
1810 |
+
* Note: callback functions get the value of the cell as an
|
1811 |
+
* argument, and whatever that callback returns will be used to
|
1812 |
+
* replace the current value of that cell.
|
1813 |
+
*
|
1814 |
+
* @param string|integer $row anything that is numeric is a valid row
|
1815 |
+
* identificator. As long as it is within the range of the currently
|
1816 |
+
* loaded dataset
|
1817 |
+
*
|
1818 |
+
* @param string $callback the callback function to be executed
|
1819 |
+
* per each cell in a row
|
1820 |
+
*
|
1821 |
+
* @access public
|
1822 |
+
* @return boolean
|
1823 |
+
* - false if callback does not exist
|
1824 |
+
* - false if row does not exits
|
1825 |
+
*/
|
1826 |
+
public function walkRow($row, $callback)
|
1827 |
+
{
|
1828 |
+
if (!function_exists($callback)) {
|
1829 |
+
return false;
|
1830 |
+
}
|
1831 |
+
if ($this->hasRow($row)) {
|
1832 |
+
foreach ($this->getRow($row) as $key => $value) {
|
1833 |
+
$this->rows[$row][$key] = $callback($value);
|
1834 |
+
}
|
1835 |
+
return true;
|
1836 |
+
}
|
1837 |
+
return false;
|
1838 |
+
}
|
1839 |
+
|
1840 |
+
/**
|
1841 |
+
* raw data as array
|
1842 |
+
*
|
1843 |
+
* Gets the data that was retrived from the csv file as an array
|
1844 |
+
*
|
1845 |
+
* Note: that changes and alterations made to rows, columns and
|
1846 |
+
* values will also reflect on what this function retrives.
|
1847 |
+
*
|
1848 |
+
* @access public
|
1849 |
+
* @return array
|
1850 |
+
* @see connect(), getHeaders(), getRows(), isSymmetric(), getAsymmetricRows(),
|
1851 |
+
* symmetrize()
|
1852 |
+
*/
|
1853 |
+
public function getRawArray()
|
1854 |
+
{
|
1855 |
+
$ret_arr = array();
|
1856 |
+
|
1857 |
+
foreach ($this->rows as $key => $row) {
|
1858 |
+
$item = array();
|
1859 |
+
foreach ($this->headers as $col => $value) {
|
1860 |
+
$item[$value] = $row[$col];
|
1861 |
+
}
|
1862 |
+
array_push($ret_arr, $item);
|
1863 |
+
unset($item);
|
1864 |
+
}
|
1865 |
+
return $ret_arr;
|
1866 |
+
}
|
1867 |
+
|
1868 |
+
/**
|
1869 |
+
* header creator
|
1870 |
+
*
|
1871 |
+
* uses prefix and creates a header for each column suffixed by a
|
1872 |
+
* numeric value
|
1873 |
+
*
|
1874 |
+
* by default the first row is interpreted as headers but if we
|
1875 |
+
* have a csv file with data only and no headers it becomes really
|
1876 |
+
* annoying to work with the current loaded data.
|
1877 |
+
*
|
1878 |
+
* this function will create a set dinamically generated headers
|
1879 |
+
* and make the current headers accessable with the row handling
|
1880 |
+
* functions
|
1881 |
+
*
|
1882 |
+
* Note: that the csv file contains only data but no headers
|
1883 |
+
* sample of a csv file "my_cool.csv"
|
1884 |
+
*
|
1885 |
+
* <code>
|
1886 |
+
* john,13,knows magic
|
1887 |
+
* tanaka,8,makes sushi
|
1888 |
+
* jose,5,dances salsa
|
1889 |
+
* </code>
|
1890 |
+
*
|
1891 |
+
* checks if the csv file was loaded
|
1892 |
+
*
|
1893 |
+
* <code>
|
1894 |
+
* $csv = new File_CSV_DataSource;
|
1895 |
+
* if (!$csv->load('my_cool.csv')) {
|
1896 |
+
* die('can not load csv file');
|
1897 |
+
* }
|
1898 |
+
* </code>
|
1899 |
+
*
|
1900 |
+
* dump current headers
|
1901 |
+
*
|
1902 |
+
* <code>
|
1903 |
+
* var_export($csv->getHeaders());
|
1904 |
+
* </code>
|
1905 |
+
*
|
1906 |
+
* standard output
|
1907 |
+
*
|
1908 |
+
* <code>
|
1909 |
+
* array (
|
1910 |
+
* 0 => 'john',
|
1911 |
+
* 1 => '13',
|
1912 |
+
* 2 => 'knows magic',
|
1913 |
+
* )
|
1914 |
+
* </code>
|
1915 |
+
*
|
1916 |
+
* generate headers named 'column' suffixed by a number and interpret
|
1917 |
+
* the previous headers as rows.
|
1918 |
+
*
|
1919 |
+
* <code>
|
1920 |
+
* $csv->createHeaders('column')
|
1921 |
+
* </code>
|
1922 |
+
*
|
1923 |
+
* dump current headers
|
1924 |
+
*
|
1925 |
+
* <code>
|
1926 |
+
* var_export($csv->getHeaders());
|
1927 |
+
* </code>
|
1928 |
+
*
|
1929 |
+
* standard output
|
1930 |
+
*
|
1931 |
+
* <code>
|
1932 |
+
* array (
|
1933 |
+
* 0 => 'column_1',
|
1934 |
+
* 1 => 'column_2',
|
1935 |
+
* 2 => 'column_3',
|
1936 |
+
* )
|
1937 |
+
* </code>
|
1938 |
+
*
|
1939 |
+
* build a relationship and dump it
|
1940 |
+
*
|
1941 |
+
* <code>
|
1942 |
+
* var_export($csv->connect());
|
1943 |
+
* </code>
|
1944 |
+
*
|
1945 |
+
* output
|
1946 |
+
*
|
1947 |
+
* <code>
|
1948 |
+
*
|
1949 |
+
* array (
|
1950 |
+
* 0 =>
|
1951 |
+
* array (
|
1952 |
+
* 'column_1' => 'john',
|
1953 |
+
* 'column_2' => '13',
|
1954 |
+
* 'column_3' => 'knows magic',
|
1955 |
+
* ),
|
1956 |
+
* 1 =>
|
1957 |
+
* array (
|
1958 |
+
* 'column_1' => 'tanaka',
|
1959 |
+
* 'column_2' => '8',
|
1960 |
+
* 'column_3' => 'makes sushi',
|
1961 |
+
* ),
|
1962 |
+
* 2 =>
|
1963 |
+
* array (
|
1964 |
+
* 'column_1' => 'jose',
|
1965 |
+
* 'column_2' => '5',
|
1966 |
+
* 'column_3' => 'dances salsa',
|
1967 |
+
* ),
|
1968 |
+
* )
|
1969 |
+
* </code>
|
1970 |
+
*
|
1971 |
+
* @param string $prefix string to use as prefix for each
|
1972 |
+
* independent header
|
1973 |
+
*
|
1974 |
+
* @access public
|
1975 |
+
* @return boolean fails if data is not symmetric
|
1976 |
+
* @see isSymmetric(), getAsymmetricRows()
|
1977 |
+
*/
|
1978 |
+
public function createHeaders($prefix)
|
1979 |
+
{
|
1980 |
+
if (!$this->isSymmetric()) {
|
1981 |
+
return false;
|
1982 |
+
}
|
1983 |
+
|
1984 |
+
$length = count($this->headers) + 1;
|
1985 |
+
$this->moveHeadersToRows();
|
1986 |
+
|
1987 |
+
$ret_arr = array();
|
1988 |
+
for ($i = 1; $i < $length; $i ++) {
|
1989 |
+
$ret_arr[] = $prefix . "_$i";
|
1990 |
+
}
|
1991 |
+
$this->headers = $ret_arr;
|
1992 |
+
return $this->isSymmetric();
|
1993 |
+
}
|
1994 |
+
|
1995 |
+
/**
|
1996 |
+
* header injector
|
1997 |
+
*
|
1998 |
+
* uses a $list of values which wil be used to replace current
|
1999 |
+
* headers.
|
2000 |
+
*
|
2001 |
+
* Note: that given $list must match the length of all rows.
|
2002 |
+
* known as symmetric. see isSymmetric() and getAsymmetricRows() methods
|
2003 |
+
*
|
2004 |
+
* Also, that current headers will be used as first row of data
|
2005 |
+
* and consecuently all rows order will change with this action.
|
2006 |
+
*
|
2007 |
+
* sample of a csv file "my_cool.csv"
|
2008 |
+
*
|
2009 |
+
* <code>
|
2010 |
+
* name,age,skill
|
2011 |
+
* john,13,knows magic
|
2012 |
+
* tanaka,8,makes sushi
|
2013 |
+
* jose,5,dances salsa
|
2014 |
+
* </code>
|
2015 |
+
*
|
2016 |
+
* load the library and csv file
|
2017 |
+
*
|
2018 |
+
* <code>
|
2019 |
+
* require_once 'File/CSV/DataSource.php';
|
2020 |
+
* $csv = new File_CSV_DataSource;
|
2021 |
+
* $csv->load('my_cool.csv');
|
2022 |
+
* </code>
|
2023 |
+
*
|
2024 |
+
* lets dump currently loaded data
|
2025 |
+
* <code>
|
2026 |
+
* var_export($csv->connect());
|
2027 |
+
* </code>
|
2028 |
+
*
|
2029 |
+
* output
|
2030 |
+
*
|
2031 |
+
* <code>
|
2032 |
+
* array (
|
2033 |
+
* 0 =>
|
2034 |
+
* array (
|
2035 |
+
* 'name' => 'john',
|
2036 |
+
* 'age' => '13',
|
2037 |
+
* 'skill' => 'knows magic',
|
2038 |
+
* ),
|
2039 |
+
* 1 =>
|
2040 |
+
* array (
|
2041 |
+
* 'name' => 'tanaka',
|
2042 |
+
* 'age' => '8',
|
2043 |
+
* 'skill' => 'makes sushi',
|
2044 |
+
* ),
|
2045 |
+
* 2 =>
|
2046 |
+
* array (
|
2047 |
+
* 'name' => 'jose',
|
2048 |
+
* 'age' => '5',
|
2049 |
+
* 'skill' => 'dances salsa',
|
2050 |
+
* ),
|
2051 |
+
* )
|
2052 |
+
* </code>
|
2053 |
+
*
|
2054 |
+
* And now lets create a new set of headers and attempt to inject
|
2055 |
+
* them into the current loaded dataset
|
2056 |
+
*
|
2057 |
+
* <code>
|
2058 |
+
* $new_headers = array('a', 'b', 'c');
|
2059 |
+
* var_export($csv->setHeaders($new_headers));
|
2060 |
+
* </code>
|
2061 |
+
*
|
2062 |
+
* output
|
2063 |
+
*
|
2064 |
+
* <code>
|
2065 |
+
* true
|
2066 |
+
* </code>
|
2067 |
+
*
|
2068 |
+
* Now lets try the same with some headers that do not match the
|
2069 |
+
* current headers length. (this should fail)
|
2070 |
+
*
|
2071 |
+
* <code>
|
2072 |
+
* $new_headers = array('a', 'b');
|
2073 |
+
* var_export($csv->setHeaders($new_headers));
|
2074 |
+
* </code>
|
2075 |
+
*
|
2076 |
+
* output
|
2077 |
+
*
|
2078 |
+
* <code>
|
2079 |
+
* false
|
2080 |
+
* </code>
|
2081 |
+
*
|
2082 |
+
* now let's dump whatever we have changed
|
2083 |
+
*
|
2084 |
+
* <code>
|
2085 |
+
* var_export($csv->connect());
|
2086 |
+
* </code>
|
2087 |
+
*
|
2088 |
+
* output
|
2089 |
+
*
|
2090 |
+
* <code>
|
2091 |
+
* array (
|
2092 |
+
* 0 =>
|
2093 |
+
* array (
|
2094 |
+
* 'a' => 'name',
|
2095 |
+
* 'b' => 'age',
|
2096 |
+
* 'c' => 'skill',
|
2097 |
+
* ),
|
2098 |
+
* 1 =>
|
2099 |
+
* array (
|
2100 |
+
* 'a' => 'john',
|
2101 |
+
* 'b' => '13',
|
2102 |
+
* 'c' => 'knows magic',
|
2103 |
+
* ),
|
2104 |
+
* 2 =>
|
2105 |
+
* array (
|
2106 |
+
* 'a' => 'tanaka',
|
2107 |
+
* 'b' => '8',
|
2108 |
+
* 'c' => 'makes sushi',
|
2109 |
+
* ),
|
2110 |
+
* 3 =>
|
2111 |
+
* array (
|
2112 |
+
* 'a' => 'jose',
|
2113 |
+
* 'b' => '5',
|
2114 |
+
* 'c' => 'dances salsa',
|
2115 |
+
* ),
|
2116 |
+
* )
|
2117 |
+
* </code>
|
2118 |
+
*
|
2119 |
+
* @param array $list a collection of names to use as headers,
|
2120 |
+
*
|
2121 |
+
* @access public
|
2122 |
+
* @return boolean fails if data is not symmetric
|
2123 |
+
* @see isSymmetric(), getAsymmetricRows(), getHeaders(), createHeaders()
|
2124 |
+
*/
|
2125 |
+
public function setHeaders($list)
|
2126 |
+
{
|
2127 |
+
if (!$this->isSymmetric()) {
|
2128 |
+
return false;
|
2129 |
+
}
|
2130 |
+
if (!is_array($list)) {
|
2131 |
+
return false;
|
2132 |
+
}
|
2133 |
+
if (count($list) != count($this->headers)) {
|
2134 |
+
return false;
|
2135 |
+
}
|
2136 |
+
$this->moveHeadersToRows();
|
2137 |
+
$this->headers = $list;
|
2138 |
+
return true;
|
2139 |
+
}
|
2140 |
+
|
2141 |
+
/**
|
2142 |
+
* csv parser
|
2143 |
+
*
|
2144 |
+
* reads csv data and transforms it into php-data
|
2145 |
+
*
|
2146 |
+
* @access protected
|
2147 |
+
* @return boolean
|
2148 |
+
*/
|
2149 |
+
protected function parse()
|
2150 |
+
{
|
2151 |
+
if (!$this->validates()) {
|
2152 |
+
return false;
|
2153 |
+
}
|
2154 |
+
|
2155 |
+
$c = 0;
|
2156 |
+
$d = $this->settings['delimiter'];
|
2157 |
+
$e = $this->settings['escape'];
|
2158 |
+
$l = $this->settings['length'];
|
2159 |
+
|
2160 |
+
$res = fopen($this->_filename, 'r');
|
2161 |
+
|
2162 |
+
$create_new_headers = false;
|
2163 |
+
|
2164 |
+
while ($keys = fgetcsv($res, $l, $d, $e)) {
|
2165 |
+
|
2166 |
+
if ($c == 0) {
|
2167 |
+
foreach ($keys as $key => $value) {
|
2168 |
+
if (preg_match('%\W(http:|https:|ftp:)$%i', $value) or strlen($value) > 15 or is_numeric($value)) {
|
2169 |
+
$create_new_headers = true;
|
2170 |
+
break;
|
2171 |
+
}
|
2172 |
+
}
|
2173 |
+
$this->headers = $keys;
|
2174 |
+
} else {
|
2175 |
+
array_push($this->rows, $keys);
|
2176 |
+
}
|
2177 |
+
|
2178 |
+
$c ++;
|
2179 |
+
}
|
2180 |
+
|
2181 |
+
fclose($res);
|
2182 |
+
$this->removeEmpty();
|
2183 |
+
|
2184 |
+
if ($create_new_headers) $this->createHeaders('column');
|
2185 |
+
|
2186 |
+
return true;
|
2187 |
+
}
|
2188 |
+
|
2189 |
+
/**
|
2190 |
+
* empty row remover
|
2191 |
+
*
|
2192 |
+
* removes all records that have been defined but have no data.
|
2193 |
+
*
|
2194 |
+
* @access protected
|
2195 |
+
* @return array containing only the rows that have data
|
2196 |
+
*/
|
2197 |
+
protected function removeEmpty()
|
2198 |
+
{
|
2199 |
+
$ret_arr = array();
|
2200 |
+
foreach ($this->rows as $row) {
|
2201 |
+
$line = trim(join('', $row));
|
2202 |
+
if (!empty($line)) {
|
2203 |
+
$ret_arr[] = $row;
|
2204 |
+
}
|
2205 |
+
}
|
2206 |
+
$this->rows = $ret_arr;
|
2207 |
+
}
|
2208 |
+
|
2209 |
+
/**
|
2210 |
+
* csv file validator
|
2211 |
+
*
|
2212 |
+
* checks wheather if the given csv file is valid or not
|
2213 |
+
*
|
2214 |
+
* @access protected
|
2215 |
+
* @return boolean
|
2216 |
+
*/
|
2217 |
+
protected function validates()
|
2218 |
+
{
|
2219 |
+
// file existance
|
2220 |
+
if (!file_exists($this->_filename)) {
|
2221 |
+
return false;
|
2222 |
+
}
|
2223 |
+
|
2224 |
+
// file readability
|
2225 |
+
if (!is_readable($this->_filename)) {
|
2226 |
+
return false;
|
2227 |
+
}
|
2228 |
+
|
2229 |
+
return true;
|
2230 |
+
}
|
2231 |
+
|
2232 |
+
/**
|
2233 |
+
* header relocator
|
2234 |
+
*
|
2235 |
+
* @access protected
|
2236 |
+
* @return void
|
2237 |
+
*/
|
2238 |
+
protected function moveHeadersToRows()
|
2239 |
+
{
|
2240 |
+
$arr = array();
|
2241 |
+
$arr[] = $this->headers;
|
2242 |
+
foreach ($this->rows as $row) {
|
2243 |
+
$arr[] = $row;
|
2244 |
+
}
|
2245 |
+
$this->rows = $arr;
|
2246 |
+
$this->headers = array();
|
2247 |
+
}
|
2248 |
+
|
2249 |
+
/**
|
2250 |
+
* array key reseter
|
2251 |
+
*
|
2252 |
+
* makes sure that an array's keys are setted in a correct numerical order
|
2253 |
+
*
|
2254 |
+
* Note: that this function does not return anything, all changes
|
2255 |
+
* are made to the original array as a reference
|
2256 |
+
*
|
2257 |
+
* @param array &$array any array, if keys are strings they will
|
2258 |
+
* be replaced with numeric values
|
2259 |
+
*
|
2260 |
+
* @access protected
|
2261 |
+
* @return void
|
2262 |
+
*/
|
2263 |
+
protected function resetKeys(&$array)
|
2264 |
+
{
|
2265 |
+
$arr = array();
|
2266 |
+
foreach ($array as $item) {
|
2267 |
+
$arr[] = $item;
|
2268 |
+
}
|
2269 |
+
$array = $arr;
|
2270 |
+
}
|
2271 |
+
|
2272 |
+
/**
|
2273 |
+
* object data flusher
|
2274 |
+
*
|
2275 |
+
* tells this object to forget all data loaded and start from
|
2276 |
+
* scratch
|
2277 |
+
*
|
2278 |
+
* @access protected
|
2279 |
+
* @return void
|
2280 |
+
*/
|
2281 |
+
protected function flush()
|
2282 |
+
{
|
2283 |
+
$this->rows = array();
|
2284 |
+
$this->headers = array();
|
2285 |
+
}
|
2286 |
+
|
2287 |
+
public function toXml() {
|
2288 |
+
$this->symmetrize();
|
2289 |
+
return ArrayToXML::toXml($this->getRawArray());
|
2290 |
+
}
|
2291 |
+
|
2292 |
+
function analyse_file($file, $capture_limit_in_kb = 10) {
|
2293 |
+
// capture starting memory usage
|
2294 |
+
$output['peak_mem']['start'] = memory_get_peak_usage(true);
|
2295 |
+
|
2296 |
+
// log the limit how much of the file was sampled (in Kb)
|
2297 |
+
$output['read_kb'] = $capture_limit_in_kb;
|
2298 |
+
|
2299 |
+
// read in file
|
2300 |
+
$fh = fopen($file, 'r');
|
2301 |
+
$contents = fread($fh, ($capture_limit_in_kb * 1024)); // in KB
|
2302 |
+
fclose($fh);
|
2303 |
+
|
2304 |
+
// specify allowed field delimiters
|
2305 |
+
$delimiters = array(
|
2306 |
+
'comma' => ',',
|
2307 |
+
'semicolon' => ';',
|
2308 |
+
'tab' => "\t",
|
2309 |
+
'pipe' => '|',
|
2310 |
+
'colon' => ':'
|
2311 |
+
);
|
2312 |
+
|
2313 |
+
// specify allowed line endings
|
2314 |
+
$line_endings = array(
|
2315 |
+
'rn' => "\r\n",
|
2316 |
+
'n' => "\n",
|
2317 |
+
'r' => "\r",
|
2318 |
+
'nr' => "\n\r"
|
2319 |
+
);
|
2320 |
+
|
2321 |
+
// loop and count each line ending instance
|
2322 |
+
foreach ($line_endings as $key => $value) {
|
2323 |
+
$line_result[$key] = substr_count($contents, $value);
|
2324 |
+
}
|
2325 |
+
|
2326 |
+
// sort by largest array value
|
2327 |
+
asort($line_result);
|
2328 |
+
|
2329 |
+
// log to output array
|
2330 |
+
$output['line_ending']['results'] = $line_result;
|
2331 |
+
$output['line_ending']['count'] = end($line_result);
|
2332 |
+
$output['line_ending']['key'] = key($line_result);
|
2333 |
+
$output['line_ending']['value'] = $line_endings[$output['line_ending']['key']];
|
2334 |
+
$lines = explode($output['line_ending']['value'], $contents);
|
2335 |
+
|
2336 |
+
// remove last line of array, as this maybe incomplete?
|
2337 |
+
array_pop($lines);
|
2338 |
+
|
2339 |
+
// create a string from the legal lines
|
2340 |
+
$complete_lines = implode(' ', $lines);
|
2341 |
+
|
2342 |
+
// log statistics to output array
|
2343 |
+
$output['lines']['count'] = count($lines);
|
2344 |
+
$output['lines']['length'] = strlen($complete_lines);
|
2345 |
+
|
2346 |
+
// loop and count each delimiter instance
|
2347 |
+
foreach ($delimiters as $delimiter_key => $delimiter) {
|
2348 |
+
$delimiter_result[$delimiter_key] = substr_count($complete_lines, $delimiter);
|
2349 |
+
}
|
2350 |
+
|
2351 |
+
// sort by largest array value
|
2352 |
+
asort($delimiter_result);
|
2353 |
+
|
2354 |
+
// log statistics to output array with largest counts as the value
|
2355 |
+
$output['delimiter']['results'] = $delimiter_result;
|
2356 |
+
$output['delimiter']['count'] = end($delimiter_result);
|
2357 |
+
$output['delimiter']['key'] = key($delimiter_result);
|
2358 |
+
$output['delimiter']['value'] = $delimiters[$output['delimiter']['key']];
|
2359 |
+
|
2360 |
+
// capture ending memory usage
|
2361 |
+
$output['peak_mem']['end'] = memory_get_peak_usage(true);
|
2362 |
+
return $output;
|
2363 |
+
}
|
2364 |
+
|
2365 |
+
}
|
2366 |
+
|
2367 |
+
class ArrayToXML
|
2368 |
+
{
|
2369 |
+
/**
|
2370 |
+
* The main function for converting to an XML document.
|
2371 |
+
* Pass in a multi dimensional array and this recrusively loops through and builds up an XML document.
|
2372 |
+
*
|
2373 |
+
* @param array $data
|
2374 |
+
* @param string $rootNodeName - what you want the root node to be - defaultsto data.
|
2375 |
+
* @param SimpleXMLElement $xml - should only be used recursively
|
2376 |
+
* @return string XML
|
2377 |
+
*/
|
2378 |
+
public static function toXml($data, $rootNodeName = 'data', $xml=null)
|
2379 |
+
{
|
2380 |
+
// turn off compatibility mode as simple xml throws a wobbly if you don't.
|
2381 |
+
if (ini_get('zend.ze1_compatibility_mode') == 1)
|
2382 |
+
{
|
2383 |
+
ini_set ('zend.ze1_compatibility_mode', 0);
|
2384 |
+
}
|
2385 |
+
|
2386 |
+
if ($xml == null)
|
2387 |
+
{
|
2388 |
+
$xml = simplexml_load_string('<?xml version="1.0" encoding="utf-8"?><'.$rootNodeName .'/>');
|
2389 |
+
}
|
2390 |
+
|
2391 |
+
// loop through the data passed in.
|
2392 |
+
foreach($data as $key => $value)
|
2393 |
+
{
|
2394 |
+
// no numeric keys in our xml please!
|
2395 |
+
if (is_numeric($key))
|
2396 |
+
{
|
2397 |
+
// make string key...
|
2398 |
+
$key = "node";
|
2399 |
+
}
|
2400 |
+
|
2401 |
+
// replace anything not alpha numeric
|
2402 |
+
$key = preg_replace('/[^a-z0-9_]/i', '', $key);
|
2403 |
+
|
2404 |
+
// if there is another array found recrusively call this function
|
2405 |
+
if (is_array($value) or is_object($value))
|
2406 |
+
{
|
2407 |
+
$node = $xml->addChild($key);
|
2408 |
+
// recrusive call.
|
2409 |
+
ArrayToXML::toXml($value, $rootNodeName, $node);
|
2410 |
+
}
|
2411 |
+
else
|
2412 |
+
{
|
2413 |
+
// add single node.
|
2414 |
+
//$value = htmlentities($value);
|
2415 |
+
$xml->addChild($key,$value);
|
2416 |
+
}
|
2417 |
+
|
2418 |
+
|
2419 |
+
}
|
2420 |
+
// pass back as string. or simple xml object if you want!
|
2421 |
+
return $xml->asXML();
|
2422 |
+
}
|
2423 |
+
|
2424 |
+
|
2425 |
+
}
|
2426 |
+
|
2427 |
+
?>
|
libraries/XmlImportException.php
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
|
4 |
+
* @package General
|
5 |
+
*/
|
6 |
+
|
7 |
+
/**
|
8 |
+
* XmlImport library exception
|
9 |
+
*/
|
10 |
+
class XmlImportException extends Exception {}
|
libraries/XmlImportParser.php
ADDED
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
|
4 |
+
* @author Pavel Kulbakin <p.kulbakin@gmail.com>
|
5 |
+
* @package General
|
6 |
+
*/
|
7 |
+
|
8 |
+
require_once dirname(__FILE__) . '/XmlImportTemplate.php';
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Is used to parse XML using specified template and root node
|
12 |
+
*/
|
13 |
+
class XmlImportParser {
|
14 |
+
/**
|
15 |
+
* SimpleXmlElement instance for the xml file
|
16 |
+
*
|
17 |
+
* @var SimpleXMLElement
|
18 |
+
*/
|
19 |
+
protected $xml;
|
20 |
+
|
21 |
+
/**
|
22 |
+
* XPath expression for selecting the root node of the record
|
23 |
+
*
|
24 |
+
* @var string
|
25 |
+
*/
|
26 |
+
protected $rootNodeXPath;
|
27 |
+
|
28 |
+
/**
|
29 |
+
* Path to cached template
|
30 |
+
*
|
31 |
+
* @var string
|
32 |
+
*/
|
33 |
+
protected $cachedTemplate;
|
34 |
+
|
35 |
+
/**
|
36 |
+
* Creates new parser instance
|
37 |
+
*
|
38 |
+
* @param string $xml
|
39 |
+
* @param string $rootNodeXPath XPath for the record root node
|
40 |
+
* @param string $cachedTemplate path to cached template
|
41 |
+
* @param bool $isURL whether xml is URL or path
|
42 |
+
*/
|
43 |
+
public function __construct($xml, $rootNodeXPath, $cachedTemplate, $isURL = false)
|
44 |
+
{
|
45 |
+
if ($isURL) {
|
46 |
+
$xml = file_get_contents($xml);
|
47 |
+
}
|
48 |
+
|
49 |
+
// FIX: remove default namespace because libxml xpath implementation doesn't handle it properly
|
50 |
+
$xml and $xml = preg_replace('%xmlns\s*=\s*([\'"]).*\1%sU', '', $xml);
|
51 |
+
|
52 |
+
$this->xml = new SimpleXMLElement($xml);
|
53 |
+
$this->rootNodeXPath = $rootNodeXPath;
|
54 |
+
$this->cachedTemplate = $cachedTemplate;
|
55 |
+
}
|
56 |
+
|
57 |
+
/**
|
58 |
+
* Gets the parser results for all records or
|
59 |
+
* the part of them if $start and $count parameters are passed
|
60 |
+
*
|
61 |
+
* @param array[optional] $records Sequence numbers of records to import (first record corresponds to 1)
|
62 |
+
* @return array
|
63 |
+
*/
|
64 |
+
public function parse($records = array())
|
65 |
+
{
|
66 |
+
empty($records) or is_array($records) or $records = array($records);
|
67 |
+
|
68 |
+
$result = array();
|
69 |
+
|
70 |
+
$rootNodes = $this->xml->xpath($this->rootNodeXPath);
|
71 |
+
if ($rootNodes === false)
|
72 |
+
throw new XmlImportException('Invalid root node XPath \'' . $this->rootNodeXPath . '\' specified');
|
73 |
+
|
74 |
+
for ($i = 0; $i < count($rootNodes); $i++) {
|
75 |
+
if (empty($records) or in_array(($i + 1) + 10 * $_SESSION['pmxi_import']['step'], $records)) {
|
76 |
+
$rootNode = $rootNodes[$i];
|
77 |
+
$template = new XmlImportTemplate($rootNode, $this->cachedTemplate);
|
78 |
+
$result[] = $template->parse();
|
79 |
+
}
|
80 |
+
}
|
81 |
+
|
82 |
+
return $result;
|
83 |
+
}
|
84 |
+
|
85 |
+
/**
|
86 |
+
* Creates new parser instance for text template specified
|
87 |
+
*
|
88 |
+
* @param string $xml
|
89 |
+
* @param string $rootNodeXPath XPath for the record root node
|
90 |
+
* @param string $template template
|
91 |
+
* @param string &$file path to the cached template
|
92 |
+
* @return XmlImportParser
|
93 |
+
*/
|
94 |
+
public static function factory($xml, $rootNodeXPath, $template, &$file = NULL)
|
95 |
+
{
|
96 |
+
|
97 |
+
$scanner = new XmlImportTemplateScanner();
|
98 |
+
$tokens = $scanner->scan(new XmlImportStringReader($template));
|
99 |
+
$t_parser = new XmlImportTemplateParser($tokens);
|
100 |
+
$tree = $t_parser->parse();
|
101 |
+
$codegenerator = new XmlImportTemplateCodeGenerator($tree);
|
102 |
+
$file = $codegenerator->generate();
|
103 |
+
|
104 |
+
return new self($xml, $rootNodeXPath, $file);
|
105 |
+
}
|
106 |
+
}
|
libraries/XmlImportReaderInterface.php
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
|
4 |
+
* @package General
|
5 |
+
*/
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Interface that allows to either peek or read symbol from class that implements it
|
9 |
+
*/
|
10 |
+
interface XmlImportReaderInterface
|
11 |
+
{
|
12 |
+
/**
|
13 |
+
* Peeks a symbol
|
14 |
+
*/
|
15 |
+
public function peek();
|
16 |
+
|
17 |
+
/**
|
18 |
+
* Reads a symbol
|
19 |
+
*/
|
20 |
+
public function read();
|
21 |
+
}
|
libraries/XmlImportStringReader.php
ADDED
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
|
4 |
+
* @package General
|
5 |
+
*/
|
6 |
+
|
7 |
+
require_once dirname(__FILE__) . '/XmlImportReaderInterface.php';
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Allows to either peek or read a character from a string buffer
|
11 |
+
*/
|
12 |
+
class XmlImportStringReader implements XmlImportReaderInterface
|
13 |
+
{
|
14 |
+
/**
|
15 |
+
* String buffer
|
16 |
+
*
|
17 |
+
* @var string
|
18 |
+
*/
|
19 |
+
private $buffer;
|
20 |
+
|
21 |
+
/**
|
22 |
+
* Current index
|
23 |
+
*
|
24 |
+
* @var int
|
25 |
+
*/
|
26 |
+
private $index = -1;
|
27 |
+
|
28 |
+
/**
|
29 |
+
* Creates new instance
|
30 |
+
*
|
31 |
+
* @param string $input
|
32 |
+
*/
|
33 |
+
public function __construct($input)
|
34 |
+
{
|
35 |
+
if (is_string($input))
|
36 |
+
$this->buffer = $input;
|
37 |
+
else
|
38 |
+
throw new InvalidArgumentException("String expected as argument.");
|
39 |
+
}
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Returns the next symbol from the buffer without changes to current index
|
43 |
+
* or false if buffer ends
|
44 |
+
*
|
45 |
+
* @return string
|
46 |
+
*/
|
47 |
+
public function peek()
|
48 |
+
{
|
49 |
+
if ($this->index + 1 >= strlen($this->buffer))
|
50 |
+
return false;
|
51 |
+
else
|
52 |
+
return $this->buffer[$this->index + 1];
|
53 |
+
}
|
54 |
+
|
55 |
+
/**
|
56 |
+
* Returns the next symbol from the buffer or false if buffer is ended
|
57 |
+
*
|
58 |
+
* @return string
|
59 |
+
*/
|
60 |
+
public function read()
|
61 |
+
{
|
62 |
+
$result = $this->peek();
|
63 |
+
if ($this->index < strlen($this->buffer))
|
64 |
+
$this->index++;
|
65 |
+
return $result;
|
66 |
+
}
|
67 |
+
}
|
libraries/XmlImportTemplate.php
ADDED
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
|
4 |
+
* @author Pavel Kulbakin <p.kulbakin@gmail.com>
|
5 |
+
* @package General
|
6 |
+
*/
|
7 |
+
|
8 |
+
require_once dirname(__FILE__) . '/XmlImportConfig.php';
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Represents a template
|
12 |
+
*/
|
13 |
+
class XmlImportTemplate {
|
14 |
+
/**
|
15 |
+
* Root element of the record that is being parsed
|
16 |
+
*
|
17 |
+
* @var SimpleXMLElement
|
18 |
+
*/
|
19 |
+
protected $xml;
|
20 |
+
|
21 |
+
/**
|
22 |
+
* file name of the cached template
|
23 |
+
*
|
24 |
+
* @var string
|
25 |
+
*/
|
26 |
+
protected $cachedTemplate;
|
27 |
+
|
28 |
+
/**
|
29 |
+
* Creates new instance
|
30 |
+
*
|
31 |
+
* @param SimpleXmlElement $xml
|
32 |
+
* @param string $cachedTemplate
|
33 |
+
*/
|
34 |
+
public function __construct($xml, $cachedTemplate)
|
35 |
+
{
|
36 |
+
$this->xml = $xml;
|
37 |
+
$this->cachedTemplate = $cachedTemplate;
|
38 |
+
}
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Parses a template using {@see $this->xml}
|
42 |
+
*
|
43 |
+
* @return string
|
44 |
+
*/
|
45 |
+
public function parse()
|
46 |
+
{
|
47 |
+
|
48 |
+
ob_start();
|
49 |
+
$err_lvl = error_reporting(E_ALL);
|
50 |
+
include $this->cachedTemplate;
|
51 |
+
error_reporting($err_lvl);
|
52 |
+
|
53 |
+
return ob_get_clean();
|
54 |
+
}
|
55 |
+
|
56 |
+
/**
|
57 |
+
* Get the value by XPath expression
|
58 |
+
*
|
59 |
+
* @param SimpleXmlElement $xpath XPath result
|
60 |
+
* @return mixed
|
61 |
+
*/
|
62 |
+
protected function getValue($xpath)
|
63 |
+
{
|
64 |
+
|
65 |
+
if (is_array($xpath) && count($xpath) > 0) {
|
66 |
+
$result = array();
|
67 |
+
foreach ($xpath as $xp) { // cancatenate multiple elements into 1 string
|
68 |
+
ob_start();
|
69 |
+
echo $xp;
|
70 |
+
$result[] = trim(ob_get_clean());
|
71 |
+
}
|
72 |
+
return implode(XmlImportConfig::getInstance()->getMultiGlue(), $result);
|
73 |
+
} else {
|
74 |
+
// return null if nothing found
|
75 |
+
return null;
|
76 |
+
}
|
77 |
+
}
|
78 |
+
}
|
libraries/XmlImportTemplateCodeGenerator.php
ADDED
@@ -0,0 +1,341 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
|
4 |
+
* @package General
|
5 |
+
*/
|
6 |
+
|
7 |
+
require_once dirname(__FILE__) . '/XmlImportConfig.php';
|
8 |
+
require_once dirname(__FILE__) . '/ast/XmlImportAstSequence.php';
|
9 |
+
require_once dirname(__FILE__) . '/ast/XmlImportAstText.php';
|
10 |
+
require_once dirname(__FILE__) . '/ast/XmlImportAstPrint.php';
|
11 |
+
require_once dirname(__FILE__) . '/ast/XmlImportAstInteger.php';
|
12 |
+
require_once dirname(__FILE__) . '/ast/XmlImportAstFloat.php';
|
13 |
+
require_once dirname(__FILE__) . '/ast/XmlImportAstString.php';
|
14 |
+
require_once dirname(__FILE__) . '/ast/XmlImportAstXPath.php';
|
15 |
+
require_once dirname(__FILE__) . '/ast/XmlImportAstFunction.php';
|
16 |
+
require_once dirname(__FILE__) . '/ast/XmlImportAstWith.php';
|
17 |
+
require_once dirname(__FILE__) . '/ast/XmlImportAstForeach.php';
|
18 |
+
require_once dirname(__FILE__) . '/ast/XmlImportAstIf.php';
|
19 |
+
require_once dirname(__FILE__) . '/ast/XmlImportAstMath.php';
|
20 |
+
|
21 |
+
/**
|
22 |
+
* Is used to generate a PHP code from AST (Abstract Syntax Tree)
|
23 |
+
*/
|
24 |
+
class XmlImportTemplateCodeGenerator
|
25 |
+
{
|
26 |
+
|
27 |
+
/**
|
28 |
+
* Top level statement sequence
|
29 |
+
*
|
30 |
+
* @var XmlImportAstSequence
|
31 |
+
*/
|
32 |
+
private $sequence;
|
33 |
+
|
34 |
+
/**
|
35 |
+
* statement sequence stack
|
36 |
+
*
|
37 |
+
* @var array
|
38 |
+
*/
|
39 |
+
private $sequenceStack = array();
|
40 |
+
|
41 |
+
/**
|
42 |
+
* SimpleXmlElement variable number
|
43 |
+
*
|
44 |
+
* @var int
|
45 |
+
*/
|
46 |
+
private $xmlVariableNumber = 0;
|
47 |
+
|
48 |
+
/**
|
49 |
+
* Previous statement
|
50 |
+
*
|
51 |
+
* @var XmlImportAstStatement
|
52 |
+
*/
|
53 |
+
private $previousStatement = null;
|
54 |
+
|
55 |
+
/**
|
56 |
+
* Stack of SimpleXmlElement instances
|
57 |
+
*
|
58 |
+
* @var array
|
59 |
+
*/
|
60 |
+
private $xmlStack = array();
|
61 |
+
|
62 |
+
/**
|
63 |
+
* Whether PHP tag is open
|
64 |
+
*
|
65 |
+
* @var bool
|
66 |
+
*/
|
67 |
+
private $isPhpTagOpen = false;
|
68 |
+
|
69 |
+
/**
|
70 |
+
* Creates new instance
|
71 |
+
*
|
72 |
+
* @param XmlImportAstSequence $sequence
|
73 |
+
*/
|
74 |
+
public function __construct(XmlImportAstSequence $sequence)
|
75 |
+
{
|
76 |
+
$this->sequence = $sequence;
|
77 |
+
}
|
78 |
+
|
79 |
+
/**
|
80 |
+
* Generates the code and returns the filename under which it was stored in
|
81 |
+
* cache directory. If $filename is null then it is generated automatically
|
82 |
+
*
|
83 |
+
* @param string $filename
|
84 |
+
* @return string
|
85 |
+
*/
|
86 |
+
public function generate($filename = null)
|
87 |
+
{
|
88 |
+
$result = '';
|
89 |
+
if (count($this->sequence->getVariables()))
|
90 |
+
{
|
91 |
+
$var = '$x' . $this->xmlVariableNumber++;
|
92 |
+
array_push($this->xmlStack, $var);
|
93 |
+
$result .= $this->openPhpTag() . $var . ' = $this->xml;' ;
|
94 |
+
}
|
95 |
+
$result .= $this->generateForSequence($this->sequence);
|
96 |
+
|
97 |
+
if (count($this->sequence->getVariables()))
|
98 |
+
{
|
99 |
+
array_pop($this->xmlStack);
|
100 |
+
}
|
101 |
+
if (is_null($filename))
|
102 |
+
{
|
103 |
+
$filename = tempnam(XmlImportConfig::getInstance()->getCacheDirectory(), 'xim');
|
104 |
+
}
|
105 |
+
|
106 |
+
file_put_contents($filename, $result);
|
107 |
+
return $filename;
|
108 |
+
}
|
109 |
+
|
110 |
+
/**
|
111 |
+
* Generates code for a statement sequence
|
112 |
+
*
|
113 |
+
* @param XmlImportAstSequence $sequence
|
114 |
+
* @return string
|
115 |
+
*/
|
116 |
+
private function generateForSequence(XmlImportAstSequence $sequence)
|
117 |
+
{
|
118 |
+
array_push($this->sequenceStack, $sequence);
|
119 |
+
$result = '';
|
120 |
+
if (count($sequence->getVariableDefinitions()) > 0)
|
121 |
+
{
|
122 |
+
$result .= $this->openPhpTag();
|
123 |
+
foreach ($sequence->getVariableDefinitions() as $xpath)
|
124 |
+
{
|
125 |
+
$result .= PHP_EOL . str_replace('{{XML}}', $this->xmlStack[count($this->xmlStack) - 1], $xpath);
|
126 |
+
}
|
127 |
+
$result .= PHP_EOL;
|
128 |
+
}
|
129 |
+
foreach ($sequence->getStatements() as $statement)
|
130 |
+
{
|
131 |
+
$result .= $this->generateForStatement($statement);
|
132 |
+
}
|
133 |
+
array_pop($this->sequenceStack);
|
134 |
+
return $result;
|
135 |
+
}
|
136 |
+
|
137 |
+
/**
|
138 |
+
* Generates code for a statement
|
139 |
+
*
|
140 |
+
* @param XmlImportAstStatement $statement
|
141 |
+
* @return string
|
142 |
+
*/
|
143 |
+
private function generateForStatement(XmlImportAstStatement $statement)
|
144 |
+
{
|
145 |
+
$result = '';
|
146 |
+
if ($statement instanceof XmlImportAstText)
|
147 |
+
{
|
148 |
+
$result .= $this->closePhpTag();
|
149 |
+
$text = preg_replace('%<\?|\?>%', '<?php echo "$0"; ?>', $statement->getValue()); // escape php tags
|
150 |
+
if ($this->previousStatement instanceof XmlImportAstPrint && (strpos($text, "\n") === 0 || strpos($text, "\r\n") === 0))
|
151 |
+
{
|
152 |
+
$result .= PHP_EOL;
|
153 |
+
}
|
154 |
+
$result .= $text;
|
155 |
+
}
|
156 |
+
else
|
157 |
+
{
|
158 |
+
$result .= $this->openPhpTag();
|
159 |
+
if ($statement instanceof XmlImportAstPrint)
|
160 |
+
{
|
161 |
+
$result .= 'echo ';
|
162 |
+
$result .= $this->generateForExpression($statement->getValue(), true) . ';';
|
163 |
+
}
|
164 |
+
elseif ($statement instanceof XmlImportAstWith)
|
165 |
+
{
|
166 |
+
$var = '$x' . $this->xmlVariableNumber++;
|
167 |
+
$result .= PHP_EOL . $var . ' = ' ;
|
168 |
+
$result .= $this->generateForExpression($statement->getXpath()) . ';' . PHP_EOL;
|
169 |
+
|
170 |
+
array_push($this->xmlStack, $var);
|
171 |
+
$result .= 'if (' . $var . ' !== false && count(' . $var . ') > 0) :' . PHP_EOL . $var .
|
172 |
+
' = ' . $var . '[0];' . PHP_EOL;
|
173 |
+
$result .= $this->generateForSequence($statement->getBody());
|
174 |
+
array_pop($this->xmlStack);
|
175 |
+
$result .= $this->openPhpTag() . PHP_EOL . 'endif;' . PHP_EOL;
|
176 |
+
}
|
177 |
+
elseif ($statement instanceof XmlImportAstForeach)
|
178 |
+
{
|
179 |
+
$var = '$x' . $this->xmlVariableNumber++;
|
180 |
+
$result .= PHP_EOL . 'foreach (' . $this->generateForExpression($statement->getXPath()) .
|
181 |
+
' as ' . $var . ') :' . PHP_EOL;
|
182 |
+
array_push($this->xmlStack, $var);
|
183 |
+
$result .= $this->generateForSequence($statement->getBody());
|
184 |
+
$result .= $this->openPhpTag() . PHP_EOL . 'endforeach;' . PHP_EOL;
|
185 |
+
array_pop($this->xmlStack);
|
186 |
+
}
|
187 |
+
elseif ($statement instanceof XmlImportAstIf)
|
188 |
+
{
|
189 |
+
$result .= PHP_EOL . 'if (' . $this->generateForExpression($statement->getCondition()) . ') :' . PHP_EOL;
|
190 |
+
$result .= $this->generateForSequence($statement->getIfBody());
|
191 |
+
foreach ($statement->getElseIfs() as $elseif)
|
192 |
+
{
|
193 |
+
$result .= $this->openPhpTag() . PHP_EOL . 'elseif (' . $this->generateForExpression($elseif->getCondition()) . ') :' . PHP_EOL;
|
194 |
+
$result .= $this->generateForSequence($elseif->getBody());
|
195 |
+
}
|
196 |
+
if (!is_null($body = $statement->getElseBody()))
|
197 |
+
{
|
198 |
+
$result .= $this->openPhpTag() . PHP_EOL . 'else :' . PHP_EOL;
|
199 |
+
$result .= $this->generateForSequence($body);
|
200 |
+
}
|
201 |
+
$result .= $this->openPhpTag() . PHP_EOL . 'endif;' . PHP_EOL;
|
202 |
+
}
|
203 |
+
|
204 |
+
}
|
205 |
+
$this->previousStatement = $statement;
|
206 |
+
return $result;
|
207 |
+
}
|
208 |
+
|
209 |
+
/**
|
210 |
+
* Generates code for expression
|
211 |
+
*
|
212 |
+
* @param XmlImportAstExpression $expression
|
213 |
+
* @param bool $inPrint whether in print or in clause or function argument
|
214 |
+
* @return string
|
215 |
+
*/
|
216 |
+
private function generateForExpression(XmlImportAstExpression $expression, $inPrint = false)
|
217 |
+
{
|
218 |
+
|
219 |
+
switch (get_class($expression))
|
220 |
+
{
|
221 |
+
case 'XmlImportAstString':
|
222 |
+
$result = '"' . $this->getEscapedValue($expression->getValue()) . '"';
|
223 |
+
break;
|
224 |
+
|
225 |
+
case 'XmlImportAstInteger':
|
226 |
+
case 'XmlImportAstFloat':
|
227 |
+
$result = $expression->getValue();
|
228 |
+
break;
|
229 |
+
|
230 |
+
case 'XmlImportAstXPath':
|
231 |
+
if ($inPrint)
|
232 |
+
{
|
233 |
+
$variables = $this->sequenceStack[count($this->sequenceStack) - 1]->getVariables();
|
234 |
+
$result = '$this->getValue(' . $variables[$expression->getValue()] . ')';
|
235 |
+
}
|
236 |
+
else
|
237 |
+
{
|
238 |
+
$variables = $this->sequenceStack[count($this->sequenceStack) - 1]->getVariables();
|
239 |
+
$result = $variables[$expression->getValue()];
|
240 |
+
}
|
241 |
+
break;
|
242 |
+
|
243 |
+
case 'XmlImportAstFunction':
|
244 |
+
$result = $this->generateForFunction($expression);
|
245 |
+
break;
|
246 |
+
case 'XmlImportAstMath':
|
247 |
+
$result = $this->generateForMath($expression);
|
248 |
+
break;
|
249 |
+
}
|
250 |
+
return $result;
|
251 |
+
}
|
252 |
+
|
253 |
+
/**
|
254 |
+
* Generates code for a function
|
255 |
+
*
|
256 |
+
* @param XmlImportAstFunction $function
|
257 |
+
* @return string
|
258 |
+
*/
|
259 |
+
private function generateForFunction(XmlImportAstFunction $function)
|
260 |
+
{
|
261 |
+
$result = $function->getName() . '(';
|
262 |
+
$arguments = $function->getArguments();
|
263 |
+
for($i = 0; $i < count($arguments); $i++)
|
264 |
+
{
|
265 |
+
$result .= $this->generateForExpression($arguments[$i]);
|
266 |
+
if ($i < (count($arguments) - 1))
|
267 |
+
$result .= ', ';
|
268 |
+
}
|
269 |
+
$result .= ')';
|
270 |
+
|
271 |
+
return $result;
|
272 |
+
}
|
273 |
+
|
274 |
+
/**
|
275 |
+
* Generates code for a function
|
276 |
+
*
|
277 |
+
* @param XmlImportAstFunction $function
|
278 |
+
* @return string
|
279 |
+
*/
|
280 |
+
private function generateForMath(XmlImportAstMath $math)
|
281 |
+
{
|
282 |
+
$result = '';
|
283 |
+
$arguments = $math->getArguments();
|
284 |
+
for($i = 0; $i < count($arguments); $i++)
|
285 |
+
{
|
286 |
+
$result .= $this->generateForExpression($arguments[$i], true);
|
287 |
+
}
|
288 |
+
|
289 |
+
return 'number_format('.str_replace("\"", "", $result).',2)';
|
290 |
+
}
|
291 |
+
|
292 |
+
/**
|
293 |
+
* Add PHP open tag if needed
|
294 |
+
*
|
295 |
+
* @return string
|
296 |
+
*/
|
297 |
+
private function openPhpTag()
|
298 |
+
{
|
299 |
+
$result = '';
|
300 |
+
if (!$this->isPhpTagOpen)
|
301 |
+
{
|
302 |
+
$this->isPhpTagOpen = true;
|
303 |
+
$result = '<?php ';
|
304 |
+
}
|
305 |
+
return $result;
|
306 |
+
}
|
307 |
+
|
308 |
+
/**
|
309 |
+
* Adds PHP close tag if needed
|
310 |
+
*
|
311 |
+
* @return string
|
312 |
+
*/
|
313 |
+
private function closePhpTag()
|
314 |
+
{
|
315 |
+
$result = '';
|
316 |
+
if ($this->isPhpTagOpen)
|
317 |
+
{
|
318 |
+
$this->isPhpTagOpen = false;
|
319 |
+
$result = '?>';
|
320 |
+
}
|
321 |
+
return $result;
|
322 |
+
}
|
323 |
+
|
324 |
+
/**
|
325 |
+
* Gets escaped value
|
326 |
+
*
|
327 |
+
* @return string
|
328 |
+
*/
|
329 |
+
private function getEscapedValue($value)
|
330 |
+
{
|
331 |
+
$escapedValue = strtr($value, array(
|
332 |
+
"\n" => "\\n",
|
333 |
+
"\t" => "\\t",
|
334 |
+
"\r" => "\\r",
|
335 |
+
"$" => "\\$",
|
336 |
+
"\"" => "\\\"",
|
337 |
+
"\\" => "\\\\",
|
338 |
+
));
|
339 |
+
return $escapedValue;
|
340 |
+
}
|
341 |
+
}
|
libraries/XmlImportTemplateParser.php
ADDED
@@ -0,0 +1,393 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
|
4 |
+
* @package General
|
5 |
+
*/
|
6 |
+
|
7 |
+
require_once dirname(__FILE__) . '/ast/XmlImportAstSequence.php';
|
8 |
+
require_once dirname(__FILE__) . '/ast/XmlImportAstPrint.php';
|
9 |
+
require_once dirname(__FILE__) . '/ast/XmlImportAstText.php';
|
10 |
+
require_once dirname(__FILE__) . '/ast/XmlImportAstWith.php';
|
11 |
+
require_once dirname(__FILE__) . '/ast/XmlImportAstForeach.php';
|
12 |
+
require_once dirname(__FILE__) . '/ast/XmlImportAstIf.php';
|
13 |
+
require_once dirname(__FILE__) . '/ast/XmlImportAstMath.php';
|
14 |
+
|
15 |
+
require_once dirname(__FILE__) . '/ast/XmlImportAstXPath.php';
|
16 |
+
require_once dirname(__FILE__) . '/ast/XmlImportAstString.php';
|
17 |
+
require_once dirname(__FILE__) . '/ast/XmlImportAstInteger.php';
|
18 |
+
require_once dirname(__FILE__) . '/ast/XmlImportAstFloat.php';
|
19 |
+
require_once dirname(__FILE__) . '/ast/XmlImportAstFunction.php';
|
20 |
+
|
21 |
+
/**
|
22 |
+
* Parses a list of nodes into AST (Abstract Syntax Tree)
|
23 |
+
*/
|
24 |
+
class XmlImportTemplateParser
|
25 |
+
{
|
26 |
+
/**
|
27 |
+
* List of tokens
|
28 |
+
*
|
29 |
+
* @var array
|
30 |
+
*/
|
31 |
+
private $tokens;
|
32 |
+
|
33 |
+
/**
|
34 |
+
* Current index
|
35 |
+
*
|
36 |
+
* @var int
|
37 |
+
*/
|
38 |
+
private $index = -1;
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Stack that stores possible block endings
|
42 |
+
*
|
43 |
+
* @var array
|
44 |
+
*/
|
45 |
+
private $clauseStack = array();
|
46 |
+
|
47 |
+
/**
|
48 |
+
* Stack of sequences
|
49 |
+
*
|
50 |
+
* @var array
|
51 |
+
*/
|
52 |
+
private $sequenceStack = array();
|
53 |
+
|
54 |
+
/**
|
55 |
+
* Whether else subclause is allowed
|
56 |
+
*
|
57 |
+
* @var bool
|
58 |
+
*/
|
59 |
+
private $elseAllowed = false;
|
60 |
+
|
61 |
+
/**
|
62 |
+
* Creates new instance
|
63 |
+
*
|
64 |
+
* @param array $tokens
|
65 |
+
*/
|
66 |
+
public function __construct(array $tokens)
|
67 |
+
{
|
68 |
+
$this->tokens = $tokens;
|
69 |
+
}
|
70 |
+
|
71 |
+
/**
|
72 |
+
* Parses the list of tokens into AST tree
|
73 |
+
*
|
74 |
+
* @return XmlImportAstSequence
|
75 |
+
*/
|
76 |
+
public function parse()
|
77 |
+
{
|
78 |
+
$result = $this->parseSequence();
|
79 |
+
|
80 |
+
if (count($this->clauseStack) > 0)
|
81 |
+
throw new XmlImportException("Unexpected end of template.");
|
82 |
+
|
83 |
+
return $result;
|
84 |
+
}
|
85 |
+
|
86 |
+
/**
|
87 |
+
* Parses sequence
|
88 |
+
*
|
89 |
+
* @return XmlImportAstSequence
|
90 |
+
*/
|
91 |
+
private function parseSequence()
|
92 |
+
{
|
93 |
+
if (($this->index + 1) == count($this->tokens))
|
94 |
+
throw new XmlImportException("Reached end of template but statement sequence expected");
|
95 |
+
$sequence = new XmlImportAstSequence();
|
96 |
+
array_push($this->sequenceStack, $sequence);
|
97 |
+
if (count($this->clauseStack) == 0)
|
98 |
+
{
|
99 |
+
while (($this->index + 1) < count($this->tokens))
|
100 |
+
{
|
101 |
+
$sequence->addStatement($this->parseStatement());
|
102 |
+
}
|
103 |
+
}
|
104 |
+
else
|
105 |
+
{
|
106 |
+
while (($this->index + 1) < count($this->tokens))
|
107 |
+
{
|
108 |
+
if ($this->tokens[$this->index + 1]->getKind() == $this->clauseStack[count($this->clauseStack) - 1])
|
109 |
+
{
|
110 |
+
$this->index++;
|
111 |
+
array_pop($this->clauseStack);
|
112 |
+
break;
|
113 |
+
}
|
114 |
+
$statement = $this->parseStatement();
|
115 |
+
if (is_null($statement))
|
116 |
+
return $sequence;
|
117 |
+
$sequence->addStatement($statement);
|
118 |
+
}
|
119 |
+
}
|
120 |
+
array_pop($this->sequenceStack);
|
121 |
+
|
122 |
+
return $sequence;
|
123 |
+
}
|
124 |
+
|
125 |
+
/**
|
126 |
+
* Parses statement
|
127 |
+
*
|
128 |
+
* @return XmlImportAstText
|
129 |
+
*/
|
130 |
+
private function parseStatement()
|
131 |
+
{
|
132 |
+
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_TEXT)
|
133 |
+
{
|
134 |
+
return new XmlImportAstText($this->tokens[++$this->index]->getValue());
|
135 |
+
}
|
136 |
+
elseif ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_PRINT)
|
137 |
+
{
|
138 |
+
$this->index++;
|
139 |
+
return new XmlImportAstPrint($this->parseExpression());
|
140 |
+
}
|
141 |
+
elseif ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_WITH)
|
142 |
+
{
|
143 |
+
return $this->parseWith();
|
144 |
+
}
|
145 |
+
elseif ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_FOREACH)
|
146 |
+
{
|
147 |
+
return $this->parseForeach();
|
148 |
+
}
|
149 |
+
elseif($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_MATH)
|
150 |
+
{
|
151 |
+
return new XmlImportAstPrint($this->parseExpression());
|
152 |
+
}
|
153 |
+
elseif ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_IF)
|
154 |
+
{
|
155 |
+
return $this->parseIf();
|
156 |
+
}
|
157 |
+
elseif($this->clauseStack[count($this->clauseStack) - 1] == XmlImportToken::KIND_ENDIF &&
|
158 |
+
in_array($this->tokens[$this->index + 1]->getKind(), array(XmlImportToken::KIND_ELSE, XmlImportToken::KIND_ELSEIF)))
|
159 |
+
{
|
160 |
+
if ($this->elseAllowed)
|
161 |
+
{
|
162 |
+
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_ELSE)
|
163 |
+
$this->elseAllowed = false;
|
164 |
+
}
|
165 |
+
else
|
166 |
+
{
|
167 |
+
throw new XmlImportException("ELSEIF or ELSE is not allowed again after ELSE");
|
168 |
+
}
|
169 |
+
return null;
|
170 |
+
}
|
171 |
+
else
|
172 |
+
throw new XmlImportException ("Unexpected token {$this->tokens[$this->index + 1]->getKind()}, statement was expected.");
|
173 |
+
}
|
174 |
+
|
175 |
+
/**
|
176 |
+
* Parses expression
|
177 |
+
*
|
178 |
+
* @return XmlImportAstXPath
|
179 |
+
*/
|
180 |
+
private function parseExpression()
|
181 |
+
{
|
182 |
+
if ($this->index + 1 == count($this->tokens))
|
183 |
+
throw new XmlImportException("Reached end of template but expression was expected");
|
184 |
+
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_FUNCTION)
|
185 |
+
{
|
186 |
+
return $this->parseFunction();
|
187 |
+
}
|
188 |
+
elseif($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_MATH)
|
189 |
+
{
|
190 |
+
return $this->parseMath();
|
191 |
+
}
|
192 |
+
elseif ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_XPATH)
|
193 |
+
{
|
194 |
+
$xpath = new XmlImportAstXPath($this->tokens[++$this->index]->getValue());
|
195 |
+
$this->sequenceStack[count($this->sequenceStack) - 1]->addVariable($xpath);
|
196 |
+
return $xpath;
|
197 |
+
}
|
198 |
+
elseif ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_STRING || $this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_OPERATION)
|
199 |
+
{
|
200 |
+
return new XmlImportAstString($this->tokens[++$this->index]->getValue());
|
201 |
+
}
|
202 |
+
elseif ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_INT)
|
203 |
+
{
|
204 |
+
return new XmlImportAstInteger($this->tokens[++$this->index]->getValue());
|
205 |
+
}
|
206 |
+
elseif ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_FLOAT)
|
207 |
+
{
|
208 |
+
return new XmlImportAstFloat($this->tokens[++$this->index]->getValue());
|
209 |
+
}
|
210 |
+
else
|
211 |
+
throw new XmlImportException("Unexpected token " . $this->tokens[$this->index + 1]->getKind());
|
212 |
+
}
|
213 |
+
|
214 |
+
/**
|
215 |
+
* Parses function
|
216 |
+
*
|
217 |
+
* @return XmlImportAstFunction
|
218 |
+
*/
|
219 |
+
private function parseFunction()
|
220 |
+
{
|
221 |
+
$function = new XmlImportAstFunction($this->tokens[++$this->index]->getValue());
|
222 |
+
if ($this->tokens[$this->index + 1]->getKind() != XmlImportToken::KIND_OPEN)
|
223 |
+
throw new XmlImportException ("Open brace expected instead of " . $this->tokens[$this->index + 1]->getKind());
|
224 |
+
$this->index++;
|
225 |
+
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_CLOSE)
|
226 |
+
{
|
227 |
+
$this->index++;
|
228 |
+
return $function;
|
229 |
+
}
|
230 |
+
else
|
231 |
+
{
|
232 |
+
while ($this->index < count($this->tokens) - 2)
|
233 |
+
{
|
234 |
+
$function->addArgument($this->parseExpression());
|
235 |
+
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_CLOSE)
|
236 |
+
{
|
237 |
+
$this->index++;
|
238 |
+
return $function;
|
239 |
+
break;
|
240 |
+
}
|
241 |
+
elseif ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_COMMA)
|
242 |
+
$this->index++;
|
243 |
+
else
|
244 |
+
throw new XmlImportException("Comma or closing brace expected instead of " . $this->tokens[$this->index + 1]->getKind());
|
245 |
+
}
|
246 |
+
throw new XmlImportException("Unexpected end of {$function->getName()} function argument list");
|
247 |
+
}
|
248 |
+
}
|
249 |
+
|
250 |
+
/**
|
251 |
+
* Parses function
|
252 |
+
*
|
253 |
+
* @return XmlImportAstFunction
|
254 |
+
*/
|
255 |
+
private function parseMath()
|
256 |
+
{
|
257 |
+
$math = new XmlImportAstMath($this->tokens[++$this->index]->getValue());
|
258 |
+
if ($this->tokens[$this->index + 1]->getKind() != XmlImportToken::KIND_OPEN)
|
259 |
+
throw new XmlImportException ("Open brace expected instead of " . $this->tokens[$this->index + 1]->getKind());
|
260 |
+
$this->index++;
|
261 |
+
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_CLOSE)
|
262 |
+
{
|
263 |
+
$this->index++;
|
264 |
+
return $math;
|
265 |
+
}
|
266 |
+
else
|
267 |
+
{
|
268 |
+
while ($this->index < count($this->tokens) - 2)
|
269 |
+
{
|
270 |
+
$math->addArgument($this->parseExpression());
|
271 |
+
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_CLOSE)
|
272 |
+
{
|
273 |
+
$this->index++;
|
274 |
+
return $math;
|
275 |
+
break;
|
276 |
+
}
|
277 |
+
elseif ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_COMMA)
|
278 |
+
$this->index++;
|
279 |
+
else
|
280 |
+
throw new XmlImportException("Comma or closing brace expected instead of " . $this->tokens[$this->index + 1]->getKind());
|
281 |
+
}
|
282 |
+
throw new XmlImportException("Unexpected end of MATH argument list");
|
283 |
+
}
|
284 |
+
}
|
285 |
+
|
286 |
+
/**
|
287 |
+
* Parses clause that uses XPath and returns XPath
|
288 |
+
*
|
289 |
+
* @return XmlImportAstXPath
|
290 |
+
*/
|
291 |
+
private function parseXPathDependant()
|
292 |
+
{
|
293 |
+
$this->index++;
|
294 |
+
|
295 |
+
if ($this->index + 1 == count($this->tokens))
|
296 |
+
throw new XmlImportException("Reached end of template but expression was expected");
|
297 |
+
|
298 |
+
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_OPEN)
|
299 |
+
$this->index++;
|
300 |
+
else
|
301 |
+
throw new XmlImportException("Open brace expected instead of " . $this->tokens[$this->index + 1]->getKind());
|
302 |
+
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_XPATH)
|
303 |
+
{
|
304 |
+
$xpath = new XmlImportAstXPath($this->tokens[++$this->index]->getValue());
|
305 |
+
$this->sequenceStack[count($this->sequenceStack) - 1]->addVariable($xpath);
|
306 |
+
}
|
307 |
+
else
|
308 |
+
throw new XmlImportException("XPath expression expected instead of " . $this->tokens[$this->index + 1]->getKind());
|
309 |
+
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_CLOSE)
|
310 |
+
$this->index++;
|
311 |
+
else
|
312 |
+
throw new XmlImportException("Close brace expected instead of " . $this->tokens[$this->index + 1]->getKind());
|
313 |
+
return $xpath;
|
314 |
+
}
|
315 |
+
|
316 |
+
/**
|
317 |
+
* Parses WITH clause
|
318 |
+
*
|
319 |
+
* @return XmlImportAstWith
|
320 |
+
*/
|
321 |
+
private function parseWith()
|
322 |
+
{
|
323 |
+
$xpath = $this->parseXPathDependant();
|
324 |
+
//store sequence exit
|
325 |
+
array_push($this->clauseStack, XmlImportToken::KIND_ENDWITH);
|
326 |
+
return new XmlImportAstWith($xpath, $this->parseSequence());
|
327 |
+
}
|
328 |
+
|
329 |
+
/**
|
330 |
+
* Parses FOREACH clause
|
331 |
+
*
|
332 |
+
* @return XmlImportAstForeach
|
333 |
+
*/
|
334 |
+
private function parseForeach()
|
335 |
+
{
|
336 |
+
$xpath = $this->parseXPathDependant();
|
337 |
+
|
338 |
+
array_push($this->clauseStack, XmlImportToken::KIND_ENDFOREACH);
|
339 |
+
return new XmlImportAstForeach($xpath, $this->parseSequence());
|
340 |
+
}
|
341 |
+
|
342 |
+
/**
|
343 |
+
* Parses IF clause
|
344 |
+
*
|
345 |
+
* @return XmlImportAstIf
|
346 |
+
*/
|
347 |
+
private function parseIf()
|
348 |
+
{
|
349 |
+
$this->index++;
|
350 |
+
$this->elseAllowed = true;
|
351 |
+
array_push($this->clauseStack, XmlImportToken::KIND_ENDIF);
|
352 |
+
|
353 |
+
if ($this->index + 1 == count($this->tokens))
|
354 |
+
throw new XmlImportException("Reached end of template but expression was expected");
|
355 |
+
|
356 |
+
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_OPEN)
|
357 |
+
$this->index++;
|
358 |
+
else
|
359 |
+
throw new XmlImportException("Open brace expected instead of " . $this->tokens[$this->index + 1]->getKind());
|
360 |
+
$if = new XmlImportAstIf($this->parseExpression());
|
361 |
+
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_CLOSE)
|
362 |
+
$this->index++;
|
363 |
+
else
|
364 |
+
throw new XmlImportException("Close brace expected instead of " . $this->tokens[$this->index + 1]->getKind());
|
365 |
+
$if->addIfBody($this->parseSequence());
|
366 |
+
if ($this->index + 1 != count($this->tokens))
|
367 |
+
{
|
368 |
+
while ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_ELSEIF)
|
369 |
+
{
|
370 |
+
$this->index++;
|
371 |
+
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_OPEN)
|
372 |
+
$this->index++;
|
373 |
+
else
|
374 |
+
throw new XmlImportException("Open brace expected instead of " . $this->tokens[$this->index + 1]->getKind());
|
375 |
+
$condition = $this->parseExpression();
|
376 |
+
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_CLOSE)
|
377 |
+
$this->index++;
|
378 |
+
else
|
379 |
+
throw new XmlImportException("Close brace expected instead of " . $this->tokens[$this->index + 1]->getKind());
|
380 |
+
$elseif = new XmlImportAstElseif($condition, $this->parseSequence());
|
381 |
+
$if->addElseif($elseif);
|
382 |
+
if ($this->index + 1 == count($this->tokens))
|
383 |
+
break;
|
384 |
+
}
|
385 |
+
if ($this->index + 1 < count($this->tokens) && $this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_ELSE)
|
386 |
+
{
|
387 |
+
$this->index++;
|
388 |
+
$if->addElseBody($this->parseSequence());
|
389 |
+
}
|
390 |
+
}
|
391 |
+
return $if;
|
392 |
+
}
|
393 |
+
}
|
libraries/XmlImportTemplateScanner.php
ADDED
@@ -0,0 +1,403 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
|
4 |
+
* @package General
|
5 |
+
*/
|
6 |
+
|
7 |
+
require_once dirname(__FILE__) . '/XmlImportToken.php';
|
8 |
+
require_once dirname(__FILE__) . '/XmlImportException.php';
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Used to scan string into a list of tokens
|
12 |
+
*/
|
13 |
+
final class XmlImportTemplateScanner
|
14 |
+
{
|
15 |
+
/**
|
16 |
+
* Language keywords
|
17 |
+
*
|
18 |
+
* @var array
|
19 |
+
*/
|
20 |
+
private $keywords = array(
|
21 |
+
'IF',
|
22 |
+
'ELSEIF',
|
23 |
+
'ELSE',
|
24 |
+
'ENDIF',
|
25 |
+
'FOREACH',
|
26 |
+
'ENDFOREACH',
|
27 |
+
'WITH',
|
28 |
+
'ENDWITH',
|
29 |
+
'MATH'
|
30 |
+
);
|
31 |
+
|
32 |
+
/**
|
33 |
+
* Parsing text
|
34 |
+
*/
|
35 |
+
const STATE_TEXT = 'STATE_TEXT';
|
36 |
+
|
37 |
+
/**
|
38 |
+
* Parsing XPath
|
39 |
+
*/
|
40 |
+
const STATE_XPATH = 'STATE_XPATH';
|
41 |
+
|
42 |
+
/**
|
43 |
+
* Parsing Language
|
44 |
+
*/
|
45 |
+
const STATE_LANG = 'STATE_LANG';
|
46 |
+
|
47 |
+
/**
|
48 |
+
* Whether it is lang block start
|
49 |
+
*
|
50 |
+
* @var bool
|
51 |
+
*/
|
52 |
+
private $isLangBegin = false;
|
53 |
+
|
54 |
+
/**
|
55 |
+
* Current parsing state
|
56 |
+
*
|
57 |
+
* @var string
|
58 |
+
*/
|
59 |
+
private $currentState = XmlImportTemplateScanner::STATE_TEXT;
|
60 |
+
|
61 |
+
/**
|
62 |
+
* Scans template from XmlImportReaderInterface and returns the list of tokens
|
63 |
+
*
|
64 |
+
* @param XmlImportReaderInterface $input
|
65 |
+
* @return array
|
66 |
+
*/
|
67 |
+
public function scan(XmlImportReaderInterface $input)
|
68 |
+
{
|
69 |
+
$results = array();
|
70 |
+
while (($ch = $input->peek()) !== false)
|
71 |
+
{
|
72 |
+
switch ($this->currentState)
|
73 |
+
{
|
74 |
+
case XmlImportTemplateScanner::STATE_TEXT:
|
75 |
+
if ($ch == '[')
|
76 |
+
{
|
77 |
+
$this->currentState = XmlImportTemplateScanner::STATE_LANG;
|
78 |
+
$this->isLangBegin = true;
|
79 |
+
//omit [
|
80 |
+
$input->read();
|
81 |
+
}
|
82 |
+
elseif ($ch == '{')
|
83 |
+
{
|
84 |
+
$this->currentState = XmlImportTemplateScanner::STATE_XPATH;
|
85 |
+
//omit {
|
86 |
+
$input->read();
|
87 |
+
}
|
88 |
+
else
|
89 |
+
{
|
90 |
+
$results[] = $this->scanText($input);
|
91 |
+
}
|
92 |
+
break;
|
93 |
+
case XmlImportTemplateScanner::STATE_XPATH:
|
94 |
+
$results = array_merge($results, $this->scanXPath($input, false));
|
95 |
+
break;
|
96 |
+
case XmlImportTemplateScanner::STATE_LANG:
|
97 |
+
$ch = $input->peek();
|
98 |
+
|
99 |
+
if (preg_match('/\s/', $ch))
|
100 |
+
{
|
101 |
+
//omit space
|
102 |
+
$input->read();
|
103 |
+
}
|
104 |
+
elseif (preg_match('/[_a-z]/i', $ch))
|
105 |
+
{
|
106 |
+
$result = $this->scanName($input);
|
107 |
+
if (is_array($result))
|
108 |
+
$results = array_merge($results, $result);
|
109 |
+
else
|
110 |
+
$results[] = $result;
|
111 |
+
}
|
112 |
+
elseif (preg_match('/(\d|-)/', $ch))
|
113 |
+
{
|
114 |
+
$result = $this->scanNumber($input);
|
115 |
+
if (is_array($result))
|
116 |
+
$results = array_merge($results, $result);
|
117 |
+
else
|
118 |
+
$results[] = $result;
|
119 |
+
}
|
120 |
+
elseif ($ch == "+" || $ch == "-" || $ch == "/" || $ch == "*")
|
121 |
+
{
|
122 |
+
$this->isLangBegin = false;
|
123 |
+
$input->read();
|
124 |
+
$results[] = new XmlImportToken(XmlImportToken::KIND_OPERATION, $ch);
|
125 |
+
}
|
126 |
+
elseif ($ch == '"')
|
127 |
+
{
|
128 |
+
//omit "
|
129 |
+
$input->read();
|
130 |
+
$result = $this->scanString($input);
|
131 |
+
if (is_array($result))
|
132 |
+
$results = array_merge($results, $result);
|
133 |
+
else
|
134 |
+
$results[] = $result;
|
135 |
+
}
|
136 |
+
elseif ($ch == '{')
|
137 |
+
{
|
138 |
+
$input->read();
|
139 |
+
$result = $this->scanXPath($input);
|
140 |
+
if (is_array($result))
|
141 |
+
$results = array_merge($results, $result);
|
142 |
+
else
|
143 |
+
$results[] = $result;
|
144 |
+
}
|
145 |
+
elseif ($ch == '(')
|
146 |
+
{
|
147 |
+
$this->isLangBegin = false;
|
148 |
+
$input->read();
|
149 |
+
$results[] = new XmlImportToken(XmlImportToken::KIND_OPEN);
|
150 |
+
}
|
151 |
+
elseif ($ch == ')')
|
152 |
+
{
|
153 |
+
$this->isLangBegin = false;
|
154 |
+
$input->read();
|
155 |
+
$results[] = new XmlImportToken(XmlImportToken::KIND_CLOSE);
|
156 |
+
}
|
157 |
+
elseif ($ch == ',')
|
158 |
+
{
|
159 |
+
$this->isLangBegin = false;
|
160 |
+
$input->read();
|
161 |
+
$results[] = new XmlImportToken(XmlImportToken::KIND_COMMA);
|
162 |
+
}
|
163 |
+
elseif ($ch == ']')
|
164 |
+
{
|
165 |
+
$this->isLangBegin = false;
|
166 |
+
$this->currentState = XmlImportTemplateScanner::STATE_TEXT;
|
167 |
+
//omit ]
|
168 |
+
$input->read();
|
169 |
+
}
|
170 |
+
else
|
171 |
+
throw new XmlImportException("Unexpected symbol '$ch'");
|
172 |
+
|
173 |
+
break;
|
174 |
+
}
|
175 |
+
}
|
176 |
+
|
177 |
+
return $results;
|
178 |
+
}
|
179 |
+
|
180 |
+
/**
|
181 |
+
* Scans text
|
182 |
+
*
|
183 |
+
* @param XmlImportReaderInterface $input
|
184 |
+
* @return XmlImportToken
|
185 |
+
*/
|
186 |
+
private function scanText($input)
|
187 |
+
{
|
188 |
+
$accum = $input->read();
|
189 |
+
while (($ch = $input->peek()) !== false)
|
190 |
+
{
|
191 |
+
if ($ch == '{' && $accum[strlen($accum) - 1] != "\\")
|
192 |
+
{
|
193 |
+
$this->currentState = XmlImportTemplateScanner::STATE_XPATH;
|
194 |
+
//omit {
|
195 |
+
$input->read();
|
196 |
+
break;
|
197 |
+
}
|
198 |
+
elseif ($ch == '[' && $accum[strlen($accum) - 1] != "\\")
|
199 |
+
{
|
200 |
+
$this->currentState = XmlImportTemplateScanner::STATE_LANG;
|
201 |
+
$this->isLangBegin = true;
|
202 |
+
//omit [
|
203 |
+
$input->read();
|
204 |
+
break;
|
205 |
+
}
|
206 |
+
else
|
207 |
+
$accum .= $input->read();
|
208 |
+
}
|
209 |
+
$accum = str_replace(array("\\[", "\\{"), array('[', '{'), $accum);
|
210 |
+
return new XmlImportToken(XmlImportToken::KIND_TEXT, $accum);
|
211 |
+
}
|
212 |
+
|
213 |
+
/**
|
214 |
+
* Scans XPath
|
215 |
+
*
|
216 |
+
* @param XmlImportReaderInterface $input
|
217 |
+
* @param bool $insideLang
|
218 |
+
* @return XmlImportToken
|
219 |
+
*/
|
220 |
+
private function scanXPath($input, $insideLang = true)
|
221 |
+
{
|
222 |
+
$accum = '';
|
223 |
+
while(($ch = $input->peek()) !== false)
|
224 |
+
{
|
225 |
+
if ($ch == '}' && (strlen($accum) == 0 || $accum[strlen($accum) - 1] != "\\"))
|
226 |
+
{
|
227 |
+
//skip }
|
228 |
+
$input->read();
|
229 |
+
$accum = str_replace("\\}", '}', $accum);
|
230 |
+
if ($insideLang)
|
231 |
+
{
|
232 |
+
if ($this->isLangBegin)
|
233 |
+
{
|
234 |
+
return array(new XmlImportToken(XmlImportToken::KIND_PRINT), new XmlImportToken(XmlImportToken::KIND_XPATH, $accum));
|
235 |
+
}
|
236 |
+
else
|
237 |
+
return new XmlImportToken(XmlImportToken::KIND_XPATH, $accum);
|
238 |
+
}
|
239 |
+
else
|
240 |
+
{
|
241 |
+
$this->currentState = XmlImportTemplateScanner::STATE_TEXT;
|
242 |
+
|
243 |
+
return array(new XmlImportToken(XmlImportToken::KIND_PRINT), new XmlImportToken(XmlImportToken::KIND_XPATH, $accum));
|
244 |
+
}
|
245 |
+
}
|
246 |
+
else
|
247 |
+
$accum .= $input->read();
|
248 |
+
}
|
249 |
+
throw new XmlImportException('Unexpected end of XPath expression \'' . $accum . '\'');
|
250 |
+
}
|
251 |
+
|
252 |
+
/**
|
253 |
+
* Scans name
|
254 |
+
*
|
255 |
+
* @param XmlImportReaderInterface $input
|
256 |
+
* @return XmlImportToken
|
257 |
+
*/
|
258 |
+
private function scanName(XmlImportReaderInterface $input)
|
259 |
+
{
|
260 |
+
$accum = $input->read();
|
261 |
+
while (preg_match('/[_a-z0-9]/i', $input->peek()))
|
262 |
+
{
|
263 |
+
$accum .= $input->read();
|
264 |
+
if ($input->peek() === false)
|
265 |
+
throw new XmlImportException("Unexpected end of function or keyword name \"$accum\"");
|
266 |
+
}
|
267 |
+
if (in_array(strtoupper($accum), $this->keywords))
|
268 |
+
{
|
269 |
+
return new XmlImportToken(strtoupper($accum));
|
270 |
+
}
|
271 |
+
else
|
272 |
+
{
|
273 |
+
if ($this->isLangBegin)
|
274 |
+
{
|
275 |
+
$this->isLangBegin = false;
|
276 |
+
return array(new XmlImportToken(XmlImportToken::KIND_PRINT), new XmlImportToken(XmlImportToken::KIND_FUNCTION, $accum));
|
277 |
+
}
|
278 |
+
else
|
279 |
+
return new XmlImportToken(XmlImportToken::KIND_FUNCTION, $accum);
|
280 |
+
}
|
281 |
+
}
|
282 |
+
|
283 |
+
/**
|
284 |
+
* Scans string literal
|
285 |
+
*
|
286 |
+
* @param XmlImportReaderInterface $input
|
287 |
+
* @return XmlImportToken
|
288 |
+
*/
|
289 |
+
private function scanString(XmlImportReaderInterface $input)
|
290 |
+
{
|
291 |
+
$accum = '';
|
292 |
+
while(($ch = $input->peek()) !== false)
|
293 |
+
{
|
294 |
+
if ($ch == '"' && (strlen($accum) == 0 || $accum[strlen($accum) - 1] != "\\"))
|
295 |
+
{
|
296 |
+
//skip "
|
297 |
+
$input->read();
|
298 |
+
$accum = str_replace("\\\"", '"', $accum);
|
299 |
+
if ($this->isLangBegin)
|
300 |
+
{
|
301 |
+
$this->isLangBegin = false;
|
302 |
+
return array(new XmlImportToken(XmlImportToken::KIND_PRINT), new XmlImportToken(XmlImportToken::KIND_STRING, $accum));
|
303 |
+
}
|
304 |
+
else
|
305 |
+
{
|
306 |
+
return new XmlImportToken(XmlImportToken::KIND_STRING, $accum);
|
307 |
+
}
|
308 |
+
}
|
309 |
+
else
|
310 |
+
$accum .= $input->read();
|
311 |
+
}
|
312 |
+
throw new XmlImportException('Unexpected end of string literal "' . $accum . '"');
|
313 |
+
}
|
314 |
+
|
315 |
+
/**
|
316 |
+
* Scans number
|
317 |
+
*
|
318 |
+
* @param XmlImportReaderInterface $input
|
319 |
+
* @return XmlImportToken
|
320 |
+
*/
|
321 |
+
private function scanNumber(XmlImportReaderInterface $input)
|
322 |
+
{
|
323 |
+
$isInt = true;
|
324 |
+
$accum = $this->scanInt($input);
|
325 |
+
if ($input->peek() == '.')
|
326 |
+
{
|
327 |
+
$isInt = false;
|
328 |
+
$accum .= $input->read();
|
329 |
+
$accum .= $this->scanNumberFrac($input);
|
330 |
+
}
|
331 |
+
if (strtolower($input->peek()) == 'e' )
|
332 |
+
{
|
333 |
+
$isInt = false;
|
334 |
+
$accum .= $input->read();
|
335 |
+
$accum .= $this->scanInt($input);
|
336 |
+
}
|
337 |
+
if ($isInt)
|
338 |
+
{
|
339 |
+
if ($this->isLangBegin)
|
340 |
+
{
|
341 |
+
$this->isLangBegin = false;
|
342 |
+
return array(new XmlImportToken(XmlImportToken::KIND_PRINT), new XmlImportToken(XmlImportToken::KIND_INT, intval($accum)));
|
343 |
+
}
|
344 |
+
else
|
345 |
+
{
|
346 |
+
return new XmlImportToken(XmlImportToken::KIND_INT, intval($accum));
|
347 |
+
}
|
348 |
+
}
|
349 |
+
else
|
350 |
+
{
|
351 |
+
if ($this->isLangBegin)
|
352 |
+
{
|
353 |
+
$this->isLangBegin = false;
|
354 |
+
return array(new XmlImportToken(XmlImportToken::KIND_PRINT), new XmlImportToken(XmlImportToken::KIND_FLOAT, floatval($accum)));
|
355 |
+
}
|
356 |
+
else
|
357 |
+
{
|
358 |
+
return new XmlImportToken(XmlImportToken::KIND_FLOAT, floatval($accum));
|
359 |
+
}
|
360 |
+
}
|
361 |
+
}
|
362 |
+
|
363 |
+
/**
|
364 |
+
* Scans integer number
|
365 |
+
*
|
366 |
+
* @param XmlImportReaderInterface $input
|
367 |
+
* @return string
|
368 |
+
*/
|
369 |
+
private function scanInt(XmlImportReaderInterface $input)
|
370 |
+
{
|
371 |
+
if (preg_match('/(\d|-)/', $input->peek()))
|
372 |
+
{
|
373 |
+
$accum = $input->read();
|
374 |
+
if ($accum == '-' && !preg_match('/\d/', $input->peek()))
|
375 |
+
throw new XmlImportException("Expected digit after a minus");
|
376 |
+
while (preg_match('/\d/', $input->peek()))
|
377 |
+
{
|
378 |
+
$accum .= $input->read();
|
379 |
+
}
|
380 |
+
return $accum;
|
381 |
+
}
|
382 |
+
else
|
383 |
+
throw new XmlImportException("digit or '-' expected in a number");
|
384 |
+
}
|
385 |
+
|
386 |
+
/**
|
387 |
+
* Scans fraction part of a number
|
388 |
+
*
|
389 |
+
* @param XmlImportReaderInterface $input
|
390 |
+
* @return string
|
391 |
+
*/
|
392 |
+
private function scanNumberFrac(XmlImportReaderInterface $input)
|
393 |
+
{
|
394 |
+
$accum = '';
|
395 |
+
while (preg_match('/\d/', $input->peek()))
|
396 |
+
{
|
397 |
+
$accum .= $input->read();
|
398 |
+
}
|
399 |
+
if (strlen($accum) == 0)
|
400 |
+
throw new XmlImportException("Digits are expected after a '.'");
|
401 |
+
return $accum;
|
402 |
+
}
|
403 |
+
}
|
libraries/XmlImportToken.php
ADDED
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
|
4 |
+
* @package General
|
5 |
+
*/
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Represents a template token
|
9 |
+
*/
|
10 |
+
class XmlImportToken
|
11 |
+
{
|
12 |
+
/**
|
13 |
+
* Token Kind
|
14 |
+
*
|
15 |
+
* @var string
|
16 |
+
*/
|
17 |
+
private $kind;
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Token value
|
21 |
+
*
|
22 |
+
* @var mixed
|
23 |
+
*/
|
24 |
+
private $value;
|
25 |
+
|
26 |
+
/**
|
27 |
+
* Token is a text
|
28 |
+
*/
|
29 |
+
const KIND_TEXT = 'TEXT';
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Token is a print statement
|
33 |
+
*/
|
34 |
+
const KIND_PRINT = 'PRINT';
|
35 |
+
|
36 |
+
/**
|
37 |
+
* Token is a XPath literal
|
38 |
+
*/
|
39 |
+
const KIND_XPATH = "XPATH";
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Token is an IF keyword
|
43 |
+
*/
|
44 |
+
const KIND_IF = "IF";
|
45 |
+
|
46 |
+
/**
|
47 |
+
* Token is an ENDIF keyword
|
48 |
+
*/
|
49 |
+
const KIND_ENDIF = "ENDIF";
|
50 |
+
|
51 |
+
/**
|
52 |
+
* Token is an ELSEIF keyword
|
53 |
+
*/
|
54 |
+
const KIND_ELSEIF = "ELSEIF";
|
55 |
+
|
56 |
+
/**
|
57 |
+
* Token is an ELSE keyword
|
58 |
+
*/
|
59 |
+
const KIND_ELSE = "ELSE";
|
60 |
+
|
61 |
+
/**
|
62 |
+
* Token is a WITH keyword
|
63 |
+
*/
|
64 |
+
const KIND_WITH = "WITH";
|
65 |
+
|
66 |
+
/**
|
67 |
+
* Token is an ENDWITH keyword
|
68 |
+
*/
|
69 |
+
const KIND_ENDWITH = "ENDWITH";
|
70 |
+
|
71 |
+
/**
|
72 |
+
* Token is a FOREACH keyword
|
73 |
+
*/
|
74 |
+
const KIND_FOREACH = "FOREACH";
|
75 |
+
|
76 |
+
/**
|
77 |
+
* Token is an ENDFOREACH keyword
|
78 |
+
*/
|
79 |
+
const KIND_ENDFOREACH = "ENDFOREACH";
|
80 |
+
|
81 |
+
/**
|
82 |
+
* Token is a function name
|
83 |
+
*/
|
84 |
+
const KIND_FUNCTION = "FUNCTION";
|
85 |
+
|
86 |
+
/**
|
87 |
+
* Token is a comma
|
88 |
+
*/
|
89 |
+
const KIND_COMMA = "COMMA";
|
90 |
+
|
91 |
+
/**
|
92 |
+
* Token is an open brace
|
93 |
+
*/
|
94 |
+
const KIND_OPEN = "OPEN";
|
95 |
+
|
96 |
+
/**
|
97 |
+
* Token is a close brace
|
98 |
+
*/
|
99 |
+
const KIND_CLOSE = "CLOSE";
|
100 |
+
|
101 |
+
/**
|
102 |
+
* Token is a string literal
|
103 |
+
*/
|
104 |
+
const KIND_STRING = "STRING";
|
105 |
+
|
106 |
+
/**
|
107 |
+
* Token is an integer number
|
108 |
+
*/
|
109 |
+
const KIND_INT = "INT";
|
110 |
+
|
111 |
+
/**
|
112 |
+
* Token is a float number
|
113 |
+
*/
|
114 |
+
const KIND_FLOAT = "FLOAT";
|
115 |
+
|
116 |
+
/**
|
117 |
+
* Token is a math on the price element
|
118 |
+
*/
|
119 |
+
const KIND_MATH = "MATH";
|
120 |
+
|
121 |
+
/**
|
122 |
+
* Token is a math on the price element
|
123 |
+
*/
|
124 |
+
const KIND_OPERATION = "OPERATION";
|
125 |
+
|
126 |
+
/**
|
127 |
+
* Creates new instance of a token
|
128 |
+
*
|
129 |
+
* @param string $kind kind of a token
|
130 |
+
* @param mixed $value value of a token
|
131 |
+
*/
|
132 |
+
public function __construct($kind, $value = null)
|
133 |
+
{
|
134 |
+
$this->kind = $kind;
|
135 |
+
$this->value = $value;
|
136 |
+
}
|
137 |
+
|
138 |
+
/**
|
139 |
+
* Gets a kind of a token
|
140 |
+
*
|
141 |
+
* @return string
|
142 |
+
*/
|
143 |
+
public function getKind()
|
144 |
+
{
|
145 |
+
return $this->kind;
|
146 |
+
}
|
147 |
+
|
148 |
+
/**
|
149 |
+
* Gets a value of a token
|
150 |
+
*
|
151 |
+
* @return mixed
|
152 |
+
*/
|
153 |
+
public function getValue()
|
154 |
+
{
|
155 |
+
return $this->value;
|
156 |
+
}
|
157 |
+
|
158 |
+
/**
|
159 |
+
* String representation of a token
|
160 |
+
*
|
161 |
+
* @return string
|
162 |
+
*/
|
163 |
+
public function __toString()
|
164 |
+
{
|
165 |
+
return '--> ' . $this->getKind() . (is_null($this->value) ? '' : ': "' . $this->getValue() . '"');
|
166 |
+
}
|
167 |
+
}
|
libraries/ast/XmlImportAstElseif.php
ADDED
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
|
4 |
+
* @package AST
|
5 |
+
*/
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Represents ELSEIF branch of IF clause
|
9 |
+
*/
|
10 |
+
class XmlImportAstElseif
|
11 |
+
{
|
12 |
+
/**
|
13 |
+
* ELSEIF branch condition
|
14 |
+
*
|
15 |
+
* @var XmlImportAstExpression
|
16 |
+
*/
|
17 |
+
private $condition;
|
18 |
+
|
19 |
+
/**
|
20 |
+
* ELSEIF branch body
|
21 |
+
*
|
22 |
+
* @var XmlImportAstSequence
|
23 |
+
*/
|
24 |
+
private $body;
|
25 |
+
|
26 |
+
/**
|
27 |
+
* Creates new instance
|
28 |
+
*
|
29 |
+
* @param XmlImportAstExpression $condition
|
30 |
+
* @param XmlImportAstSequence $body
|
31 |
+
*/
|
32 |
+
public function __construct(XmlImportAstExpression $condition, XmlImportAstSequence $body)
|
33 |
+
{
|
34 |
+
$this->condition = $condition;
|
35 |
+
$this->body = $body;
|
36 |
+
}
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Gets ELSEIF branch condition
|
40 |
+
*
|
41 |
+
* @return XmlImportAstExpression
|
42 |
+
*/
|
43 |
+
public function getCondition()
|
44 |
+
{
|
45 |
+
return $this->condition;
|
46 |
+
}
|
47 |
+
|
48 |
+
/**
|
49 |
+
* Gets body
|
50 |
+
*
|
51 |
+
* @return XmlImportAstSequence
|
52 |
+
*/
|
53 |
+
public function getBody()
|
54 |
+
{
|
55 |
+
return $this->body;
|
56 |
+
}
|
57 |
+
|
58 |
+
/**
|
59 |
+
* String representation of an object
|
60 |
+
*
|
61 |
+
* @return string
|
62 |
+
*/
|
63 |
+
public function __toString()
|
64 |
+
{
|
65 |
+
$result = " Elseif:\n";
|
66 |
+
$result .= ' Condition: ' . $this->condition . "\n";
|
67 |
+
$result .= " Body:\n";
|
68 |
+
$array = explode("\n", $this->body);
|
69 |
+
for ($i = 0; $i < count($array); $i++)
|
70 |
+
{
|
71 |
+
$array[$i] = ' ' . $array[$i];
|
72 |
+
}
|
73 |
+
$result .= implode("\n", $array) . "\n";
|
74 |
+
return $result;
|
75 |
+
}
|
76 |
+
}
|
libraries/ast/XmlImportAstExpression.php
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
|
4 |
+
* @package AST
|
5 |
+
*/
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Represents expression
|
9 |
+
*
|
10 |
+
* @abstract
|
11 |
+
*/
|
12 |
+
abstract class XmlImportAstExpression
|
13 |
+
{
|
14 |
+
|
15 |
+
}
|
libraries/ast/XmlImportAstFloat.php
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
|
4 |
+
* @package AST
|
5 |
+
*/
|
6 |
+
|
7 |
+
require_once dirname(__FILE__) . '/XmlImportAstLiteral.php';
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Represents float number
|
11 |
+
*/
|
12 |
+
class XmlImportAstFloat extends XmlImportAstLiteral
|
13 |
+
{
|
14 |
+
|
15 |
+
}
|
libraries/ast/XmlImportAstForeach.php
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
|
4 |
+
* @package AST
|
5 |
+
*/
|
6 |
+
|
7 |
+
require_once dirname(__FILE__) . '/XmlImportAstXpathClause.php';
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Represents a FOREACH clause
|
11 |
+
*/
|
12 |
+
class XmlImportAstForeach extends XmlImportAstXPathClause
|
13 |
+
{
|
14 |
+
}
|
libraries/ast/XmlImportAstFunction.php
ADDED
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
|
4 |
+
* @package AST
|
5 |
+
*/
|
6 |
+
|
7 |
+
require_once dirname(__FILE__) . '/XmlImportAstExpression.php';
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Represents a function
|
11 |
+
*/
|
12 |
+
class XmlImportAstFunction extends XmlImportAstExpression
|
13 |
+
{
|
14 |
+
/**
|
15 |
+
* Name of a function
|
16 |
+
*
|
17 |
+
* @var string
|
18 |
+
*/
|
19 |
+
private $name;
|
20 |
+
|
21 |
+
/**
|
22 |
+
* Function arguments
|
23 |
+
*
|
24 |
+
* @var array
|
25 |
+
*/
|
26 |
+
private $arguments = array();
|
27 |
+
|
28 |
+
/**
|
29 |
+
* Creates new instance
|
30 |
+
*
|
31 |
+
* @param string $name
|
32 |
+
*/
|
33 |
+
public function __construct($name)
|
34 |
+
{
|
35 |
+
$this->name = $name;
|
36 |
+
}
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Gets name of a function
|
40 |
+
*
|
41 |
+
* @return string
|
42 |
+
*/
|
43 |
+
public function getName()
|
44 |
+
{
|
45 |
+
return $this->name;
|
46 |
+
}
|
47 |
+
|
48 |
+
/**
|
49 |
+
* Adds argument to a function
|
50 |
+
*
|
51 |
+
* @param XmlImportAstExpression $argument
|
52 |
+
*/
|
53 |
+
public function addArgument(XmlImportAstExpression $argument)
|
54 |
+
{
|
55 |
+
$this->arguments[] = $argument;
|
56 |
+
}
|
57 |
+
|
58 |
+
/**
|
59 |
+
* Gets function arguments
|
60 |
+
*
|
61 |
+
* @return array
|
62 |
+
*/
|
63 |
+
public function getArguments()
|
64 |
+
{
|
65 |
+
return $this->arguments;
|
66 |
+
}
|
67 |
+
|
68 |
+
/**
|
69 |
+
* String representation of a function
|
70 |
+
*
|
71 |
+
* @return string
|
72 |
+
*/
|
73 |
+
public function __toString()
|
74 |
+
{
|
75 |
+
$result = "--> begin " . get_class($this) . "\n";
|
76 |
+
foreach ($this->getArguments() as $argument)
|
77 |
+
{
|
78 |
+
$array = explode("\n", $argument);
|
79 |
+
for ($i = 0; $i < count($array); $i++)
|
80 |
+
{
|
81 |
+
$array[$i] = ' ' . $array[$i];
|
82 |
+
}
|
83 |
+
$result .= implode("\n", $array) . "\n";
|
84 |
+
}
|
85 |
+
|
86 |
+
$result .= "--> end " . get_class($this);
|
87 |
+
|
88 |
+
return $result;
|
89 |
+
}
|
90 |
+
}
|
libraries/ast/XmlImportAstIf.php
ADDED
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
|
4 |
+
* @package AST
|
5 |
+
*/
|
6 |
+
|
7 |
+
require_once dirname(__FILE__) . '/XmlImportAstStatement.php';
|
8 |
+
require_once dirname(__FILE__) . '/XmlImportAstElseif.php';
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Represents an IF clause
|
12 |
+
*/
|
13 |
+
class XmlImportAstIf extends XmlImportAstStatement
|
14 |
+
{
|
15 |
+
/**
|
16 |
+
* Condition
|
17 |
+
*
|
18 |
+
* @var XmlImportAstExpression
|
19 |
+
*/
|
20 |
+
private $condition;
|
21 |
+
|
22 |
+
/**
|
23 |
+
* If body
|
24 |
+
*
|
25 |
+
* @var XmlImportAstSequence
|
26 |
+
*/
|
27 |
+
private $ifBody;
|
28 |
+
|
29 |
+
/**
|
30 |
+
* List of elseif subclauses
|
31 |
+
*
|
32 |
+
* @var array
|
33 |
+
*/
|
34 |
+
private $elseIfs = array();
|
35 |
+
|
36 |
+
/**
|
37 |
+
* Else body
|
38 |
+
*
|
39 |
+
* @var XmlImportAstSequence
|
40 |
+
*/
|
41 |
+
private $elseBody;
|
42 |
+
|
43 |
+
/**
|
44 |
+
* Creates new instance
|
45 |
+
*
|
46 |
+
* @param XmlImportAstExpression $condition
|
47 |
+
*/
|
48 |
+
public function __construct(XmlImportAstExpression $condition)
|
49 |
+
{
|
50 |
+
$this->condition = $condition;
|
51 |
+
}
|
52 |
+
|
53 |
+
/**
|
54 |
+
* Gets condition
|
55 |
+
*
|
56 |
+
* @return XmlImportAstExpression
|
57 |
+
*/
|
58 |
+
public function getCondition()
|
59 |
+
{
|
60 |
+
return $this->condition;
|
61 |
+
}
|
62 |
+
|
63 |
+
/**
|
64 |
+
* Gets if body
|
65 |
+
*
|
66 |
+
* @return XmlImportAstSequence
|
67 |
+
*/
|
68 |
+
public function getIfBody()
|
69 |
+
{
|
70 |
+
return $this->ifBody;
|
71 |
+
}
|
72 |
+
|
73 |
+
/**
|
74 |
+
* Adds If body
|
75 |
+
*
|
76 |
+
* @param XmlImportAstSequence $body
|
77 |
+
*/
|
78 |
+
public function addIfBody(XmlImportAstSequence $body)
|
79 |
+
{
|
80 |
+
$this->ifBody = $body;
|
81 |
+
}
|
82 |
+
|
83 |
+
/**
|
84 |
+
* Gets list of elseif subcloses
|
85 |
+
*
|
86 |
+
* @return array
|
87 |
+
*/
|
88 |
+
public function getElseIfs()
|
89 |
+
{
|
90 |
+
return $this->elseIfs;
|
91 |
+
}
|
92 |
+
|
93 |
+
/**
|
94 |
+
* Gets else body
|
95 |
+
*
|
96 |
+
* @return XmlImportAstSequence
|
97 |
+
*/
|
98 |
+
public function getElseBody()
|
99 |
+
{
|
100 |
+
return $this->elseBody;
|
101 |
+
}
|
102 |
+
|
103 |
+
/**
|
104 |
+
* Adds else body
|
105 |
+
*
|
106 |
+
* @param XmlImportAstSequence $body
|
107 |
+
*/
|
108 |
+
public function addElseBody(XmlImportAstSequence $body)
|
109 |
+
{
|
110 |
+
$this->elseBody = $body;
|
111 |
+
}
|
112 |
+
|
113 |
+
/**
|
114 |
+
* Adds elseif subclause
|
115 |
+
*
|
116 |
+
* @param XmlImportAstElseif $elseif
|
117 |
+
*/
|
118 |
+
public function addElseif(XmlImportAstElseif $elseif)
|
119 |
+
{
|
120 |
+
$this->elseIfs[] = $elseif;
|
121 |
+
}
|
122 |
+
|
123 |
+
/**
|
124 |
+
* String represetation of an IF clause
|
125 |
+
*
|
126 |
+
* @return string
|
127 |
+
*/
|
128 |
+
public function __toString()
|
129 |
+
{
|
130 |
+
$result = "--> begin " . get_class($this) . "\n";
|
131 |
+
$result .= ' Condition: ' . $this->condition . "\n";
|
132 |
+
$result .= " Body:\n";
|
133 |
+
$array = explode("\n", $this->ifBody);
|
134 |
+
for ($i = 0; $i < count($array); $i++)
|
135 |
+
{
|
136 |
+
$array[$i] = ' ' . $array[$i];
|
137 |
+
}
|
138 |
+
$result .= implode("\n", $array) . "\n";
|
139 |
+
foreach($this->elseIfs as $elseIf)
|
140 |
+
{
|
141 |
+
$array = explode("\n", $elseIf);
|
142 |
+
for ($i = 0; $i < count($array); $i++)
|
143 |
+
{
|
144 |
+
$array[$i] = '' . $array[$i];
|
145 |
+
}
|
146 |
+
$result .= implode("\n", $array) . "\n";
|
147 |
+
}
|
148 |
+
if (!is_null($this->elseBody))
|
149 |
+
{
|
150 |
+
$result .= " Else:\n";
|
151 |
+
$array = explode("\n", $this->elseBody);
|
152 |
+
for ($i = 0; $i < count($array); $i++)
|
153 |
+
{
|
154 |
+
$array[$i] = ' ' . $array[$i];
|
155 |
+
}
|
156 |
+
$result .= implode("\n", $array) . "\n";
|
157 |
+
}
|
158 |
+
$result .= "--> end " . get_class($this) . "\n";
|
159 |
+
return $result;
|
160 |
+
}
|
161 |
+
}
|
libraries/ast/XmlImportAstInteger.php
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
|
4 |
+
* @package AST
|
5 |
+
*/
|
6 |
+
|
7 |
+
require_once dirname(__FILE__) . '/XmlImportAstLiteral.php';
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Represents an integer number
|
11 |
+
*/
|
12 |
+
class XmlImportAstInteger extends XmlImportAstLiteral
|
13 |
+
{
|
14 |
+
|
15 |
+
}
|
libraries/ast/XmlImportAstLiteral.php
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
|
4 |
+
* @package AST
|
5 |
+
*/
|
6 |
+
|
7 |
+
require_once dirname(__FILE__) . '/XmlImportAstExpression.php';
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Represents a literal node
|
11 |
+
*
|
12 |
+
* @abstract
|
13 |
+
*/
|
14 |
+
abstract class XmlImportAstLiteral extends XmlImportAstExpression
|
15 |
+
{
|
16 |
+
/**
|
17 |
+
* Vsalue of a node
|
18 |
+
*
|
19 |
+
* @var mixed
|
20 |
+
*/
|
21 |
+
private $value;
|
22 |
+
|
23 |
+
/**
|
24 |
+
* Creates new instance of a token
|
25 |
+
*
|
26 |
+
* @param mixed $value
|
27 |
+
*/
|
28 |
+
public function __construct($value)
|
29 |
+
{
|
30 |
+
$this->value = $value;
|
31 |
+
}
|
32 |
+
|
33 |
+
/**
|
34 |
+
* Gets a value of a node
|
35 |
+
*
|
36 |
+
* @return mixed
|
37 |
+
*/
|
38 |
+
public function getValue()
|
39 |
+
{
|
40 |
+
return $this->value;
|
41 |
+
}
|
42 |
+
|
43 |
+
/**
|
44 |
+
* String representation of an literal
|
45 |
+
*
|
46 |
+
* @return string
|
47 |
+
*/
|
48 |
+
public function __toString()
|
49 |
+
{
|
50 |
+
return get_class($this) . ': "' . $this->getValue() . "\"";
|
51 |
+
}
|
52 |
+
}
|
libraries/ast/XmlImportAstMath.php
ADDED
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
|
4 |
+
* @package AST
|
5 |
+
*/
|
6 |
+
|
7 |
+
require_once dirname(__FILE__) . '/XmlImportAstExpression.php';
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Represents a function
|
11 |
+
*/
|
12 |
+
class XmlImportAstMath extends XmlImportAstExpression
|
13 |
+
{
|
14 |
+
/**
|
15 |
+
* Function arguments
|
16 |
+
*
|
17 |
+
* @var array
|
18 |
+
*/
|
19 |
+
private $arguments = array();
|
20 |
+
|
21 |
+
/**
|
22 |
+
* Creates new instance
|
23 |
+
*
|
24 |
+
* @param string $name
|
25 |
+
*/
|
26 |
+
public function __construct($name)
|
27 |
+
{
|
28 |
+
$this->name = $name;
|
29 |
+
}
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Adds argument to a function
|
33 |
+
*
|
34 |
+
* @param XmlImportAstExpression $argument
|
35 |
+
*/
|
36 |
+
public function addArgument(XmlImportAstExpression $argument)
|
37 |
+
{
|
38 |
+
$this->arguments[] = $argument;
|
39 |
+
}
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Gets function arguments
|
43 |
+
*
|
44 |
+
* @return array
|
45 |
+
*/
|
46 |
+
public function getArguments()
|
47 |
+
{
|
48 |
+
return $this->arguments;
|
49 |
+
}
|
50 |
+
|
51 |
+
/**
|
52 |
+
* String representation of a function
|
53 |
+
*
|
54 |
+
* @return string
|
55 |
+
*/
|
56 |
+
public function __toString()
|
57 |
+
{
|
58 |
+
$result = "--> begin " . get_class($this) . "\n";
|
59 |
+
foreach ($this->getArguments() as $argument)
|
60 |
+
{
|
61 |
+
$array = explode("\n", $argument);
|
62 |
+
for ($i = 0; $i < count($array); $i++)
|
63 |
+
{
|
64 |
+
$array[$i] = ' ' . $array[$i];
|
65 |
+
}
|
66 |
+
$result .= implode("\n", $array) . "\n";
|
67 |
+
}
|
68 |
+
|
69 |
+
$result .= "--> end " . get_class($this);
|
70 |
+
|
71 |
+
return $result;
|
72 |
+
}
|
73 |
+
}
|
libraries/ast/XmlImportAstPrint.php
ADDED
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
|
4 |
+
* @package AST
|
5 |
+
*/
|
6 |
+
|
7 |
+
require_once dirname(__FILE__) . '/XmlImportAstStatement.php';
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Represents a PRINT node
|
11 |
+
*/
|
12 |
+
class XmlImportAstPrint extends XmlImportAstStatement
|
13 |
+
{
|
14 |
+
/**
|
15 |
+
* Expression to print
|
16 |
+
*
|
17 |
+
* @var XmlImportAstExpression
|
18 |
+
*/
|
19 |
+
private $value;
|
20 |
+
|
21 |
+
/**
|
22 |
+
* Creates new instance of a statement
|
23 |
+
*
|
24 |
+
* @param XmlImportAstExpression $value
|
25 |
+
*/
|
26 |
+
public function __construct(XmlImportAstExpression $value)
|
27 |
+
{
|
28 |
+
$this->value = $value;
|
29 |
+
}
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Get value to be printed
|
33 |
+
*
|
34 |
+
* @return XmlImportAstExpression
|
35 |
+
*/
|
36 |
+
public function getValue()
|
37 |
+
{
|
38 |
+
return $this->value;
|
39 |
+
}
|
40 |
+
|
41 |
+
/**
|
42 |
+
* String representation of a PRINT clause
|
43 |
+
*
|
44 |
+
* @return string
|
45 |
+
*/
|
46 |
+
public function __toString()
|
47 |
+
{
|
48 |
+
$result = "--> begin " . get_class($this) . "\n";
|
49 |
+
$array = explode("\n", $this->value);
|
50 |
+
for ($i = 0; $i < count($array); $i++)
|
51 |
+
{
|
52 |
+
$array[$i] = ' ' . $array[$i];
|
53 |
+
}
|
54 |
+
$result .= implode("\n", $array) . "\n";
|
55 |
+
|
56 |
+
$result .= "--> end " . get_class($this);
|
57 |
+
|
58 |
+
return $result;
|
59 |
+
}
|
60 |
+
}
|
libraries/ast/XmlImportAstSequence.php
ADDED
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
|
4 |
+
* @package AST
|
5 |
+
*/
|
6 |
+
|
7 |
+
require_once dirname(__FILE__) . '/XmlImportAstStatement.php';
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Represents statement sequence
|
11 |
+
*/
|
12 |
+
class XmlImportAstSequence extends XmlImportAstStatement
|
13 |
+
{
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Sequence instance number
|
17 |
+
*
|
18 |
+
* @var int
|
19 |
+
*/
|
20 |
+
private static $sequenceInstanceNumber = 0;
|
21 |
+
|
22 |
+
/**
|
23 |
+
* Current sequence number
|
24 |
+
*
|
25 |
+
* @var int
|
26 |
+
*/
|
27 |
+
private $sequenceNumber;
|
28 |
+
|
29 |
+
/**
|
30 |
+
* Current variable number
|
31 |
+
*
|
32 |
+
* @var int
|
33 |
+
*/
|
34 |
+
private $varNumber = 0;
|
35 |
+
|
36 |
+
/**
|
37 |
+
* List of statements
|
38 |
+
*
|
39 |
+
* @var array
|
40 |
+
*/
|
41 |
+
private $statements = array();
|
42 |
+
|
43 |
+
/**
|
44 |
+
* Variable definitions
|
45 |
+
*
|
46 |
+
* @var array
|
47 |
+
*/
|
48 |
+
private $variableDefinitions = array();
|
49 |
+
|
50 |
+
/**
|
51 |
+
* Variables
|
52 |
+
*
|
53 |
+
* @var array
|
54 |
+
*/
|
55 |
+
private $variables = array();
|
56 |
+
|
57 |
+
/**
|
58 |
+
* Creates new instance
|
59 |
+
*/
|
60 |
+
public function __construct()
|
61 |
+
{
|
62 |
+
$this->sequenceNumber = self::$sequenceInstanceNumber++;
|
63 |
+
}
|
64 |
+
|
65 |
+
/**
|
66 |
+
* Adds statement to a sequence
|
67 |
+
*
|
68 |
+
* @param XmlImportAstStatement $statement
|
69 |
+
*/
|
70 |
+
public function addStatement(XmlImportAstStatement $statement)
|
71 |
+
{
|
72 |
+
$this->statements[] = $statement;
|
73 |
+
}
|
74 |
+
|
75 |
+
/**
|
76 |
+
* Gets the list of statements
|
77 |
+
*
|
78 |
+
* @return array
|
79 |
+
*/
|
80 |
+
public function getStatements()
|
81 |
+
{
|
82 |
+
return $this->statements;
|
83 |
+
}
|
84 |
+
|
85 |
+
/**
|
86 |
+
* Adds variable to a sequence
|
87 |
+
*
|
88 |
+
* @param XmlImportAstXPath $xpath
|
89 |
+
*/
|
90 |
+
public function addVariable(XmlImportAstXPath $xpath)
|
91 |
+
{
|
92 |
+
if (!array_key_exists($xpath->getValue(), $this->variables))
|
93 |
+
{
|
94 |
+
$var = '$v' . $this->sequenceNumber . '_' . $this->varNumber++;
|
95 |
+
$value = $escapedValue = strtr($xpath->getValue(), array(
|
96 |
+
"\n" => "\\n",
|
97 |
+
"\t" => "\\t",
|
98 |
+
"\r" => "\\r",
|
99 |
+
"$" => "\\$",
|
100 |
+
"\"" => "\\\"",
|
101 |
+
"\\" => "\\\\",
|
102 |
+
));
|
103 |
+
$result = $var . ' = {{XML}}->xpath("' . $value . '");';
|
104 |
+
$this->variables[$xpath->getValue()] = $var;
|
105 |
+
$this->variableDefinitions[] = $result;
|
106 |
+
}
|
107 |
+
}
|
108 |
+
|
109 |
+
/**
|
110 |
+
* Gets variable definitions
|
111 |
+
*
|
112 |
+
* @return array
|
113 |
+
*/
|
114 |
+
public function getVariableDefinitions()
|
115 |
+
{
|
116 |
+
return $this->variableDefinitions;
|
117 |
+
}
|
118 |
+
|
119 |
+
/**
|
120 |
+
* Gets variables
|
121 |
+
*
|
122 |
+
* @return array
|
123 |
+
*/
|
124 |
+
public function getVariables()
|
125 |
+
{
|
126 |
+
return $this->variables;
|
127 |
+
}
|
128 |
+
|
129 |
+
/**
|
130 |
+
* Returns the number of current instance
|
131 |
+
*
|
132 |
+
* @return int
|
133 |
+
*/
|
134 |
+
public function getSequenceNumber()
|
135 |
+
{
|
136 |
+
return $this->sequenceNumber;
|
137 |
+
}
|
138 |
+
|
139 |
+
/**
|
140 |
+
* String representation of a sequence node
|
141 |
+
*
|
142 |
+
* @return string
|
143 |
+
*/
|
144 |
+
public function __toString()
|
145 |
+
{
|
146 |
+
$result = "--> begin " . get_class($this) . "\n";
|
147 |
+
foreach ($this->getStatements() as $statement)
|
148 |
+
{
|
149 |
+
$array = explode("\n", $statement);
|
150 |
+
for ($i = 0; $i < count($array); $i++)
|
151 |
+
{
|
152 |
+
$array[$i] = ' ' . $array[$i];
|
153 |
+
}
|
154 |
+
$result .= implode("\n", $array) . "\n";
|
155 |
+
}
|
156 |
+
|
157 |
+
$result .= "--> end " . get_class($this);
|
158 |
+
|
159 |
+
return $result;
|
160 |
+
}
|
161 |
+
}
|
libraries/ast/XmlImportAstStatement.php
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
|
4 |
+
* @package AST
|
5 |
+
*/
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Represents a statement
|
9 |
+
*
|
10 |
+
* @abstract
|
11 |
+
*/
|
12 |
+
abstract class XmlImportAstStatement
|
13 |
+
{
|
14 |
+
|
15 |
+
}
|
libraries/ast/XmlImportAstString.php
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
|
4 |
+
* @package AST
|
5 |
+
*/
|
6 |
+
|
7 |
+
require_once dirname(__FILE__) . '/XmlImportAstLiteral.php';
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Represents a string
|
11 |
+
*/
|
12 |
+
class XmlImportAstString extends XmlImportAstLiteral
|
13 |
+
{
|
14 |
+
|
15 |
+
}
|
libraries/ast/XmlImportAstText.php
ADDED
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
|
4 |
+
* @package AST
|
5 |
+
*/
|
6 |
+
|
7 |
+
require_once dirname(__FILE__) . '/XmlImportAstStatement.php';
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Represents a text
|
11 |
+
*/
|
12 |
+
class XmlImportAstText extends XmlImportAstStatement
|
13 |
+
{
|
14 |
+
/**
|
15 |
+
* Value of a text node
|
16 |
+
*
|
17 |
+
* @var string
|
18 |
+
*/
|
19 |
+
private $value;
|
20 |
+
|
21 |
+
/**
|
22 |
+
* Creates new instance
|
23 |
+
*
|
24 |
+
* @param string $value
|
25 |
+
*/
|
26 |
+
public function __construct($value)
|
27 |
+
{
|
28 |
+
$this->value = $value;
|
29 |
+
}
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Gets value of a text node
|
33 |
+
*
|
34 |
+
* @return string
|
35 |
+
*/
|
36 |
+
public function getValue()
|
37 |
+
{
|
38 |
+
return $this->value;
|
39 |
+
}
|
40 |
+
|
41 |
+
/**
|
42 |
+
* String representation of the node
|
43 |
+
*
|
44 |
+
* @return string
|
45 |
+
*/
|
46 |
+
public function __toString()
|
47 |
+
{
|
48 |
+
$result = "--> begin " . get_class($this) . "\n";
|
49 |
+
$array = explode("\n", trim($this->getValue()));
|
50 |
+
for ($i = 0; $i < count($array); $i++)
|
51 |
+
{
|
52 |
+
$array[$i] = ' ' . $array[$i];
|
53 |
+
}
|
54 |
+
$result .= implode("\n", $array) . "\n";
|
55 |
+
$result .= "--> end " . get_class($this);
|
56 |
+
|
57 |
+
return $result;
|
58 |
+
}
|
59 |
+
}
|
libraries/ast/XmlImportAstWith.php
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
|
4 |
+
* @package AST
|
5 |
+
*/
|
6 |
+
|
7 |
+
require_once dirname(__FILE__) . '/XmlImportAstXpathClause.php';
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Represents a WITH clause
|
11 |
+
*/
|
12 |
+
class XmlImportAstWith extends XmlImportAstXPathClause
|
13 |
+
{
|
14 |
+
|
15 |
+
}
|
libraries/ast/XmlImportAstXPath.php
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
|
4 |
+
* @package AST
|
5 |
+
*/
|
6 |
+
|
7 |
+
require_once dirname(__FILE__) . '/XmlImportAstLiteral.php';
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Represents XPath literal
|
11 |
+
*/
|
12 |
+
class XmlImportAstXPath extends XmlImportAstLiteral
|
13 |
+
{
|
14 |
+
|
15 |
+
}
|
libraries/ast/XmlImportAstXpathClause.php
ADDED
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
|
4 |
+
* @package AST
|
5 |
+
*/
|
6 |
+
|
7 |
+
require_once dirname(__FILE__) . '/XmlImportAstStatement.php';
|
8 |
+
require_once dirname(__FILE__) . '/XmlImportAstXPath.php';
|
9 |
+
require_once dirname(__FILE__) . '/XmlImportAstSequence.php';
|
10 |
+
|
11 |
+
/**
|
12 |
+
* Represents a clause that is based on XPath
|
13 |
+
*/
|
14 |
+
abstract class XmlImportAstXPathClause extends XmlImportAstStatement
|
15 |
+
{
|
16 |
+
/**
|
17 |
+
* XPath expression
|
18 |
+
*
|
19 |
+
* @var XmlImportAstXPath
|
20 |
+
*/
|
21 |
+
private $xpath;
|
22 |
+
|
23 |
+
/**
|
24 |
+
* Clause body
|
25 |
+
*
|
26 |
+
* @var XmlImportAstSequence
|
27 |
+
*/
|
28 |
+
private $body;
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Creates new instance
|
32 |
+
*
|
33 |
+
* @param XmlImportAstXPath $xpath
|
34 |
+
* @param XmlImportAstSequence $body
|
35 |
+
*/
|
36 |
+
public function __construct(XmlImportAstXPath $xpath, XmlImportAstSequence $body)
|
37 |
+
{
|
38 |
+
$this->xpath = $xpath;
|
39 |
+
$this->body = $body;
|
40 |
+
}
|
41 |
+
|
42 |
+
/**
|
43 |
+
* Gets XPath
|
44 |
+
*
|
45 |
+
* @return XmlImportAstXPath
|
46 |
+
*/
|
47 |
+
public function getXpath()
|
48 |
+
{
|
49 |
+
return $this->xpath;
|
50 |
+
}
|
51 |
+
|
52 |
+
/**
|
53 |
+
* Gets clause body
|
54 |
+
*
|
55 |
+
* @return XmlImportAstSequence
|
56 |
+
*/
|
57 |
+
public function getBody()
|
58 |
+
{
|
59 |
+
return $this->body;
|
60 |
+
}
|
61 |
+
|
62 |
+
/**
|
63 |
+
* String representation of a clause
|
64 |
+
*
|
65 |
+
* @return string
|
66 |
+
*/
|
67 |
+
public function __toString()
|
68 |
+
{
|
69 |
+
$result = "--> begin " . get_class($this) . "\n";
|
70 |
+
$result .= ' XPath: ' . $this->xpath . "\n";
|
71 |
+
$result .= " Body:\n";
|
72 |
+
|
73 |
+
$array = explode("\n", $this->body);
|
74 |
+
for ($i = 0; $i < count($array); $i++)
|
75 |
+
{
|
76 |
+
$array[$i] = ' ' . $array[$i];
|
77 |
+
}
|
78 |
+
|
79 |
+
$result .= implode("\n", $array) . "\n";
|
80 |
+
$result .= "--> end " . get_class($this) . "\n";
|
81 |
+
return $result;
|
82 |
+
}
|
83 |
+
}
|
models/file/list.php
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class PMXI_File_List extends PMXI_Model_List {
|
4 |
+
public function __construct() {
|
5 |
+
parent::__construct();
|
6 |
+
$this->setTable(PMXI_Plugin::getInstance()->getTablePrefix() . 'files');
|
7 |
+
}
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Sweep history files in accordance with plugin settings
|
11 |
+
* @return PMXI_File_List
|
12 |
+
* @chainable
|
13 |
+
*/
|
14 |
+
public function sweepHistory() {
|
15 |
+
$age = PMXI_Plugin::getInstance()->getOption('history_file_age');
|
16 |
+
if ($age > 0) {
|
17 |
+
$date = new DateTime(); $date->modify('-' . $age . ' day');
|
18 |
+
foreach ($this->getBy('registered_on <', $date->format('Y-m-d'))->convertRecords() as $f) {
|
19 |
+
$f->delete();
|
20 |
+
}
|
21 |
+
}
|
22 |
+
$count = PMXI_Plugin::getInstance()->getOption('history_file_count');
|
23 |
+
if ($count > 0) {
|
24 |
+
$count_actual = $this->countBy();
|
25 |
+
if ($count_actual > $count) foreach ($this->getBy(NULL, 'registered_on', 1, $count_actual - $count)->convertRecords() as $f) {
|
26 |
+
$f->delete();
|
27 |
+
}
|
28 |
+
}
|
29 |
+
|
30 |
+
return $this;
|
31 |
+
}
|
32 |
+
}
|
models/file/record.php
ADDED
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class PMXI_File_Record extends PMXI_Model_Record {
|
4 |
+
/**
|
5 |
+
* Initialize model instance
|
6 |
+
* @param array[optional] $data Array of record data to initialize object with
|
7 |
+
*/
|
8 |
+
public function __construct($data = array()) {
|
9 |
+
parent::__construct($data);
|
10 |
+
$this->setTable(PMXI_Plugin::getInstance()->getTablePrefix() . 'files');
|
11 |
+
}
|
12 |
+
|
13 |
+
/**
|
14 |
+
* @see PMXI_Model_Record::insert()
|
15 |
+
*/
|
16 |
+
public function insert() {
|
17 |
+
$file_contents = NULL;
|
18 |
+
if ($this->offsetExists('contents')) {
|
19 |
+
$file_contents = $this['contents'];
|
20 |
+
unset($this->contents);
|
21 |
+
}
|
22 |
+
|
23 |
+
parent::insert();
|
24 |
+
|
25 |
+
if (isset($this->id) and ! is_null($file_contents)) {
|
26 |
+
file_put_contents(PMXI_Plugin::ROOT_DIR . '/history/' . $this->id, $file_contents);
|
27 |
+
}
|
28 |
+
|
29 |
+
$list = new PMXI_File_List();
|
30 |
+
$list->sweepHistory();
|
31 |
+
return $this;
|
32 |
+
}
|
33 |
+
|
34 |
+
/**
|
35 |
+
* @see PMXI_Model_Record::update()
|
36 |
+
*/
|
37 |
+
public function update() {
|
38 |
+
$file_contents = NULL;
|
39 |
+
if ($this->offsetExists('contents')) {
|
40 |
+
$file_contents = $this['contents'];
|
41 |
+
unset($this->contents);
|
42 |
+
}
|
43 |
+
|
44 |
+
parent::update();
|
45 |
+
|
46 |
+
if (isset($this->id) and ! is_null($file_contents)) {
|
47 |
+
file_put_contents(PMXI_Plugin::ROOT_DIR . '/history/' . $this->id, $file_contents);
|
48 |
+
}
|
49 |
+
|
50 |
+
return $this;
|
51 |
+
}
|
52 |
+
|
53 |
+
public function __isset($field) {
|
54 |
+
if ('contents' == $field and ! $this->offsetExists($field)) {
|
55 |
+
return isset($this->id) and file_exists(PMXI_Plugin::ROOT_DIR . '/history/' . $this->id);
|
56 |
+
}
|
57 |
+
return parent::__isset($field);
|
58 |
+
}
|
59 |
+
|
60 |
+
public function __get($field) {
|
61 |
+
if ('contents' == $field and ! $this->offsetExists('contents')) {
|
62 |
+
if (isset($this->contents)) {
|
63 |
+
$this['contents'] = file_get_contents(PMXI_Plugin::ROOT_DIR . '/history/' . $this->id);
|
64 |
+
} else {
|
65 |
+
$this->contents = NULL;
|
66 |
+
}
|
67 |
+
}
|
68 |
+
return parent::__get($field);
|
69 |
+
}
|
70 |
+
|
71 |
+
public function delete() {
|
72 |
+
if ($this->id) { // delete history file first
|
73 |
+
$file_name = PMXI_Plugin::ROOT_DIR . '/history/' . $this->id;
|
74 |
+
is_file($file_name) and unlink($file_name);
|
75 |
+
}
|
76 |
+
return parent::delete();
|
77 |
+
}
|
78 |
+
}
|
models/import/list.php
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class PMXI_Import_List extends PMXI_Model_List {
|
4 |
+
public function __construct() {
|
5 |
+
parent::__construct();
|
6 |
+
$this->setTable(PMXI_Plugin::getInstance()->getTablePrefix() . 'imports');
|
7 |
+
}
|
8 |
+
}
|
models/import/record.php
ADDED
@@ -0,0 +1,728 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class PMXI_Import_Record extends PMXI_Model_Record {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Some pre-processing logic, such as removing control characters from xml to prevent parsing errors
|
7 |
+
* @param string $xml
|
8 |
+
*/
|
9 |
+
public static function preprocessXml( & $xml) {
|
10 |
+
//$xml = preg_replace('%\pC(?<!\s)%u', '', $xml); // remove control chars but not white-spacing ones
|
11 |
+
//use HTML::Entities;
|
12 |
+
$xml = preg_replace("/&.{0,}?;/",'',$xml);
|
13 |
+
}
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Validate XML to be valid for improt
|
17 |
+
* @param string $xml
|
18 |
+
* @param WP_Error[optional] $errors
|
19 |
+
* @return bool Validation status
|
20 |
+
*/
|
21 |
+
public static function validateXml( & $xml, $errors = NULL) {
|
22 |
+
if (FALSE === $xml or '' == $xml) {
|
23 |
+
$errors and $errors->add('form-validation', __('XML file does not exist, not accessible or empty', 'pmxi_plugin'));
|
24 |
+
} else {
|
25 |
+
if (PMXI_Plugin::getInstance()->getOption('html_entities')) PMXI_Import_Record::preprocessXml($xml);
|
26 |
+
libxml_use_internal_errors(true);
|
27 |
+
libxml_clear_errors();
|
28 |
+
$_x = @simplexml_load_string($xml);
|
29 |
+
$xml_errors = libxml_get_errors();
|
30 |
+
libxml_clear_errors();
|
31 |
+
if ($xml_errors) {
|
32 |
+
$error_msg = '<strong>' . __('Invalid XML', 'pmxi_plugin') . '</strong><ul>';
|
33 |
+
foreach($xml_errors as $error) {
|
34 |
+
$error_msg .= '<li>';
|
35 |
+
$error_msg .= __('Line', 'pmxi_plugin') . ' ' . $error->line . ', ';
|
36 |
+
$error_msg .= __('Column', 'pmxi_plugin') . ' ' . $error->column . ', ';
|
37 |
+
$error_msg .= __('Code', 'pmxi_plugin') . ' ' . $error->code . ': ';
|
38 |
+
$error_msg .= '<em>' . trim(esc_html($error->message)) . '</em>';
|
39 |
+
$error_msg .= '</li>';
|
40 |
+
}
|
41 |
+
$error_msg .= '</ul>';
|
42 |
+
$errors and $errors->add('form-validation', $error_msg);
|
43 |
+
} else {
|
44 |
+
return true;
|
45 |
+
}
|
46 |
+
}
|
47 |
+
return false;
|
48 |
+
}
|
49 |
+
|
50 |
+
/**
|
51 |
+
* Initialize model instance
|
52 |
+
* @param array[optional] $data Array of record data to initialize object with
|
53 |
+
*/
|
54 |
+
public function __construct($data = array()) {
|
55 |
+
parent::__construct($data);
|
56 |
+
$this->setTable(PMXI_Plugin::getInstance()->getTablePrefix() . 'imports');
|
57 |
+
}
|
58 |
+
|
59 |
+
/**
|
60 |
+
* Check whether current import should be perfomed again according to scheduling options
|
61 |
+
*/
|
62 |
+
public function isDue()
|
63 |
+
{
|
64 |
+
if ( ! $this->scheduled or date('YmdHi') <= date('YmdHi', strtotime($this->registered_on))) return false; // scheduling is disabled or the task has been executed this very minute
|
65 |
+
if ('0000-00-00 00:00:00' == $this->registered_on) return true; // never executed but scheduled
|
66 |
+
|
67 |
+
$task = new _PMXI_Import_Record_Cron_Parser($this->scheduled);
|
68 |
+
return $task->isDue($this->registered_on);
|
69 |
+
}
|
70 |
+
|
71 |
+
/**
|
72 |
+
* Import all files matched by path
|
73 |
+
* @param callback[optional] $logger Method where progress messages are submmitted
|
74 |
+
* @return PMXI_Import_Record
|
75 |
+
* @chainable
|
76 |
+
*/
|
77 |
+
public function execute($logger = NULL) {
|
78 |
+
$this->set('registered_on', date('Y-m-d H:i:s'))->save(); // update registered_on to indicated that job has been exectured even if no files are going to be imported by the rest of the method
|
79 |
+
|
80 |
+
if ($this->path) {
|
81 |
+
if (in_array($this->type, array('ftp', 'file'))) { // file paths support patterns
|
82 |
+
$logger and call_user_func($logger, __('Reading files for import...', 'pmxi_plugin'));
|
83 |
+
$files = PMXI_Helper::safe_glob($this->path, PMXI_Helper::GLOB_NODIR | PMXI_Helper::GLOB_PATH);
|
84 |
+
$logger and call_user_func($logger, sprintf(_n('%s file found', '%s files found', count($files), 'pmxi_plugin'), count($files)));
|
85 |
+
} else { // single file path
|
86 |
+
$files = array($this->path);
|
87 |
+
}
|
88 |
+
foreach ($files as $ind => $path) {
|
89 |
+
$logger and call_user_func($logger, sprintf(__('Importing %s (%s of %s)', 'pmxi_plugin'), $path, $ind + 1, count($files)));
|
90 |
+
|
91 |
+
ob_start();
|
92 |
+
readgzfile($path);
|
93 |
+
$xml = ob_get_clean();
|
94 |
+
if ( ! $xml) {
|
95 |
+
$logger and call_user_func($logger, __('<b>ERROR</b>', 'pmxi_plugin') . ': file is not accessible or empty');
|
96 |
+
} elseif ( ! PMXI_Import_Record::validateXml($xml)) {
|
97 |
+
$logger and call_user_func($logger, __('<b>ERROR</b>', 'pmxi_plugin') . ': file is not a properly fromatted XML document');
|
98 |
+
} else {
|
99 |
+
$this->process($xml, $logger);
|
100 |
+
}
|
101 |
+
}
|
102 |
+
$logger and call_user_func($logger, __('Complete', 'pmxi_plugin'));
|
103 |
+
}
|
104 |
+
return $this;
|
105 |
+
}
|
106 |
+
|
107 |
+
/**
|
108 |
+
* Perform import operation
|
109 |
+
* @param string $xml XML string to import
|
110 |
+
* @param callback[optional] $logger Method where progress messages are submmitted
|
111 |
+
* @return PMXI_Import_Record
|
112 |
+
* @chainable
|
113 |
+
*/
|
114 |
+
public function process($xml, $logger = NULL, $last_chank = false) {
|
115 |
+
add_filter('user_has_cap', array($this, '_filter_has_cap_unfiltered_html')); kses_init(); // do not perform special filtering for imported content
|
116 |
+
|
117 |
+
$this->options += PMXI_Plugin::get_default_import_options(); // make sure all options are defined
|
118 |
+
|
119 |
+
$first_chank = $_SESSION['pmxi_import']['options']['is_first_chank'];
|
120 |
+
|
121 |
+
$postRecord = new PMXI_Post_Record();
|
122 |
+
|
123 |
+
$tmp_files = array();
|
124 |
+
// compose records to import
|
125 |
+
$records = array();
|
126 |
+
if ($this->options['is_import_specified']) {
|
127 |
+
foreach (preg_split('% *, *%', $this->options['import_specified'], -1, PREG_SPLIT_NO_EMPTY) as $chank) {
|
128 |
+
if (preg_match('%^(\d+)-(\d+)$%', $chank, $mtch)) {
|
129 |
+
$records = array_merge($records, range(intval($mtch[1]), intval($mtch[2])));
|
130 |
+
} else {
|
131 |
+
$records = array_merge($records, array(intval($chank)));
|
132 |
+
}
|
133 |
+
}
|
134 |
+
}
|
135 |
+
try {
|
136 |
+
|
137 |
+
$first_chank and $logger and call_user_func($logger, __('Composing contents...', 'pmxi_plugin'));
|
138 |
+
$contents = XmlImportParser::factory(
|
139 |
+
(intval($this->template['is_keep_linebreaks']) ? $xml : preg_replace('%\r\n?|\n%', ' ', $xml)),
|
140 |
+
$this->xpath,
|
141 |
+
$this->template['content'],
|
142 |
+
$file)->parse($records
|
143 |
+
); $tmp_files[] = $file;
|
144 |
+
|
145 |
+
$first_chank and $logger and call_user_func($logger, __('Composing titles...', 'pmxi_plugin'));
|
146 |
+
$titles = XmlImportParser::factory($xml, $this->xpath, $this->template['title'], $file)->parse($records); $tmp_files[] = $file;
|
147 |
+
|
148 |
+
$first_chank and $logger and call_user_func($logger, __('Composing dates...', 'pmxi_plugin'));
|
149 |
+
|
150 |
+
$dates = XmlImportParser::factory($xml, $this->xpath, $this->options['date'], $file)->parse($records); $tmp_files[] = $file;
|
151 |
+
$warned = array(); // used to prevent the same notice displaying several times
|
152 |
+
foreach ($dates as $i => $d) {
|
153 |
+
$time = strtotime($d);
|
154 |
+
if (FALSE === $time) {
|
155 |
+
in_array($d, $warned) or $logger and call_user_func($logger, sprintf(__('<b>WARNING</b>: unrecognized date format `%s`, assigning current date', 'pmxi_plugin'), $warned[] = $d));
|
156 |
+
$time = time();
|
157 |
+
}
|
158 |
+
$dates[$i] = date('Y-m-d H:i:s', $time);
|
159 |
+
}
|
160 |
+
|
161 |
+
if ('post' == $this->options['type']) {
|
162 |
+
$tags = array();
|
163 |
+
if ($this->options['tags']) {
|
164 |
+
$first_chank and $logger and call_user_func($logger, __('Composing tags...', 'pmxi_plugin'));
|
165 |
+
$tags_raw = XmlImportParser::factory($xml, $this->xpath, $this->options['tags'], $file)->parse($records); $tmp_files[] = $file;
|
166 |
+
foreach ($tags_raw as $i => $t_raw) {
|
167 |
+
$tags[$i] = '';
|
168 |
+
if ('' != $t_raw) $tags[$i] = implode(', ', str_getcsv($t_raw, $this->options['tags_delim']));
|
169 |
+
}
|
170 |
+
} else {
|
171 |
+
count($titles) and $tags = array_fill(0, count($titles), '');
|
172 |
+
}
|
173 |
+
$cats = array();
|
174 |
+
if ($this->options['categories']) {
|
175 |
+
$first_chank and $logger and call_user_func($logger, __('Composing categories...', 'pmxi_plugin'));
|
176 |
+
$cats_raw = XmlImportParser::factory($xml, $this->xpath, $this->options['categories'], $file)->parse($records); $tmp_files[] = $file;
|
177 |
+
$warned = array(); // used to prevent the same notice displaying several times
|
178 |
+
foreach ($cats_raw as $i => $c_raw) {
|
179 |
+
$cats[$i] = array();
|
180 |
+
if ('' != $c_raw) foreach (str_getcsv($c_raw, $this->options['categories_delim']) as $c) if ('' != $c) {
|
181 |
+
$cat = get_term_by('name', $c, 'category') or ctype_digit($c) and $cat = get_term_by('id', $c, 'category');
|
182 |
+
if ( ! $cat) { // create category automatically
|
183 |
+
$cat_id = wp_create_category($c);
|
184 |
+
if ( ! $cat_id) {
|
185 |
+
in_array($c, $warned) or $logger and call_user_func($logger, sprintf(__('<b>WARNING</b>: Unable to create category `%s`, skipping', 'pmxi_plugin'), $warned[] = $c));
|
186 |
+
} else {
|
187 |
+
$cats[$i][] = $cat_id;
|
188 |
+
}
|
189 |
+
} else {
|
190 |
+
$cats[$i][] = $cat->term_id;
|
191 |
+
}
|
192 |
+
}
|
193 |
+
}
|
194 |
+
} else {
|
195 |
+
count($titles) and $cats = array_fill(0, count($titles), '');
|
196 |
+
}
|
197 |
+
}
|
198 |
+
// [custom taxonomies]
|
199 |
+
$taxonomies = array();
|
200 |
+
$taxonomies_param = $this->options['type'].'_taxonomies';
|
201 |
+
if ('page' == $this->options['type']) {
|
202 |
+
$taxonomies_object_type = 'page';
|
203 |
+
} elseif ('' != $this->options['custom_type']) {
|
204 |
+
$taxonomies_object_type = $this->options['custom_type'];
|
205 |
+
} else {
|
206 |
+
$taxonomies_object_type = 'post';
|
207 |
+
}
|
208 |
+
foreach ($this->options[$taxonomies_param] as $tx_name => $tx_template) if ('' != $tx_template) {
|
209 |
+
$tx = get_taxonomy($tx_name);
|
210 |
+
if (in_array($taxonomies_object_type, $tx->object_type)) {
|
211 |
+
$first_chank and $logger and call_user_func($logger, sprintf(__('Composing terms for `%s` taxonomy...', 'pmxi_plugin'), $tx->labels->name));
|
212 |
+
$txes = array();
|
213 |
+
$txes_raw = XmlImportParser::factory($xml, $this->xpath, $tx_template, $file)->parse($records); $tmp_files[] = $file;
|
214 |
+
foreach ($txes_raw as $i => $tx_raw) {
|
215 |
+
$taxonomies[$tx_name][$i] = str_getcsv($tx_raw, $this->options[$taxonomies_param.'_delim'][$tx_name]);
|
216 |
+
}
|
217 |
+
}
|
218 |
+
}
|
219 |
+
// [/custom taxonomies]
|
220 |
+
|
221 |
+
$first_chank and $logger and call_user_func($logger, __('Composing custom parameters...', 'pmxi_plugin'));
|
222 |
+
$meta_keys = array(); $meta_values = array();
|
223 |
+
foreach ($this->options['custom_name'] as $j => $custom_name) {
|
224 |
+
$meta_keys[$j] = XmlImportParser::factory($xml, $this->xpath, $custom_name, $file)->parse($records); $tmp_files[] = $file;
|
225 |
+
$meta_values[$j] = XmlImportParser::factory($xml, $this->xpath, $this->options['custom_value'][$j], $file)->parse($records); $tmp_files[] = $file;
|
226 |
+
}
|
227 |
+
if ( ! (($uploads = wp_upload_dir()) && false === $uploads['error'])) {
|
228 |
+
$logger and call_user_func($logger, __('<b>ERROR</b>', 'pmxi_plugin') . ': ' . $uploads['error']);
|
229 |
+
$logger and call_user_func($logger, __('<b>WARNING</b>: No featured images will be created', 'pmxi_plugin'));
|
230 |
+
} else {
|
231 |
+
$first_chank and $logger and call_user_func($logger, __('Composing URLs for featured images...', 'pmxi_plugin'));
|
232 |
+
$featured_images = array();
|
233 |
+
if ($this->options['featured_image']) {
|
234 |
+
// Detect if images is separated by comma
|
235 |
+
$imgs = explode(',',$this->options['featured_image']);
|
236 |
+
if (!empty($imgs)){
|
237 |
+
$parse_multiple = true;
|
238 |
+
foreach($imgs as $img) if (!preg_match("/{.*}/", trim($img))) $parse_multiple = false;
|
239 |
+
if ($parse_multiple)
|
240 |
+
{
|
241 |
+
foreach($imgs as $img)
|
242 |
+
{
|
243 |
+
$posts_images = XmlImportParser::factory($xml, $this->xpath, $img, $file)->parse($records); $tmp_files[] = $file;
|
244 |
+
foreach($posts_images as $i => $val)
|
245 |
+
{
|
246 |
+
$reg_exUrl = "/(http|https|ftp|ftps)\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,4}(\/\S*)?/";
|
247 |
+
preg_match_all($reg_exUrl, $val, $matches);
|
248 |
+
$usedPatterns = array();
|
249 |
+
foreach($matches[0] as $pattern){
|
250 |
+
if(!array_key_exists($pattern, $usedPatterns)){
|
251 |
+
$usedPatterns[$pattern]=true;
|
252 |
+
$featured_images[$i][] = str_replace(',','',$pattern);
|
253 |
+
}
|
254 |
+
}
|
255 |
+
}
|
256 |
+
|
257 |
+
}
|
258 |
+
}
|
259 |
+
else
|
260 |
+
{
|
261 |
+
$featured_images = XmlImportParser::factory($xml, $this->xpath, $this->options['featured_image'], $file)->parse($records); $tmp_files[] = $file;
|
262 |
+
}
|
263 |
+
}
|
264 |
+
|
265 |
+
} else {
|
266 |
+
count($titles) and $featured_images = array_fill(0, count($titles), '');
|
267 |
+
}
|
268 |
+
}
|
269 |
+
$first_chank and $logger and call_user_func($logger, __('Composing unique keys...', 'pmxi_plugin'));
|
270 |
+
$unique_keys = XmlImportParser::factory($xml, $this->xpath, $this->options['unique_key'], $file)->parse($records); $tmp_files[] = $file;
|
271 |
+
|
272 |
+
$first_chank and $logger and call_user_func($logger, __('Processing posts...', 'pmxi_plugin'));
|
273 |
+
|
274 |
+
if ('post' == $this->options['type'] and '' != $this->options['custom_type']) {
|
275 |
+
$post_type = $this->options['custom_type'];
|
276 |
+
} else {
|
277 |
+
$post_type = $this->options['type'];
|
278 |
+
}
|
279 |
+
|
280 |
+
$current_post_ids = array();
|
281 |
+
foreach ($titles as $i => $void) {
|
282 |
+
$articleData = array(
|
283 |
+
'post_type' => $post_type,
|
284 |
+
'post_status' => $this->options['status'],
|
285 |
+
'comment_status' => $this->options['comment_status'],
|
286 |
+
'ping_status' => $this->options['ping_status'],
|
287 |
+
'post_title' => $titles[$i],
|
288 |
+
'post_content' => $contents[$i],
|
289 |
+
'post_date' => $dates[$i],
|
290 |
+
'post_date_gmt' => get_gmt_from_date($dates[$i]),
|
291 |
+
'post_author' => $this->options['author'],
|
292 |
+
);
|
293 |
+
if ('post' == $articleData['post_type']) {
|
294 |
+
$articleData += array(
|
295 |
+
'post_category' => $cats[$i],
|
296 |
+
'tags_input' => $tags[$i],
|
297 |
+
);
|
298 |
+
} else { // page
|
299 |
+
$articleData += array(
|
300 |
+
'menu_order' => $this->options['order'],
|
301 |
+
'post_parent' => $this->options['parent'],
|
302 |
+
);
|
303 |
+
}
|
304 |
+
$postRecord->clear();
|
305 |
+
// find corresponding article among previously imported
|
306 |
+
$postRecord->getBy(array(
|
307 |
+
'unique_key' => $unique_keys[$i],
|
308 |
+
'import_id' => $this->id,
|
309 |
+
));
|
310 |
+
if ( ! $postRecord->isEmpty()) {
|
311 |
+
$post_to_update = get_post($post_to_update_id = $postRecord->post_id);
|
312 |
+
if ($post_to_update) { // existing post is found
|
313 |
+
if ($this->options['is_keep_former_posts']) {
|
314 |
+
$logger and call_user_func($logger, sprintf(__('<b>SKIPPED</b>: Previously imported record found for `%s`', 'pmxi_plugin'), $articleData['post_title']));
|
315 |
+
continue;
|
316 |
+
}
|
317 |
+
$articleData['ID'] = $postRecord->post_id;
|
318 |
+
// preserve date of already existing article when duplicate is found
|
319 |
+
$articleData['post_date'] = $post_to_update->post_date;
|
320 |
+
$articleData['post_date_gmt'] = $post_to_update->post_date_gmt;
|
321 |
+
if ($this->options['is_keep_categories']) { // preserve categories and tags of already existing article if corresponding setting is specified
|
322 |
+
$cats_list = get_the_category($articleData['ID']);
|
323 |
+
if (is_wp_error($cats_list)) {
|
324 |
+
$logger and call_user_func($logger, sprintf(__('<b>WARNING</b>: Unable to get current categories for article #%d, updating with those read from XML file', 'pmxi_plugin'), $articleData['ID']));
|
325 |
+
} else {
|
326 |
+
$cats_new = array();
|
327 |
+
foreach ($cats_list as $c) {
|
328 |
+
$cats_new[] = $c->cat_ID;
|
329 |
+
}
|
330 |
+
$articleData['post_category'] = $cats_new;
|
331 |
+
}
|
332 |
+
|
333 |
+
$tags_list = get_the_tags($articleData['ID']);
|
334 |
+
if (is_wp_error($tags_list)) {
|
335 |
+
$logger and call_user_func($logger, sprintf(__('<b>WARNING</b>: Unable to get current tags for article #%d, updating with those read from XML file', 'pmxi_plugin'), $articleData['ID']));
|
336 |
+
} else {
|
337 |
+
$tags_new = array();
|
338 |
+
if ($tags_list) foreach ($tags_list as $t) {
|
339 |
+
$tags_new[] = $t->name;
|
340 |
+
}
|
341 |
+
$articleData['tags_input'] = implode(', ', $tags_new);
|
342 |
+
}
|
343 |
+
|
344 |
+
foreach (array_keys($taxonomies) as $tx_name) {
|
345 |
+
$txes_list = get_the_terms($articleData['ID'], $tx_name);
|
346 |
+
if (is_wp_error($txes_list)) {
|
347 |
+
$logger and call_user_func($logger, sprintf(__('<b>WARNING</b>: Unable to get current taxonomies for article #%d, updating with those read from XML file', 'pmxi_plugin'), $articleData['ID']));
|
348 |
+
} else {
|
349 |
+
$txes_new = array();
|
350 |
+
foreach ($txes_list as $t) {
|
351 |
+
$txes_new[] = $t->name;
|
352 |
+
}
|
353 |
+
$taxonomies[$tx_name][$i] = $txes_new;
|
354 |
+
}
|
355 |
+
}
|
356 |
+
}
|
357 |
+
if ($this->options['is_keep_status']) { // preserve status and trashed flag
|
358 |
+
$articleData['post_status'] = $post_to_update->post_status;
|
359 |
+
}
|
360 |
+
} else { // existing post not found though it's track was found... clear the leftover, plugin will continue to treat record as new
|
361 |
+
$postRecord->delete();
|
362 |
+
}
|
363 |
+
}
|
364 |
+
|
365 |
+
if ( ! empty($articleData['ID'])) { // handle obsolete attachments (i.e. delete or keep) according to import settings
|
366 |
+
empty($this->options['is_keep_attachments']) and wp_delete_attachments($articleData['ID']);
|
367 |
+
}
|
368 |
+
// cloak urls with `WP Wizard Cloak` if corresponding option is set
|
369 |
+
if ( ! empty($this->options['is_cloak']) and class_exists('PMLC_Plugin')) {
|
370 |
+
if (preg_match_all('%<a\s[^>]*href=(?(?=")"([^"]*)"|(?(?=\')\'([^\']*)\'|([^\s>]*)))%is', $articleData['post_content'], $matches, PREG_PATTERN_ORDER)) {
|
371 |
+
$hrefs = array_unique(array_merge(array_filter($matches[1]), array_filter($matches[2]), array_filter($matches[3])));
|
372 |
+
foreach ($hrefs as $url) {
|
373 |
+
if (preg_match('%^\w+://%i', $url)) { // mask only links having protocol
|
374 |
+
// try to find matching cloaked link among already registered ones
|
375 |
+
$list = new PMLC_Link_List(); $linkTable = $list->getTable();
|
376 |
+
$rule = new PMLC_Rule_Record(); $ruleTable = $rule->getTable();
|
377 |
+
$dest = new PMLC_Destination_Record(); $destTable = $dest->getTable();
|
378 |
+
$list->join($ruleTable, "$ruleTable.link_id = $linkTable.id")
|
379 |
+
->join($destTable, "$destTable.rule_id = $ruleTable.id")
|
380 |
+
->setColumns("$linkTable.*")
|
381 |
+
->getBy(array(
|
382 |
+
"$linkTable.destination_type =" => 'ONE_SET',
|
383 |
+
"$linkTable.is_trashed =" => 0,
|
384 |
+
"$linkTable.preset =" => '',
|
385 |
+
"$linkTable.expire_on =" => '0000-00-00',
|
386 |
+
"$ruleTable.type =" => 'ONE_SET',
|
387 |
+
"$destTable.weight =" => 100,
|
388 |
+
"$destTable.url LIKE" => $url,
|
389 |
+
), NULL, 1, 1)->convertRecords();
|
390 |
+
if ($list->count()) { // matching link found
|
391 |
+
$link = $list[0];
|
392 |
+
} else { // register new cloaked link
|
393 |
+
global $wpdb;
|
394 |
+
$slug = max(
|
395 |
+
intval($wpdb->get_var("SELECT MAX(CONVERT(name, SIGNED)) FROM $linkTable")),
|
396 |
+
intval($wpdb->get_var("SELECT MAX(CONVERT(slug, SIGNED)) FROM $linkTable")),
|
397 |
+
0
|
398 |
+
);
|
399 |
+
$i = 0; do {
|
400 |
+
is_int(++$slug) and $slug > 0 or $slug = 1;
|
401 |
+
$is_slug_found = ! intval($wpdb->get_var("SELECT COUNT(*) FROM $linkTable WHERE name = '$slug' OR slug = '$slug'"));
|
402 |
+
} while( ! $is_slug_found and $i++ < 100000);
|
403 |
+
if ($is_slug_found) {
|
404 |
+
$link = new PMLC_Link_Record(array(
|
405 |
+
'name' => strval($slug),
|
406 |
+
'slug' => strval($slug),
|
407 |
+
'header_tracking_code' => '',
|
408 |
+
'footer_tracking_code' => '',
|
409 |
+
'redirect_type' => '301',
|
410 |
+
'destination_type' => 'ONE_SET',
|
411 |
+
'preset' => '',
|
412 |
+
'forward_url_params' => 1,
|
413 |
+
'no_global_tracking_code' => 0,
|
414 |
+
'expire_on' => '0000-00-00',
|
415 |
+
'created_on' => date('Y-m-d H:i:s'),
|
416 |
+
'is_trashed' => 0,
|
417 |
+
));
|
418 |
+
$link->insert();
|
419 |
+
$rule = new PMLC_Rule_Record(array(
|
420 |
+
'link_id' => $link->id,
|
421 |
+
'type' => 'ONE_SET',
|
422 |
+
'rule' => '',
|
423 |
+
));
|
424 |
+
$rule->insert();
|
425 |
+
$dest = new PMLC_Destination_Record(array(
|
426 |
+
'rule_id' => $rule->id,
|
427 |
+
'url' => $url,
|
428 |
+
'weight' => 100,
|
429 |
+
));
|
430 |
+
$dest->insert();
|
431 |
+
} else {
|
432 |
+
$logger and call_user_func($logger, sprintf(__('<b>ERROR</b>: Unable to create cloaked link for %s', 'pmxi_plugin'), $url));
|
433 |
+
$link = NULL;
|
434 |
+
}
|
435 |
+
}
|
436 |
+
if ($link) { // cloaked link is found or created for url
|
437 |
+
$articleData['post_content'] = preg_replace('%' . preg_quote($url, '%') . '(?=([\s\'"]|$))%i', $link->getUrl(), $articleData['post_content']);
|
438 |
+
}
|
439 |
+
}
|
440 |
+
}
|
441 |
+
}
|
442 |
+
}
|
443 |
+
// insert article being imported
|
444 |
+
$pid = wp_insert_post($articleData, true);
|
445 |
+
if (is_wp_error($pid)) {
|
446 |
+
$logger and call_user_func($logger, __('<b>ERROR</b>', 'pmxi_plugin') . ': ' . $pid->get_error_message());
|
447 |
+
} else {
|
448 |
+
$current_post_ids[] = $pid;
|
449 |
+
// associate post with import
|
450 |
+
$postRecord->isEmpty() and $postRecord->set(array(
|
451 |
+
'post_id' => $pid,
|
452 |
+
'import_id' => $this->id,
|
453 |
+
'unique_key' => $unique_keys[$i],
|
454 |
+
))->insert();
|
455 |
+
|
456 |
+
// [custom taxonomies]
|
457 |
+
foreach ($taxonomies as $tx_name => $txes) {
|
458 |
+
$term_ids = wp_set_object_terms($pid, $txes[$i], $tx_name);
|
459 |
+
if (is_wp_error($term_ids)) {
|
460 |
+
$logger and call_user_func($logger, __('<b>ERROR</b>', 'pmxi_plugin') . ': '.$term_ids->get_error_message());
|
461 |
+
}
|
462 |
+
}
|
463 |
+
// [/custom taxonomies]
|
464 |
+
|
465 |
+
if (empty($articleData['ID'])) {
|
466 |
+
$logger and call_user_func($logger, sprintf(__('`%s` post created successfully', 'pmxi_plugin'), $articleData['post_title']));
|
467 |
+
} else {
|
468 |
+
$logger and call_user_func($logger, sprintf(__('`%s` post updated successfully', 'pmxi_plugin'), $articleData['post_title']));
|
469 |
+
}
|
470 |
+
}
|
471 |
+
wp_cache_flush();
|
472 |
+
}
|
473 |
+
if ( ! empty($this->options['is_delete_missing'])) { // delete posts which are not in current import set
|
474 |
+
$logger and call_user_func($logger, 'Removing previously imported posts which are no longer actual...');
|
475 |
+
$postList = new PMXI_Post_List();
|
476 |
+
foreach ($postList->getBy(array('import_id' => $this->id, 'post_id NOT IN' => $current_post_ids)) as $missingPost) {
|
477 |
+
empty($this->options['is_keep_attachments']) and wp_delete_attachments($missingPost['post_id']);
|
478 |
+
wp_delete_post($missingPost['post_id'], true);
|
479 |
+
}
|
480 |
+
}
|
481 |
+
|
482 |
+
} catch (XmlImportException $e) {
|
483 |
+
$logger and call_user_func($logger, __('<b>ERROR</b>', 'pmxi_plugin') . ': ' . $e->getMessage());
|
484 |
+
}
|
485 |
+
$last_chank and $this->set('registered_on', date('Y-m-d H:i:s'))->save(); // specify execution is successful
|
486 |
+
|
487 |
+
$last_chank and $logger and call_user_func($logger, __('Cleaning temporary data...', 'pmxi_plugin'));
|
488 |
+
foreach ($tmp_files as $file) { // remove all temporary files created
|
489 |
+
unlink($file);
|
490 |
+
}
|
491 |
+
|
492 |
+
if ($this->options['is_delete_source'] and $last_chank) {
|
493 |
+
$logger and call_user_func($logger, __('Deleting source XML file...', 'pmxi_plugin'));
|
494 |
+
if ( ! @unlink($this->path)) {
|
495 |
+
$logger and call_user_func($logger, sprintf(__('<b>WARNING</b>: Unable to remove %s', 'pmxi_plugin'), $this->path));
|
496 |
+
}
|
497 |
+
}
|
498 |
+
$logger and $last_chank and call_user_func($logger, 'Done');
|
499 |
+
|
500 |
+
remove_filter('user_has_cap', array($this, '_filter_has_cap_unfiltered_html')); kses_init(); // return any filtering rules back if they has been disabled for import procedure
|
501 |
+
|
502 |
+
return $this;
|
503 |
+
}
|
504 |
+
|
505 |
+
public function _filter_has_cap_unfiltered_html($caps)
|
506 |
+
{
|
507 |
+
$caps['unfiltered_html'] = true;
|
508 |
+
return $caps;
|
509 |
+
}
|
510 |
+
|
511 |
+
/**
|
512 |
+
* Find duplicates according to settings
|
513 |
+
*/
|
514 |
+
public function findDuplicates($articleData)
|
515 |
+
{
|
516 |
+
$field = 'post_' . $this->options['duplicate_indicator']; // post_title or post_content
|
517 |
+
return $this->wpdb->get_col($this->wpdb->prepare("
|
518 |
+
SELECT ID FROM " . $this->wpdb->posts . "
|
519 |
+
WHERE
|
520 |
+
post_type = %s
|
521 |
+
AND ID != %s
|
522 |
+
AND REPLACE(REPLACE(REPLACE($field, ' ', ''), '\\t', ''), '\\n', '') = %s
|
523 |
+
",
|
524 |
+
$articleData['post_type'],
|
525 |
+
isset($articleData['ID']) ? $articleData['ID'] : 0,
|
526 |
+
preg_replace('%[ \\t\\n]%', '', $articleData[$field])
|
527 |
+
));
|
528 |
+
}
|
529 |
+
|
530 |
+
/**
|
531 |
+
* Clear associations with posts
|
532 |
+
* @param bool[optional] $keepPosts When set to false associated wordpress posts will be deleted as well
|
533 |
+
* @return PMXI_Import_Record
|
534 |
+
* @chainable
|
535 |
+
*/
|
536 |
+
public function deletePosts($keepPosts = TRUE) {
|
537 |
+
$post = new PMXI_Post_List();
|
538 |
+
if ($keepPosts) {
|
539 |
+
$this->wpdb->query($this->wpdb->prepare('DELETE FROM ' . $post->getTable() . ' WHERE import_id = %s', $this->id));
|
540 |
+
} else {
|
541 |
+
foreach ($post->getBy('import_id', $this->id)->convertRecords() as $p) {
|
542 |
+
empty($this->options['is_keep_attachments']) and wp_delete_attachments($p->post_id);
|
543 |
+
wp_delete_post($p->post_id, TRUE);
|
544 |
+
}
|
545 |
+
}
|
546 |
+
return $this;
|
547 |
+
}
|
548 |
+
/**
|
549 |
+
* Delete associated files
|
550 |
+
* @return PMXI_Import_Record
|
551 |
+
* @chainable
|
552 |
+
*/
|
553 |
+
public function deleteFiles() {
|
554 |
+
$fileList = new PMXI_File_List();
|
555 |
+
foreach($fileList->getBy('import_id', $this->id)->convertRecords() as $f) {
|
556 |
+
$f->delete();
|
557 |
+
}
|
558 |
+
return $this;
|
559 |
+
}
|
560 |
+
|
561 |
+
/**
|
562 |
+
* @see parent::delete()
|
563 |
+
* @param bool[optional] $keepPosts When set to false associated wordpress posts will be deleted as well
|
564 |
+
*/
|
565 |
+
public function delete($keepPosts = TRUE) {
|
566 |
+
$this->deletePosts($keepPosts)->deleteFiles();
|
567 |
+
|
568 |
+
return parent::delete();
|
569 |
+
}
|
570 |
+
|
571 |
+
}
|
572 |
+
|
573 |
+
/**
|
574 |
+
* Cron schedule parser
|
575 |
+
*/
|
576 |
+
class _PMXI_Import_Record_Cron_Parser
|
577 |
+
{
|
578 |
+
/**
|
579 |
+
* @var array Cron parts
|
580 |
+
*/
|
581 |
+
private $_cronParts;
|
582 |
+
|
583 |
+
/**
|
584 |
+
* Constructor
|
585 |
+
*
|
586 |
+
* @param string $schedule Cron schedule string (e.g. '8 * * * *'). The
|
587 |
+
* schedule can handle ranges (10-12) and intervals
|
588 |
+
* (*\/10 [remove the backslash]). Schedule parts should map to
|
589 |
+
* minute [0-59], hour [0-23], day of month, month [1-12], day of week [1-7]
|
590 |
+
*
|
591 |
+
* @throws InvalidArgumentException if $schedule is not a valid cron schedule
|
592 |
+
*/
|
593 |
+
public function __construct($schedule)
|
594 |
+
{
|
595 |
+
$this->_cronParts = explode(' ', $schedule);
|
596 |
+
if (count($this->_cronParts) != 5) {
|
597 |
+
throw new Exception($schedule . ' is not a valid cron schedule string');
|
598 |
+
}
|
599 |
+
}
|
600 |
+
|
601 |
+
/**
|
602 |
+
* Check if a date/time unit value satisfies a crontab unit
|
603 |
+
*
|
604 |
+
* @param DateTime $nextRun Current next run date
|
605 |
+
* @param string $unit Date/time unit type (e.g. Y, m, d, H, i)
|
606 |
+
* @param string $schedule Cron schedule variable
|
607 |
+
*
|
608 |
+
* @return bool Returns TRUE if the unit satisfies the constraint
|
609 |
+
*/
|
610 |
+
public function unitSatisfiesCron(DateTime $nextRun, $unit, $schedule)
|
611 |
+
{
|
612 |
+
$unitValue = (int)$nextRun->format($unit);
|
613 |
+
|
614 |
+
if ($schedule == '*') {
|
615 |
+
return true;
|
616 |
+
} if (strpos($schedule, '-')) {
|
617 |
+
list($first, $last) = explode('-', $schedule);
|
618 |
+
return $unitValue >= $first && $unitValue <= $last;
|
619 |
+
} else if (strpos($schedule, '*/') !== false) {
|
620 |
+
list($delimiter, $interval) = explode('*/', $schedule);
|
621 |
+
return $unitValue % (int)$interval == 0;
|
622 |
+
} else {
|
623 |
+
return $unitValue == (int)$schedule;
|
624 |
+
}
|
625 |
+
}
|
626 |
+
|
627 |
+
/**
|
628 |
+
* Get the date in which the cron will run next
|
629 |
+
*
|
630 |
+
* @param string|DateTime (optional) $fromTime Set the relative start time
|
631 |
+
*
|
632 |
+
* @return DateTime
|
633 |
+
*/
|
634 |
+
public function getNextRunDate($fromTime = 'now')
|
635 |
+
{
|
636 |
+
$nextRun = ($fromTime instanceof DateTime) ? $fromTime : new DateTime($fromTime);
|
637 |
+
$nextRun->setTime($nextRun->format('H'), $nextRun->format('i'), 0);
|
638 |
+
$nextRun->modify('+1 minute'); // make sure we don't return the very date is submitted to the function
|
639 |
+
$nextRunLimit = clone $nextRun; $nextRunLimit->modify('+1 year');
|
640 |
+
|
641 |
+
while ($nextRun < $nextRunLimit) { // Set a hard limit to bail on an impossible date
|
642 |
+
|
643 |
+
// Adjust the month until it matches. Reset day to 1 and reset time.
|
644 |
+
if ( ! $this->unitSatisfiesCron($nextRun, 'm', $this->getSchedule('month'))) {
|
645 |
+
$nextRun->modify('+1 month');
|
646 |
+
$nextRun->setDate($nextRun->format('Y'), $nextRun->format('m'), 1);
|
647 |
+
$nextRun->setTime(0, 0, 0);
|
648 |
+
continue;
|
649 |
+
}
|
650 |
+
|
651 |
+
// Adjust the day of the month by incrementing the day until it matches. Reset time.
|
652 |
+
if ( ! $this->unitSatisfiesCron($nextRun, 'd', $this->getSchedule('day_of_month'))) {
|
653 |
+
$nextRun->modify('+1 day');
|
654 |
+
$nextRun->setTime(0, 0, 0);
|
655 |
+
continue;
|
656 |
+
}
|
657 |
+
|
658 |
+
// Adjust the day of week by incrementing the day until it matches. Resest time.
|
659 |
+
if ( ! $this->unitSatisfiesCron($nextRun, 'N', $this->getSchedule('day_of_week'))) {
|
660 |
+
$nextRun->modify('+1 day');
|
661 |
+
$nextRun->setTime(0, 0, 0);
|
662 |
+
continue;
|
663 |
+
}
|
664 |
+
|
665 |
+
// Adjust the hour until it matches the set hour. Set seconds and minutes to 0
|
666 |
+
if ( ! $this->unitSatisfiesCron($nextRun, 'H', $this->getSchedule('hour'))) {
|
667 |
+
$nextRun->modify('+1 hour');
|
668 |
+
$nextRun->setTime($nextRun->format('H'), 0, 0);
|
669 |
+
continue;
|
670 |
+
}
|
671 |
+
|
672 |
+
// Adjust the minutes until it matches a set minute
|
673 |
+
if ( ! $this->unitSatisfiesCron($nextRun, 'i', $this->getSchedule('minute'))) {
|
674 |
+
$nextRun->modify('+1 minute');
|
675 |
+
continue;
|
676 |
+
}
|
677 |
+
|
678 |
+
break;
|
679 |
+
}
|
680 |
+
|
681 |
+
return $nextRun;
|
682 |
+
}
|
683 |
+
|
684 |
+
/**
|
685 |
+
* Get all or part of the cron schedule string
|
686 |
+
*
|
687 |
+
* @param string $part Specify the part to retrieve or NULL to get the full
|
688 |
+
* cron schedule string. $part can be the PHP date() part of a date
|
689 |
+
* formatted string or one of the following values:
|
690 |
+
* NULL, 'minute', 'hour', 'month', 'day_of_week', 'day_of_month'
|
691 |
+
*
|
692 |
+
* @return string
|
693 |
+
*/
|
694 |
+
public function getSchedule($part = null)
|
695 |
+
{
|
696 |
+
switch ($part) {
|
697 |
+
case 'minute': case 'i':
|
698 |
+
return $this->_cronParts[0];
|
699 |
+
case 'hour': case 'H':
|
700 |
+
return $this->_cronParts[1];
|
701 |
+
case 'day_of_month': case 'd':
|
702 |
+
return $this->_cronParts[2];
|
703 |
+
case 'month': case 'm':
|
704 |
+
return $this->_cronParts[3];
|
705 |
+
case 'day_of_week': case 'N':
|
706 |
+
return $this->_cronParts[4];
|
707 |
+
default:
|
708 |
+
return implode(' ', $this->_cronParts);
|
709 |
+
}
|
710 |
+
}
|
711 |
+
|
712 |
+
/**
|
713 |
+
* Deterime if the cron is due to run based on the current time, last run
|
714 |
+
* time, and the next run time.
|
715 |
+
*
|
716 |
+
* If the relative next run time based on the last run time is not equal to
|
717 |
+
* the next suggested run time based on the current time, then the cron
|
718 |
+
* needs to run.
|
719 |
+
*
|
720 |
+
* @param string|DateTime $lastRun (optional) Date the cron was last run.
|
721 |
+
*
|
722 |
+
* @return bool Returns TRUE if the cron is due to run or FALSE if not
|
723 |
+
*/
|
724 |
+
public function isDue($lastRun = 'now')
|
725 |
+
{
|
726 |
+
return $this->getNextRunDate($lastRun) < $this->getNextRunDate();
|
727 |
+
}
|
728 |
+
}
|
models/model.php
ADDED
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Base class for models
|
4 |
+
*
|
5 |
+
* @author Pavel Kulbakin <p.kulbakin@gmail.com>
|
6 |
+
*/
|
7 |
+
abstract class PMXI_Model extends ArrayObject {
|
8 |
+
/**
|
9 |
+
* WPDB instance
|
10 |
+
* @var wpdb
|
11 |
+
*/
|
12 |
+
protected $wpdb;
|
13 |
+
/**
|
14 |
+
* Table name the model is linked to
|
15 |
+
* @var string
|
16 |
+
*/
|
17 |
+
protected $table;
|
18 |
+
/**
|
19 |
+
* Array of columns representing primary key
|
20 |
+
* @var array
|
21 |
+
*/
|
22 |
+
protected $primary = array('id');
|
23 |
+
/**
|
24 |
+
* Wether key field is auto_increment (sure make scence only if key s
|
25 |
+
* @var bool
|
26 |
+
*/
|
27 |
+
protected $auto_increment = FALSE;
|
28 |
+
|
29 |
+
/**
|
30 |
+
* Cached data retrieved from database
|
31 |
+
* @var array
|
32 |
+
*/
|
33 |
+
private static $meta_cache = array();
|
34 |
+
|
35 |
+
/**
|
36 |
+
* Initialize model
|
37 |
+
* @param array[optional] $data Array of record data to initialize object with
|
38 |
+
*/
|
39 |
+
public function __construct() {
|
40 |
+
$this->wpdb = $GLOBALS['wpdb'];
|
41 |
+
}
|
42 |
+
|
43 |
+
/**
|
44 |
+
* Read records from database by specified fields and values
|
45 |
+
* When 1st parameter is an array, it expected to be an associative array of field => value pairs to read data by
|
46 |
+
* If 2 parameters are set, first one is expected to be a field name and second - it's value
|
47 |
+
*
|
48 |
+
* @param string|array $field
|
49 |
+
* @param mixed[optional] $value
|
50 |
+
* @return PMXI_Model
|
51 |
+
*/
|
52 |
+
abstract public function getBy($field = NULL, $value = NULL);
|
53 |
+
|
54 |
+
/**
|
55 |
+
* Magic function to automatically resolve calls like $obj->getBy%FIELD_NAME%
|
56 |
+
* @param string $method
|
57 |
+
* @param array $args
|
58 |
+
* @return PMXI_Model
|
59 |
+
*/
|
60 |
+
public function __call($method, $args) {
|
61 |
+
if (preg_match('%^get_?by_?(.+)%i', $method, $mtch)) {
|
62 |
+
array_unshift($args, $mtch[1]);
|
63 |
+
return call_user_func_array(array($this, 'getBy'), $args);
|
64 |
+
} else {
|
65 |
+
throw new Exception("Requested method " . get_class($this) . "::$method doesn't exist.");
|
66 |
+
}
|
67 |
+
}
|
68 |
+
|
69 |
+
/**
|
70 |
+
* Bind model to database table
|
71 |
+
* @param string $tableName
|
72 |
+
* @return PMXI_Model
|
73 |
+
*/
|
74 |
+
public function setTable($tableName) {
|
75 |
+
if ( ! is_null($this->table)) {
|
76 |
+
throw new Exception('Table name cannot be changed once being set.');
|
77 |
+
}
|
78 |
+
$this->table = $tableName;
|
79 |
+
if ( ! isset(self::$meta_cache[$this->table])) {
|
80 |
+
$tableMeta = $this->wpdb->get_results("SHOW COLUMNS FROM $this->table", ARRAY_A);
|
81 |
+
$primary = array();
|
82 |
+
$auto_increment = false;
|
83 |
+
foreach ($tableMeta as $colMeta) {
|
84 |
+
if ('PRI' == $colMeta['Key']) {
|
85 |
+
$primary[] = $colMeta['Field'];
|
86 |
+
}
|
87 |
+
if ('auto_increment' == $colMeta['Extra']) {
|
88 |
+
$auto_increment = true;
|
89 |
+
break; // no point to iterate futher since auto_increment means corresponding primary key is simple
|
90 |
+
}
|
91 |
+
}
|
92 |
+
self::$meta_cache[$this->table] = array('primary' => $primary, 'auto_increment' => $auto_increment);
|
93 |
+
}
|
94 |
+
$this->primary = self::$meta_cache[$this->table]['primary'];
|
95 |
+
$this->auto_increment = self::$meta_cache[$this->table]['auto_increment'];
|
96 |
+
|
97 |
+
return $this;
|
98 |
+
}
|
99 |
+
|
100 |
+
/**
|
101 |
+
* Return database table name this object is bound to
|
102 |
+
* @return string
|
103 |
+
*/
|
104 |
+
public function getTable() {
|
105 |
+
return $this->table;
|
106 |
+
}
|
107 |
+
/**
|
108 |
+
* Return column name with table name
|
109 |
+
* @param string $col
|
110 |
+
* @return string
|
111 |
+
*/
|
112 |
+
public function getFieldName($col) {
|
113 |
+
return $this->table . '.' . $col;
|
114 |
+
}
|
115 |
+
|
116 |
+
/**
|
117 |
+
* Compose WHERE clause based on parameters provided
|
118 |
+
* @param string|array $field
|
119 |
+
* @param mixed[optional] $value
|
120 |
+
* @param string[optional] $operator AND or OR string, 'AND' by default
|
121 |
+
* @return string
|
122 |
+
*/
|
123 |
+
protected function buildWhere($field, $value = NULL, $operator = NULL) {
|
124 |
+
if ( ! is_array($field)) {
|
125 |
+
$field = array($field => $value);
|
126 |
+
} else { // shift arguments
|
127 |
+
$operator = $value;
|
128 |
+
}
|
129 |
+
! is_null($operator) or $operator = 'AND'; // apply default operator value
|
130 |
+
|
131 |
+
$where = array();
|
132 |
+
foreach ($field as $key => $val) {
|
133 |
+
if (is_int($key)) {
|
134 |
+
$where[] = '(' . call_user_func_array(array($this, 'buildWhere'), $val) . ')';
|
135 |
+
} else {
|
136 |
+
if ( ! preg_match('%^(.+?) *(=|<>|!=|<|>|<=|>=| (NOT +)?(IN|(LIKE|REGEXP|RLIKE)( BINARY)?))?$%i', trim($key), $mtch)) {
|
137 |
+
throw new Exception('Wrong field name format.');
|
138 |
+
}
|
139 |
+
$key = $mtch[1];
|
140 |
+
if (is_array($val) and (empty($mtch[2]) or 'IN' == strtoupper($mtch[4]))) {
|
141 |
+
$op = empty($mtch[2]) ? 'IN' : strtoupper(trim($mtch[2]));
|
142 |
+
$where[] = $this->wpdb->prepare("$key $op (" . implode(', ', array_fill(0, count($val), "%s")) . ")", $val);
|
143 |
+
} else {
|
144 |
+
$op = empty($mtch[2]) ? '=' : strtoupper(trim($mtch[2]));
|
145 |
+
$where[] = $this->wpdb->prepare("$key $op %s", $val);
|
146 |
+
}
|
147 |
+
}
|
148 |
+
}
|
149 |
+
return implode(" $operator ", $where);
|
150 |
+
}
|
151 |
+
|
152 |
+
|
153 |
+
/**
|
154 |
+
* Return associative array with record data
|
155 |
+
* @param bool[optional] $serialize Whether returned fields should be serialized
|
156 |
+
* @return array
|
157 |
+
*/
|
158 |
+
public function toArray($serialize = FALSE) {
|
159 |
+
$result = (array)$this;
|
160 |
+
if ($serialize) {
|
161 |
+
foreach ($result as $k => $v) {
|
162 |
+
if ( ! is_scalar($v)) {
|
163 |
+
$result[$k] = serialize($v);
|
164 |
+
}
|
165 |
+
}
|
166 |
+
}
|
167 |
+
return $result;
|
168 |
+
}
|
169 |
+
|
170 |
+
/**
|
171 |
+
* Check whether object data is empty
|
172 |
+
* @return bool
|
173 |
+
*/
|
174 |
+
public function isEmpty() {
|
175 |
+
return $this->count() == 0;
|
176 |
+
}
|
177 |
+
|
178 |
+
/**
|
179 |
+
* Empty object data
|
180 |
+
* @return PMXI_Model
|
181 |
+
*/
|
182 |
+
public function clear() {
|
183 |
+
$this->exchangeArray(array());
|
184 |
+
return $this;
|
185 |
+
}
|
186 |
+
|
187 |
+
/**
|
188 |
+
* Delete all content from model's table
|
189 |
+
* @return PMXI_Model
|
190 |
+
*/
|
191 |
+
public function truncateTable() {
|
192 |
+
if (FALSE !== $this->wpdb->query("TRUNCATE $this->table")) {
|
193 |
+
return $this;
|
194 |
+
} else {
|
195 |
+
throw new Exception($this->wpdb->last_error);
|
196 |
+
}
|
197 |
+
}
|
198 |
+
}
|
models/model/list.php
ADDED
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Incapsulates behavior for list of database records
|
4 |
+
*
|
5 |
+
* @author Pavel Kulbakin <p.kulbakin@gmail.com>
|
6 |
+
*/
|
7 |
+
class PMXI_Model_List extends PMXI_Model {
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Total number of records in database which correspond last getBy rule without paging
|
11 |
+
* @var int
|
12 |
+
*/
|
13 |
+
protected $total = 0;
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Joined tables
|
17 |
+
* @var array
|
18 |
+
*/
|
19 |
+
protected $joined = array();
|
20 |
+
/**
|
21 |
+
* Columns to select from database
|
22 |
+
* @var string
|
23 |
+
*/
|
24 |
+
protected $what = '*';
|
25 |
+
/**
|
26 |
+
* Sets table to use in conjuction with primary list table
|
27 |
+
* @param string $table Table to join
|
28 |
+
* @param string $on Condition to join
|
29 |
+
* @param string $type Join type (INNER, OUTER, etc)
|
30 |
+
* @return PMXI_Model_List
|
31 |
+
*/
|
32 |
+
public function join($table, $on, $type = 'INNER') {
|
33 |
+
$this->joined[] = ( ! is_null($type) ? $type . ' ' : '') . 'JOIN ' . $table . ' ON ' . $on;
|
34 |
+
return $this;
|
35 |
+
}
|
36 |
+
|
37 |
+
/**
|
38 |
+
* Set columns to be selected from database
|
39 |
+
* @param array $columns
|
40 |
+
* @return PMXI_Model_List
|
41 |
+
*/
|
42 |
+
public function setColumns($columns) {
|
43 |
+
is_array($columns) or $columns = func_get_args();
|
44 |
+
$this->what = implode(', ', $columns);
|
45 |
+
return $this;
|
46 |
+
}
|
47 |
+
|
48 |
+
/**
|
49 |
+
* Read records from database by specified fields and values
|
50 |
+
* When 1st parameter is an array, it's expected to be an associative array of field => value pairs to read data by
|
51 |
+
* When 2nd parameter is a scalar, it's expected to be a field name and second parameter - it's value
|
52 |
+
*
|
53 |
+
* @param string|array[optional] $field
|
54 |
+
* @param mixed[optional] $value
|
55 |
+
* @param string[optional] $orderBy Ordering rule
|
56 |
+
* @param int[optional] $page Paging paramter used to limit number of records returned
|
57 |
+
* @param int[optional] $perPage Page size when paging parameter is used (20 by default)
|
58 |
+
* @return PMXI_Model_List
|
59 |
+
*/
|
60 |
+
public function getBy($field = NULL, $value = NULL, $orderBy = NULL, $page = NULL, $perPage = NULL, $groupBy = NULL) {
|
61 |
+
if (is_array($field) or is_null($field)) { // when associative array is submitted, do not expect second paramter to be $value, but act as if there is no $value parameter at all
|
62 |
+
$groupBy = $perPage; $perPage = $page; $page = $orderBy; $orderBy = $value; $value = NULL;
|
63 |
+
}
|
64 |
+
! is_null($perPage) or $perPage = 20; // set default value for page length
|
65 |
+
$page = intval($page);
|
66 |
+
|
67 |
+
$sql = "FROM $this->table ";
|
68 |
+
$sql .= implode(' ', $this->joined);
|
69 |
+
if ( ! is_null($field)) {
|
70 |
+
$sql .= " WHERE " . $this->buildWhere($field, $value);
|
71 |
+
}
|
72 |
+
if ( ! is_null($groupBy)) {
|
73 |
+
$sql .= " GROUP BY $groupBy";
|
74 |
+
}
|
75 |
+
is_null($orderBy) and $orderBy = implode(', ', $this->primary); // default sort order is by primary key
|
76 |
+
$sql .= " ORDER BY $orderBy";
|
77 |
+
if ($page > 0) {
|
78 |
+
$sql = "SELECT SQL_CALC_FOUND_ROWS $this->what $sql LIMIT " . intval(($page - 1) * $perPage) . ", " . intval($perPage);
|
79 |
+
} else {
|
80 |
+
$sql = "SELECT $this->what $sql";
|
81 |
+
}
|
82 |
+
$result = $this->wpdb->get_results($sql, ARRAY_A);
|
83 |
+
if (is_array($result)) {
|
84 |
+
foreach ($result as $i => $row) {
|
85 |
+
foreach ($row as $k => $v) {
|
86 |
+
if (is_serialized($v)) {
|
87 |
+
$result[$i][$k] = unserialize($v);
|
88 |
+
}
|
89 |
+
}
|
90 |
+
}
|
91 |
+
if ($page > 0) {
|
92 |
+
$this->total = intval($this->wpdb->get_var('SELECT FOUND_ROWS()'));
|
93 |
+
} else {
|
94 |
+
$this->total = count($result);
|
95 |
+
}
|
96 |
+
$this->exchangeArray($result);
|
97 |
+
} else {
|
98 |
+
$this->total = 0;
|
99 |
+
$this->clear();
|
100 |
+
}
|
101 |
+
return $this;
|
102 |
+
}
|
103 |
+
|
104 |
+
/**
|
105 |
+
* Count records in table
|
106 |
+
* @param string|array $field
|
107 |
+
* @param mixed[optional] $value
|
108 |
+
* @return int
|
109 |
+
*/
|
110 |
+
public function countBy($field = NULL, $value = NULL) {
|
111 |
+
$sql = "SELECT COUNT(*) FROM $this->table ";
|
112 |
+
$sql .= implode(' ', $this->joined);
|
113 |
+
if ( ! is_null($field)) {
|
114 |
+
$sql .= " WHERE " . $this->buildWhere($field, $value);
|
115 |
+
}
|
116 |
+
return intval($this->wpdb->get_var($sql));
|
117 |
+
}
|
118 |
+
|
119 |
+
/**
|
120 |
+
* Method returns number of rows in database which correspond last getBy query
|
121 |
+
* @return int
|
122 |
+
*/
|
123 |
+
public function total() {
|
124 |
+
return $this->total;
|
125 |
+
}
|
126 |
+
|
127 |
+
/**
|
128 |
+
* Converts elements to instances of specifield class. If includeFields are provided only fields listed are included
|
129 |
+
* @param string[optoinal] $elementClass
|
130 |
+
* @param array[optional] $includeFields
|
131 |
+
* @return PMXI_Model_List
|
132 |
+
*/
|
133 |
+
public function convertRecords($elementClass = NULL, $includeFields = NULL) {
|
134 |
+
! is_null($elementClass) or $elementClass = preg_replace('%List$%', 'Record', get_class($this));
|
135 |
+
if ( ! is_subclass_of($elementClass, PMXI_Plugin::PREFIX . 'Model_Record')) {
|
136 |
+
throw new Exception("Provideded class name $elementClass must be a subclass of " . PMXI_Plugin::PREFIX . 'Model_Record');
|
137 |
+
}
|
138 |
+
$records = $this->exchangeArray(array());
|
139 |
+
foreach ($records as $r) {
|
140 |
+
$data = (array)$r;
|
141 |
+
if ( ! is_null($includeFields)) {
|
142 |
+
$data = array_intersect_key($data, array_flip($includeFields));
|
143 |
+
}
|
144 |
+
$this[] = new $elementClass($data);
|
145 |
+
}
|
146 |
+
return $this;
|
147 |
+
}
|
148 |
+
|
149 |
+
}
|
models/model/record.php
ADDED
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Base class for models
|
4 |
+
*
|
5 |
+
* @author Pavel Kulbakin <p.kulbakin@gmail.com>
|
6 |
+
*/
|
7 |
+
class PMXI_Model_Record extends PMXI_Model {
|
8 |
+
/**
|
9 |
+
* Initialize model
|
10 |
+
* @param array[optional] $data Array of record data to initialize object with
|
11 |
+
*/
|
12 |
+
public function __construct($data = array()) {
|
13 |
+
parent::__construct();
|
14 |
+
if (! is_array($data)) {
|
15 |
+
throw new Exception("Array expected as paramenter for " . get_class($this) . "::" . __METHOD__);
|
16 |
+
}
|
17 |
+
$data and $this->set($data);
|
18 |
+
}
|
19 |
+
|
20 |
+
/**
|
21 |
+
* @see PMXI_Model::getBy()
|
22 |
+
* @return PMXI_Model_Record
|
23 |
+
*/
|
24 |
+
public function getBy($field = NULL, $value = NULL) {
|
25 |
+
if (is_null($field)) {
|
26 |
+
throw new Exception("Field parameter is expected at " . get_class($this) . "::" . __METHOD__);
|
27 |
+
}
|
28 |
+
$sql = "SELECT * FROM $this->table WHERE " . $this->buildWhere($field, $value);
|
29 |
+
$result = $this->wpdb->get_row($sql, ARRAY_A);
|
30 |
+
if (is_array($result)) {
|
31 |
+
foreach ($result as $k => $v) {
|
32 |
+
if (is_serialized($v)) {
|
33 |
+
$result[$k] = unserialize($v);
|
34 |
+
}
|
35 |
+
}
|
36 |
+
$this->exchangeArray($result);
|
37 |
+
} else {
|
38 |
+
$this->clear();
|
39 |
+
}
|
40 |
+
return $this;
|
41 |
+
}
|
42 |
+
|
43 |
+
/**
|
44 |
+
* Ger records related to current one
|
45 |
+
* @param string $model Class name of model of related records
|
46 |
+
* @param array[optoinal] $keyAssoc
|
47 |
+
* @return PMXI_Model_List
|
48 |
+
*/
|
49 |
+
public function getRelated($model, $keyAssoc = NULL) {
|
50 |
+
$related = new $model();
|
51 |
+
if ( ! empty($this->id)) {
|
52 |
+
if (is_null($keyAssoc)) {
|
53 |
+
$defaultPrefix = strtolower(preg_replace('%^' . strtoupper(PMXI_Plugin::PREFIX) . '|_Record$%', '', get_class($this)));
|
54 |
+
$keyAssoc = array();
|
55 |
+
foreach ($this->primary as $key) {
|
56 |
+
$keyAssoc = array($defaultPrefix . '_' . $key => $key);
|
57 |
+
}
|
58 |
+
}
|
59 |
+
foreach ($keyAssoc as $foreign => $local) {
|
60 |
+
$keyAssoc[$foreign] = $this->$local;
|
61 |
+
}
|
62 |
+
$related->getBy($keyAssoc);
|
63 |
+
}
|
64 |
+
return $related instanceof PMXI_Model_List ? $related->convertRecords() : $related;
|
65 |
+
}
|
66 |
+
|
67 |
+
/**
|
68 |
+
* Saves currently set object data as database record
|
69 |
+
* @return PMXI_Model_Record
|
70 |
+
*/
|
71 |
+
public function insert() {
|
72 |
+
if ($this->wpdb->insert($this->table, $this->toArray(TRUE))) {
|
73 |
+
if (isset($this->auto_increment)) {
|
74 |
+
$this[$this->primary[0]] = $this->wpdb->insert_id;
|
75 |
+
}
|
76 |
+
return $this;
|
77 |
+
} else {
|
78 |
+
throw new Exception($this->wpdb->last_error);
|
79 |
+
}
|
80 |
+
}
|
81 |
+
/**
|
82 |
+
* Update record in database
|
83 |
+
* @return PMXI_Model_Record
|
84 |
+
*/
|
85 |
+
public function update() {
|
86 |
+
$record = $this->toArray(TRUE);
|
87 |
+
$this->wpdb->update($this->table, $record, array_intersect_key($record, array_flip($this->primary)));
|
88 |
+
if ($this->wpdb->last_error) {
|
89 |
+
throw new Exception($this->wpdb->last_error);
|
90 |
+
}
|
91 |
+
return $this;
|
92 |
+
}
|
93 |
+
|
94 |
+
/**
|
95 |
+
* Delete record form database
|
96 |
+
* @return PMXI_Model_Record
|
97 |
+
*/
|
98 |
+
public function delete() {
|
99 |
+
if ($this->wpdb->query("DELETE FROM $this->table WHERE " . $this->buildWhere(array_intersect_key($this->toArray(TRUE), array_flip($this->primary))))) {
|
100 |
+
return $this;
|
101 |
+
} else {
|
102 |
+
throw new Exception($this->wpdb->last_error);
|
103 |
+
}
|
104 |
+
}
|
105 |
+
/**
|
106 |
+
* Insert or Update the record
|
107 |
+
* WARNING: function doesn't check actual record presents in database, it simply tries to insert if no primary key specified and update otherwise
|
108 |
+
* @return PMXI_Model_Record
|
109 |
+
*/
|
110 |
+
public function save() {
|
111 |
+
if (array_intersect_key($this->toArray(TRUE), array_flip($this->primary))) {
|
112 |
+
$this->update();
|
113 |
+
} else {
|
114 |
+
$this->insert();
|
115 |
+
}
|
116 |
+
return $this;
|
117 |
+
}
|
118 |
+
|
119 |
+
/**
|
120 |
+
* Set record data
|
121 |
+
* When 1st parameter is an array, it expected to be an associative array of field => value pairs
|
122 |
+
* If 2 parameters are set, first one is expected to be a field name and second - it's value
|
123 |
+
*
|
124 |
+
* @param string|array $field
|
125 |
+
* @param mixed[optional] $value
|
126 |
+
* @return PMXI_Model_Record
|
127 |
+
*/
|
128 |
+
public function set($field, $value = NULL) {
|
129 |
+
if (is_array($field) and ( ! is_null($value) or 0 == count($field))) {
|
130 |
+
throw new Exception(__CLASS__ . "::set method expects either not empty associative array as the only paramter or field name and it's value as two seperate parameters.");
|
131 |
+
}
|
132 |
+
if (is_array($field)) {
|
133 |
+
$this->exchangeArray(array_merge($this->toArray(), $field));
|
134 |
+
} else {
|
135 |
+
$this[$field] = $value;
|
136 |
+
}
|
137 |
+
|
138 |
+
return $this;
|
139 |
+
}
|
140 |
+
|
141 |
+
/**
|
142 |
+
* Magic method to resolved object-like request to record values in format $obj->%FIELD_NAME%
|
143 |
+
* @param string $field
|
144 |
+
* @return mixed
|
145 |
+
*/
|
146 |
+
public function __get($field) {
|
147 |
+
if ( ! $this->offsetExists($field)) {
|
148 |
+
throw new Exception("Undefined field $field.");
|
149 |
+
}
|
150 |
+
return $this[$field];
|
151 |
+
}
|
152 |
+
/**
|
153 |
+
* Magic method to assign values to record fields in format $obj->%FIELD_NAME = value
|
154 |
+
* @param string $field
|
155 |
+
* @param mixed $value
|
156 |
+
*/
|
157 |
+
public function __set($field, $value) {
|
158 |
+
$this[$field] = $value;
|
159 |
+
}
|
160 |
+
/**
|
161 |
+
* Magic method to check wether some record fields are set
|
162 |
+
* @param string $field
|
163 |
+
* @return bool
|
164 |
+
*/
|
165 |
+
public function __isset($field) {
|
166 |
+
return $this->offsetExists($field);
|
167 |
+
}
|
168 |
+
/**
|
169 |
+
* Magic method to unset record fields
|
170 |
+
* @param string $field
|
171 |
+
*/
|
172 |
+
public function __unset($field) {
|
173 |
+
$this->offsetUnset($field);
|
174 |
+
}
|
175 |
+
|
176 |
+
}
|
models/post/list.php
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class PMXI_Post_List extends PMXI_Model_List {
|
4 |
+
protected $primary = array('post_id');
|
5 |
+
|
6 |
+
public function __construct() {
|
7 |
+
parent::__construct();
|
8 |
+
$this->setTable(PMXI_Plugin::getInstance()->getTablePrefix() . 'posts');
|
9 |
+
}
|
10 |
+
}
|
models/post/record.php
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class PMXI_Post_Record extends PMXI_Model_Record {
|
4 |
+
protected $primary = array('post_id');
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Initialize model instance
|
8 |
+
* @param array[optional] $data Array of record data to initialize object with
|
9 |
+
*/
|
10 |
+
public function __construct($data = array()) {
|
11 |
+
parent::__construct($data);
|
12 |
+
$this->setTable(PMXI_Plugin::getInstance()->getTablePrefix() . 'posts');
|
13 |
+
}
|
14 |
+
|
15 |
+
}
|
models/template/list.php
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class PMXI_Template_List extends PMXI_Model_List {
|
4 |
+
public function __construct() {
|
5 |
+
parent::__construct();
|
6 |
+
$this->setTable(PMXI_Plugin::getInstance()->getTablePrefix() . 'templates');
|
7 |
+
}
|
8 |
+
}
|
models/template/record.php
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class PMXI_Template_Record extends PMXI_Model_Record {
|
4 |
+
/**
|
5 |
+
* Initialize model instance
|
6 |
+
* @param array[optional] $data Array of record data to initialize object with
|
7 |
+
*/
|
8 |
+
public function __construct($data = array()) {
|
9 |
+
parent::__construct($data);
|
10 |
+
$this->setTable(PMXI_Plugin::getInstance()->getTablePrefix() . 'templates');
|
11 |
+
}
|
12 |
+
|
13 |
+
}
|
plugin.php
ADDED
@@ -0,0 +1,479 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/*
|
3 |
+
Plugin Name: WP All Import
|
4 |
+
Plugin URI: http://www.wpallimport.com/upgrade-to-pro
|
5 |
+
Description: The most powerful solution for importing XML and CSV files to WordPress. Create Posts and Pages with content from any XML or CSV file. Perform scheduled updates and overwrite of existing import jobs. Free lite edition.
|
6 |
+
Version: 2.12
|
7 |
+
Author: Soflyy
|
8 |
+
*/
|
9 |
+
/**
|
10 |
+
* Plugin root dir with forward slashes as directory separator regardless of actuall DIRECTORY_SEPARATOR value
|
11 |
+
* @var string
|
12 |
+
*/
|
13 |
+
define('PMXI_ROOT_DIR', str_replace('\\', '/', dirname(__FILE__)));
|
14 |
+
/**
|
15 |
+
* Plugin root url for referencing static content
|
16 |
+
* @var string
|
17 |
+
*/
|
18 |
+
define('PMXI_ROOT_URL', rtrim(plugin_dir_url(__FILE__), '/'));
|
19 |
+
/**
|
20 |
+
* Plugin prefix for making names unique (be aware that this variable is used in conjuction with naming convention,
|
21 |
+
* i.e. in order to change it one must not only modify this constant but also rename all constants, classes and functions which
|
22 |
+
* names composed using this prefix)
|
23 |
+
* @var string
|
24 |
+
*/
|
25 |
+
define('PMXI_PREFIX', 'pmxi_');
|
26 |
+
|
27 |
+
/**
|
28 |
+
* Main plugin file, Introduces MVC pattern
|
29 |
+
*
|
30 |
+
* @singletone
|
31 |
+
* @author Pavel Kulbakin <p.kulbakin@gmail.com>
|
32 |
+
*/
|
33 |
+
final class PMXI_Plugin {
|
34 |
+
/**
|
35 |
+
* Singletone instance
|
36 |
+
* @var PMXI_Plugin
|
37 |
+
*/
|
38 |
+
protected static $instance;
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Plugin options
|
42 |
+
* @var array
|
43 |
+
*/
|
44 |
+
protected $options = array();
|
45 |
+
|
46 |
+
/**
|
47 |
+
* Plugin root dir
|
48 |
+
* @var string
|
49 |
+
*/
|
50 |
+
const ROOT_DIR = PMXI_ROOT_DIR;
|
51 |
+
/**
|
52 |
+
* Plugin root URL
|
53 |
+
* @var string
|
54 |
+
*/
|
55 |
+
const ROOT_URL = PMXI_ROOT_URL;
|
56 |
+
/**
|
57 |
+
* Prefix used for names of shortcodes, action handlers, filter functions etc.
|
58 |
+
* @var string
|
59 |
+
*/
|
60 |
+
const PREFIX = PMXI_PREFIX;
|
61 |
+
/**
|
62 |
+
* Plugin file path
|
63 |
+
* @var string
|
64 |
+
*/
|
65 |
+
const FILE = __FILE__;
|
66 |
+
|
67 |
+
/**
|
68 |
+
* Return singletone instance
|
69 |
+
* @return PMXI_Plugin
|
70 |
+
*/
|
71 |
+
static public function getInstance() {
|
72 |
+
if (self::$instance == NULL) {
|
73 |
+
self::$instance = new self();
|
74 |
+
}
|
75 |
+
return self::$instance;
|
76 |
+
}
|
77 |
+
|
78 |
+
/**
|
79 |
+
* Common logic for requestin plugin info fields
|
80 |
+
*/
|
81 |
+
public function __call($method, $args) {
|
82 |
+
if (preg_match('%^get(.+)%i', $method, $mtch)) {
|
83 |
+
$info = get_plugin_data(self::FILE);
|
84 |
+
if (isset($info[$mtch[1]])) {
|
85 |
+
return $info[$mtch[1]];
|
86 |
+
}
|
87 |
+
}
|
88 |
+
throw new Exception("Requested method " . get_class($this) . "::$method doesn't exist.");
|
89 |
+
}
|
90 |
+
|
91 |
+
/**
|
92 |
+
* Get path to plagin dir relative to wordpress root
|
93 |
+
* @param bool[optional] $noForwardSlash Whether path should be returned withot forwarding slash
|
94 |
+
* @return string
|
95 |
+
*/
|
96 |
+
public function getRelativePath($noForwardSlash = false) {
|
97 |
+
$wp_root = str_replace('\\', '/', ABSPATH);
|
98 |
+
return ($noForwardSlash ? '' : '/') . str_replace($wp_root, '', self::ROOT_DIR);
|
99 |
+
}
|
100 |
+
|
101 |
+
/**
|
102 |
+
* Check whether plugin is activated as network one
|
103 |
+
* @return bool
|
104 |
+
*/
|
105 |
+
public function isNetwork() {
|
106 |
+
if ( !is_multisite() )
|
107 |
+
return false;
|
108 |
+
|
109 |
+
$plugins = get_site_option('active_sitewide_plugins');
|
110 |
+
if (isset($plugins[plugin_basename(self::FILE)]))
|
111 |
+
return true;
|
112 |
+
|
113 |
+
return false;
|
114 |
+
}
|
115 |
+
|
116 |
+
/**
|
117 |
+
* Check whether permalinks is enabled
|
118 |
+
* @return bool
|
119 |
+
*/
|
120 |
+
public function isPermalinks() {
|
121 |
+
global $wp_rewrite;
|
122 |
+
|
123 |
+
return $wp_rewrite->using_permalinks();
|
124 |
+
}
|
125 |
+
|
126 |
+
/**
|
127 |
+
* Return prefix for plugin database tables
|
128 |
+
* @return string
|
129 |
+
*/
|
130 |
+
public function getTablePrefix() {
|
131 |
+
global $wpdb;
|
132 |
+
return ($this->isNetwork() ? $wpdb->base_prefix : $wpdb->prefix) . self::PREFIX;
|
133 |
+
}
|
134 |
+
|
135 |
+
/**
|
136 |
+
* Class constructor containing dispatching logic
|
137 |
+
* @param string $rootDir Plugin root dir
|
138 |
+
* @param string $pluginFilePath Plugin main file
|
139 |
+
*/
|
140 |
+
protected function __construct() {
|
141 |
+
// regirster autoloading method
|
142 |
+
if (function_exists('__autoload') and ! in_array('__autoload', spl_autoload_functions())) { // make sure old way of autoloading classes is not broken
|
143 |
+
spl_autoload_register('__autoload');
|
144 |
+
}
|
145 |
+
spl_autoload_register(array($this, '__autoload'));
|
146 |
+
|
147 |
+
// register helpers
|
148 |
+
if (is_dir(self::ROOT_DIR . '/helpers')) foreach (PMXI_Helper::safe_glob(self::ROOT_DIR . '/helpers/*.php', PMXI_Helper::GLOB_RECURSE | PMXI_Helper::GLOB_PATH) as $filePath) {
|
149 |
+
require_once $filePath;
|
150 |
+
}
|
151 |
+
|
152 |
+
// init plugin options
|
153 |
+
$option_name = get_class($this) . '_Options';
|
154 |
+
$options_default = PMXI_Config::createFromFile(self::ROOT_DIR . '/config/options.php')->toArray();
|
155 |
+
$this->options = array_intersect_key(get_option($option_name, array()), $options_default) + $options_default;
|
156 |
+
$this->options = array_intersect_key($options_default, array_flip(array('info_api_url'))) + $this->options; // make sure hidden options apply upon plugin reactivation
|
157 |
+
update_option($option_name, $this->options);
|
158 |
+
$this->options = get_option(get_class($this) . '_Options');
|
159 |
+
|
160 |
+
register_activation_hook(self::FILE, array($this, '__activation'));
|
161 |
+
|
162 |
+
// register action handlers
|
163 |
+
if (is_dir(self::ROOT_DIR . '/actions')) if (is_dir(self::ROOT_DIR . '/actions')) foreach (PMXI_Helper::safe_glob(self::ROOT_DIR . '/actions/*.php', PMXI_Helper::GLOB_RECURSE | PMXI_Helper::GLOB_PATH) as $filePath) {
|
164 |
+
require_once $filePath;
|
165 |
+
$function = $actionName = basename($filePath, '.php');
|
166 |
+
if (preg_match('%^(.+?)[_-](\d+)$%', $actionName, $m)) {
|
167 |
+
$actionName = $m[1];
|
168 |
+
$priority = intval($m[2]);
|
169 |
+
} else {
|
170 |
+
$priority = 10;
|
171 |
+
}
|
172 |
+
add_action($actionName, self::PREFIX . str_replace('-', '_', $function), $priority, 99); // since we don't know at this point how many parameters each plugin expects, we make sure they will be provided with all of them (it's unlikely any developer will specify more than 99 parameters in a function)
|
173 |
+
}
|
174 |
+
|
175 |
+
// register filter handlers
|
176 |
+
if (is_dir(self::ROOT_DIR . '/filters')) foreach (PMXI_Helper::safe_glob(self::ROOT_DIR . '/filters/*.php', PMXI_Helper::GLOB_RECURSE | PMXI_Helper::GLOB_PATH) as $filePath) {
|
177 |
+
require_once $filePath;
|
178 |
+
$function = $actionName = basename($filePath, '.php');
|
179 |
+
if (preg_match('%^(.+?)[_-](\d+)$%', $actionName, $m)) {
|
180 |
+
$actionName = $m[1];
|
181 |
+
$priority = intval($m[2]);
|
182 |
+
} else {
|
183 |
+
$priority = 10;
|
184 |
+
}
|
185 |
+
add_filter($actionName, self::PREFIX . str_replace('-', '_', $function), $priority, 99); // since we don't know at this point how many parameters each plugin expects, we make sure they will be provided with all of them (it's unlikely any developer will specify more than 99 parameters in a function)
|
186 |
+
}
|
187 |
+
|
188 |
+
// register shortcodes handlers
|
189 |
+
if (is_dir(self::ROOT_DIR . '/shortcodes')) foreach (PMXI_Helper::safe_glob(self::ROOT_DIR . '/shortcodes/*.php', PMXI_Helper::GLOB_RECURSE | PMXI_Helper::GLOB_PATH) as $filePath) {
|
190 |
+
$tag = strtolower(str_replace('/', '_', preg_replace('%^' . preg_quote(self::ROOT_DIR . '/shortcodes/', '%') . '|\.php$%', '', $filePath)));
|
191 |
+
add_shortcode($tag, array($this, 'shortcodeDispatcher'));
|
192 |
+
}
|
193 |
+
|
194 |
+
// register admin page pre-dispatcher
|
195 |
+
add_action('admin_init', array($this, '__adminInit'));
|
196 |
+
|
197 |
+
}
|
198 |
+
|
199 |
+
/**
|
200 |
+
* pre-dispatching logic for admin page controllers
|
201 |
+
*/
|
202 |
+
public function __adminInit() {
|
203 |
+
$input = new PMXI_Input();
|
204 |
+
$page = strtolower($input->getpost('page', ''));
|
205 |
+
if (preg_match('%^' . preg_quote(str_replace('_', '-', self::PREFIX), '%') . '([\w-]+)$%', $page)) {
|
206 |
+
$this->adminDispatcher($page, strtolower($input->getpost('action', 'index')));
|
207 |
+
}
|
208 |
+
}
|
209 |
+
|
210 |
+
/**
|
211 |
+
* Dispatch shorttag: create corresponding controller instance and call its index method
|
212 |
+
* @param array $args Shortcode tag attributes
|
213 |
+
* @param string $content Shortcode tag content
|
214 |
+
* @param string $tag Shortcode tag name which is being dispatched
|
215 |
+
* @return string
|
216 |
+
*/
|
217 |
+
public function shortcodeDispatcher($args, $content, $tag) {
|
218 |
+
|
219 |
+
$controllerName = self::PREFIX . preg_replace('%(^|_).%e', 'strtoupper("$0")', $tag); // capitalize first letters of class name parts and add prefix
|
220 |
+
$controller = new $controllerName();
|
221 |
+
if ( ! $controller instanceof PMXI_Controller) {
|
222 |
+
throw new Exception("Shortcode `$tag` matches to a wrong controller type.");
|
223 |
+
}
|
224 |
+
ob_start();
|
225 |
+
$controller->index($args, $content);
|
226 |
+
return ob_get_clean();
|
227 |
+
}
|
228 |
+
|
229 |
+
/**
|
230 |
+
* Dispatch admin page: call corresponding controller based on get parameter `page`
|
231 |
+
* The method is called twice: 1st time as handler `parse_header` action and then as admin menu item handler
|
232 |
+
* @param string[optional] $page When $page set to empty string ealier buffered content is outputted, otherwise controller is called based on $page value
|
233 |
+
*/
|
234 |
+
public function adminDispatcher($page = '', $action = 'index') {
|
235 |
+
static $buffer = NULL;
|
236 |
+
static $buffer_callback = NULL;
|
237 |
+
if ('' === $page) {
|
238 |
+
if ( ! is_null($buffer)) {
|
239 |
+
echo '<div class="wrap">';
|
240 |
+
echo $buffer;
|
241 |
+
do_action('pmxi_action_after');
|
242 |
+
echo '</div>';
|
243 |
+
} elseif ( ! is_null($buffer_callback)) {
|
244 |
+
echo '<div class="wrap">';
|
245 |
+
call_user_func($buffer_callback);
|
246 |
+
do_action('pmxi_action_after');
|
247 |
+
echo '</div>';
|
248 |
+
} else {
|
249 |
+
throw new Exception('There is no previousely buffered content to display.');
|
250 |
+
}
|
251 |
+
} else {
|
252 |
+
$controllerName = preg_replace('%(^' . preg_quote(self::PREFIX, '%') . '|_).%e', 'strtoupper("$0")', str_replace('-', '_', $page)); // capitalize prefix and first letters of class name parts
|
253 |
+
$actionName = str_replace('-', '_', $action);
|
254 |
+
if (method_exists($controllerName, $actionName)) {
|
255 |
+
$this->_admin_current_screen = (object)array(
|
256 |
+
'id' => $controllerName,
|
257 |
+
'base' => $controllerName,
|
258 |
+
'action' => $actionName,
|
259 |
+
'is_ajax' => isset($_SERVER['HTTP_X_REQUESTED_WITH']) and strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest',
|
260 |
+
'is_network' => is_network_admin(),
|
261 |
+
'is_user' => is_user_admin(),
|
262 |
+
);
|
263 |
+
add_filter('current_screen', array($this, 'getAdminCurrentScreen'));
|
264 |
+
add_filter('admin_body_class', create_function('', 'return "' . PMXI_Plugin::PREFIX . 'plugin";'));
|
265 |
+
|
266 |
+
$controller = new $controllerName();
|
267 |
+
if ( ! $controller instanceof PMXI_Controller_Admin) {
|
268 |
+
throw new Exception("Administration page `$page` matches to a wrong controller type.");
|
269 |
+
}
|
270 |
+
|
271 |
+
if ($this->_admin_current_screen->is_ajax) { // ajax request
|
272 |
+
$controller->$action();
|
273 |
+
do_action('pmxi_action_after');
|
274 |
+
die(); // stop processing since we want to output only what controller is randered, nothing in addition
|
275 |
+
} elseif ( ! $controller->isInline) {
|
276 |
+
ob_start();
|
277 |
+
$controller->$action();
|
278 |
+
$buffer = ob_get_clean();
|
279 |
+
} else {
|
280 |
+
$buffer_callback = array($controller, $action);
|
281 |
+
}
|
282 |
+
} else { // redirect to dashboard if requested page and/or action don't exist
|
283 |
+
wp_redirect(admin_url()); die();
|
284 |
+
}
|
285 |
+
}
|
286 |
+
}
|
287 |
+
|
288 |
+
protected $_admin_current_screen = NULL;
|
289 |
+
public function getAdminCurrentScreen()
|
290 |
+
{
|
291 |
+
return $this->_admin_current_screen;
|
292 |
+
}
|
293 |
+
|
294 |
+
/**
|
295 |
+
* Autoloader
|
296 |
+
* It's assumed class name consists of prefix folloed by its name which in turn corresponds to location of source file
|
297 |
+
* if `_` symbols replaced by directory path separator. File name consists of prefix folloed by last part in class name (i.e.
|
298 |
+
* symbols after last `_` in class name)
|
299 |
+
* When class has prefix it's source is looked in `models`, `controllers`, `shortcodes` folders, otherwise it looked in `core` or `library` folder
|
300 |
+
*
|
301 |
+
* @param string $className
|
302 |
+
* @return bool
|
303 |
+
*/
|
304 |
+
public function __autoload($className) {
|
305 |
+
$is_prefix = false;
|
306 |
+
$filePath = str_replace('_', '/', preg_replace('%^' . preg_quote(self::PREFIX, '%') . '%', '', strtolower($className), 1, $is_prefix)) . '.php';
|
307 |
+
if ( ! $is_prefix) { // also check file with original letter case
|
308 |
+
$filePathAlt = $className . '.php';
|
309 |
+
}
|
310 |
+
foreach ($is_prefix ? array('models', 'controllers', 'shortcodes', 'classes') : array('libraries') as $subdir) {
|
311 |
+
$path = self::ROOT_DIR . '/' . $subdir . '/' . $filePath;
|
312 |
+
if (is_file($path)) {
|
313 |
+
require $path;
|
314 |
+
return TRUE;
|
315 |
+
}
|
316 |
+
if ( ! $is_prefix) {
|
317 |
+
$pathAlt = self::ROOT_DIR . '/' . $subdir . '/' . $filePathAlt;
|
318 |
+
if (is_file($pathAlt)) {
|
319 |
+
require $pathAlt;
|
320 |
+
return TRUE;
|
321 |
+
}
|
322 |
+
}
|
323 |
+
}
|
324 |
+
|
325 |
+
$this->__ver_2_06_load_options_add();
|
326 |
+
|
327 |
+
return FALSE;
|
328 |
+
}
|
329 |
+
|
330 |
+
/**
|
331 |
+
* Get plugin option
|
332 |
+
* @param string[optional] $option Parameter to return, all array of options is returned if not set
|
333 |
+
* @return mixed
|
334 |
+
*/
|
335 |
+
public function getOption($option = NULL) {
|
336 |
+
if (is_null($option)) {
|
337 |
+
return $this->options;
|
338 |
+
} else if (isset($this->options[$option])) {
|
339 |
+
return $this->options[$option];
|
340 |
+
} else {
|
341 |
+
throw new Exception("Specified option is not defined for the plugin");
|
342 |
+
}
|
343 |
+
}
|
344 |
+
/**
|
345 |
+
* Update plugin option value
|
346 |
+
* @param string $option Parameter name or array of name => value pairs
|
347 |
+
* @param mixed[optional] $value New value for the option, if not set than 1st parameter is supposed to be array of name => value pairs
|
348 |
+
* @return array
|
349 |
+
*/
|
350 |
+
public function updateOption($option, $value = NULL) {
|
351 |
+
is_null($value) or $option = array($option => $value);
|
352 |
+
if (array_diff_key($option, $this->options)) {
|
353 |
+
throw new Exception("Specified option is not defined for the plugin");
|
354 |
+
}
|
355 |
+
$this->options = $option + $this->options;
|
356 |
+
update_option(get_class($this) . '_Options', $this->options);
|
357 |
+
|
358 |
+
return $this->options;
|
359 |
+
}
|
360 |
+
|
361 |
+
/**
|
362 |
+
* Plugin activation logic
|
363 |
+
*/
|
364 |
+
public function __activation() {
|
365 |
+
// uncaught exception doesn't prevent plugin from being activated, therefore replace it with fatal error so it does
|
366 |
+
set_exception_handler(create_function('$e', 'trigger_error($e->getMessage(), E_USER_ERROR);'));
|
367 |
+
|
368 |
+
// create plugin options
|
369 |
+
$option_name = get_class($this) . '_Options';
|
370 |
+
$options_default = PMXI_Config::createFromFile(self::ROOT_DIR . '/config/options.php')->toArray();
|
371 |
+
update_option($option_name, $options_default);
|
372 |
+
|
373 |
+
// create/update required database tables
|
374 |
+
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
375 |
+
require self::ROOT_DIR . '/schema.php';
|
376 |
+
dbDelta($plugin_queries);
|
377 |
+
|
378 |
+
$this->__ver_1_04_transition_fix();
|
379 |
+
|
380 |
+
// sync data between plugin tables and wordpress (mostly for the case when plugin is reactivated)
|
381 |
+
global $wpdb;
|
382 |
+
$post = new PMXI_Post_Record();
|
383 |
+
$wpdb->query('DELETE FROM ' . $post->getTable() . ' WHERE post_id NOT IN (SELECT ID FROM ' . $wpdb->posts . ')');
|
384 |
+
|
385 |
+
}
|
386 |
+
|
387 |
+
/**
|
388 |
+
* Method perfoms transition from version when file history has been stored in dabase to the solution when it stored on disk
|
389 |
+
* NOTE: the function can be removed when plugin version progress and it's sure matter nobody has ver 1.03
|
390 |
+
*/
|
391 |
+
public function __ver_1_04_transition_fix() {
|
392 |
+
if ( ! is_dir(self::ROOT_DIR . '/history') or ! is_writable(self::ROOT_DIR . '/history')) {
|
393 |
+
die(sprintf(__('History folder %s must be writable', 'pmxi_plugin'), self::ROOT_DIR . '/history'));
|
394 |
+
}
|
395 |
+
|
396 |
+
$table = $table = $this->getTablePrefix() . 'files';
|
397 |
+
global $wpdb;
|
398 |
+
$tablefields = $wpdb->get_results("DESCRIBE {$table};");
|
399 |
+
// For every field in the table
|
400 |
+
foreach ($tablefields as $tablefield) {
|
401 |
+
if ('contents' == $tablefield->Field) {
|
402 |
+
$list = new PMXI_File_List();
|
403 |
+
for ($i = 1; $list->getBy(NULL, 'id', $i, 1)->count(); $i++) {
|
404 |
+
foreach ($list->convertRecords() as $file) {
|
405 |
+
$file->save(); // resave file for file to be stored in history folder
|
406 |
+
}
|
407 |
+
}
|
408 |
+
|
409 |
+
$wpdb->query("ALTER TABLE {$table} DROP " . $tablefield->Field);
|
410 |
+
break;
|
411 |
+
}
|
412 |
+
}
|
413 |
+
}
|
414 |
+
|
415 |
+
/**
|
416 |
+
*
|
417 |
+
*
|
418 |
+
*
|
419 |
+
*/
|
420 |
+
public function __ver_2_06_load_options_add() {
|
421 |
+
$table = $table = $this->getTablePrefix() . 'templates';
|
422 |
+
global $wpdb;
|
423 |
+
$wpdb->query("ALTER TABLE {$table} ADD options TEXT NOT NULL");
|
424 |
+
$wpdb->query("ALTER TABLE {$table} ADD scheduled VARCHAR(64) NOT NULL");
|
425 |
+
}
|
426 |
+
|
427 |
+
/**
|
428 |
+
* Method returns default import options, main utility of the method is to avoid warnings when new
|
429 |
+
* option is introduced but already registered imports don't have it
|
430 |
+
*/
|
431 |
+
public static function get_default_import_options() {
|
432 |
+
return array(
|
433 |
+
'type' => 'post',
|
434 |
+
'custom_type' => '',
|
435 |
+
'categories' => '',
|
436 |
+
'categories_delim' => ',',
|
437 |
+
'tags' => '',
|
438 |
+
'tags_delim' => ',',
|
439 |
+
'post_taxonomies' => array(),
|
440 |
+
'post_taxonomies_delim' => array(),
|
441 |
+
'parent' => '',
|
442 |
+
'order' => '0',
|
443 |
+
'status' => 'publish',
|
444 |
+
'page_taxonomies' => array(),
|
445 |
+
'page_taxonomies_delim' => array(),
|
446 |
+
'date_type' => 'specific',
|
447 |
+
'date' => 'now',
|
448 |
+
'date_start' => 'now',
|
449 |
+
'date_end' => 'now',
|
450 |
+
'custom_name' => array(),
|
451 |
+
'custom_value' => array(),
|
452 |
+
'comment_status' => 'open',
|
453 |
+
'ping_status' => 'open',
|
454 |
+
'author' => wp_get_current_user()->ID,
|
455 |
+
'featured_image' => '',
|
456 |
+
'is_import_specified' => 0,
|
457 |
+
'import_specified' => '',
|
458 |
+
'is_delete_source' => 0,
|
459 |
+
'is_cloak' => 0,
|
460 |
+
'unique_key' => '',
|
461 |
+
|
462 |
+
'is_delete_missing' => 0,
|
463 |
+
'is_keep_former_posts' => 0,
|
464 |
+
'is_keep_status' => 0,
|
465 |
+
'is_keep_categories' => 0,
|
466 |
+
'is_keep_attachments' => 0,
|
467 |
+
'is_duplicates' => 0,
|
468 |
+
|
469 |
+
'duplicate_indicator' => 'title',
|
470 |
+
'duplicate_action' => 'keep',
|
471 |
+
'is_first_chank' => 0,
|
472 |
+
'is_update_previous' => 0,
|
473 |
+
'is_scheduled' => '',
|
474 |
+
'scheduled_period' => ''
|
475 |
+
);
|
476 |
+
}
|
477 |
+
}
|
478 |
+
|
479 |
+
PMXI_Plugin::getInstance();
|
readme.txt
ADDED
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
=== WP All Import ===
|
2 |
+
Contributors: soflyy
|
3 |
+
Tags: wordpress, xml, csv, datafeed, import
|
4 |
+
Requires at least: 3.0
|
5 |
+
Tested up to: 3.4.1
|
6 |
+
Stable tag: 2.12
|
7 |
+
|
8 |
+
The most powerful tool for importing any CSV or XML feed to WordPress.
|
9 |
+
|
10 |
+
== Description ==
|
11 |
+
|
12 |
+
= WP All Import Professional Edition =
|
13 |
+
|
14 |
+
[Upgrade to the professional edition of WP All Import.](http://www.wpallimport.com/upgrade-to-pro)
|
15 |
+
|
16 |
+
*WP All Import Pro* is a **significant upgrade** that adds many features to WP All Import Lite:
|
17 |
+
|
18 |
+
[Demo Video](http://www.wpallimport.com/) | [Pricing](http://www.wpallimport.com/order-now/)
|
19 |
+
|
20 |
+
|
21 |
+
WP All Import is a powerful XML and CSV import plugin.
|
22 |
+
|
23 |
+
WP All Import can be used for everything from building a store with an affiliate datafeed to displaying live stock quotes or sports scores to building a real estate portal.
|
24 |
+
|
25 |
+
= Features =
|
26 |
+
|
27 |
+
* Upload XML/CSV from your computer, or get it from a URL/FTP server
|
28 |
+
* Use XPath to filter a file and only import the entries you want.
|
29 |
+
* Drag & Drop template tags into the WYSIWYG post editor to design your posts and choose what data goes where.
|
30 |
+
* Save options & templates.
|
31 |
+
* Auto-creation of categories based on data in the feed.
|
32 |
+
* Managing of previous imports
|
33 |
+
|
34 |
+
|
35 |
+
== Premium Support ==
|
36 |
+
Upgrade to the professional edition of WP All Import for premium support.
|
37 |
+
|
38 |
+
E-mail: support@soflyy.com
|
39 |
+
|
40 |
+
== Installation ==
|
41 |
+
|
42 |
+
Either: -
|
43 |
+
|
44 |
+
* Upload the plugin from the Plugins page in WordPress
|
45 |
+
* Unzip wp-all-import.zip and upload the contents to /wp-content/plugins/, and then activate the plugin from the Plugins page in WordPress
|
46 |
+
|
47 |
+
|
48 |
+
== Frequently Asked Questions ==
|
49 |
+
|
50 |
+
*What are the differences between the Lite and Professional editions?*
|
51 |
+
The Professional edition adds support for Custom Fields, Custom Post Types, recurring/scheduled imports, downloading/importing of images, and [more](http://www.wpallimport.com/upgrade-to-pro/).
|
52 |
+
|
53 |
+
*What size files can WP All Import handle?*
|
54 |
+
Generally, WP All Import can comfortably handle files of up to 100Mb in most shared hosting environments (GoDaddy, HostGator, etc.). On VPS or dedicated hosting environments, the size of file WP All Import can handle is only limited by the power of the server. When importing very large files it is recommended to split them into chunks and import each chunk individually.
|
55 |
+
|
56 |
+
*Does my CSV have to be in any particular format?*
|
57 |
+
Use pretty much any delimiter you want. It has to work with the [fgetcsv](http://php.net/manual/en/function.fgetcsv.php) PHP function.
|
58 |
+
|
59 |
+
== Screenshots ==
|
60 |
+
|
61 |
+
1. Choose file.
|
62 |
+
2. Element selector.
|
63 |
+
3. Template designer.
|
64 |
+
4. Post options.
|
65 |
+
|
66 |
+
== Changelog ==
|
67 |
+
|
68 |
+
= 2.12 =
|
69 |
+
* Initial release on WordPress.org.
|
schema.php
ADDED
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Plugin database schema
|
4 |
+
* WARNING:
|
5 |
+
* dbDelta() doesn't like empty lines in schema string, so don't put them there;
|
6 |
+
* WPDB doesn't like NULL values so better not to have them in the tables;
|
7 |
+
*/
|
8 |
+
|
9 |
+
/**
|
10 |
+
* The database character collate.
|
11 |
+
* @var string
|
12 |
+
* @global string
|
13 |
+
* @name $charset_collate
|
14 |
+
*/
|
15 |
+
$charset_collate = '';
|
16 |
+
|
17 |
+
// Declare these as global in case schema.php is included from a function.
|
18 |
+
global $wpdb, $plugin_queries;
|
19 |
+
|
20 |
+
if ( ! empty($wpdb->charset))
|
21 |
+
$charset_collate = "DEFAULT CHARACTER SET $wpdb->charset";
|
22 |
+
if ( ! empty($wpdb->collate))
|
23 |
+
$charset_collate .= " COLLATE $wpdb->collate";
|
24 |
+
|
25 |
+
$table_prefix = PMXI_Plugin::getInstance()->getTablePrefix();
|
26 |
+
|
27 |
+
$plugin_queries = <<<SCHEMA
|
28 |
+
CREATE TABLE {$table_prefix}templates (
|
29 |
+
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
30 |
+
options TEXT,
|
31 |
+
scheduled VARCHAR(64) NOT NULL DEFAULT '',
|
32 |
+
name VARCHAR(200) NOT NULL DEFAULT '',
|
33 |
+
title TEXT,
|
34 |
+
content LONGTEXT,
|
35 |
+
is_keep_linebreaks TINYINT(1) NOT NULL DEFAULT 0,
|
36 |
+
PRIMARY KEY (id)
|
37 |
+
) $charset_collate;
|
38 |
+
CREATE TABLE {$table_prefix}imports (
|
39 |
+
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
40 |
+
name VARCHAR(255) NOT NULL DEFAULT '',
|
41 |
+
type VARCHAR(32) NOT NULL DEFAULT '',
|
42 |
+
path TEXT,
|
43 |
+
xpath VARCHAR(255) NOT NULL DEFAULT '',
|
44 |
+
template LONGTEXT,
|
45 |
+
options TEXT,
|
46 |
+
scheduled VARCHAR(64) NOT NULL DEFAULT '',
|
47 |
+
registered_on DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
|
48 |
+
PRIMARY KEY (id)
|
49 |
+
) $charset_collate;
|
50 |
+
CREATE TABLE {$table_prefix}posts (
|
51 |
+
post_id BIGINT(20) UNSIGNED NOT NULL,
|
52 |
+
import_id BIGINT(20) UNSIGNED NOT NULL,
|
53 |
+
unique_key TEXT,
|
54 |
+
PRIMARY KEY (post_id)
|
55 |
+
) $charset_collate;
|
56 |
+
CREATE TABLE {$table_prefix}files (
|
57 |
+
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
58 |
+
import_id BIGINT(20) UNSIGNED NOT NULL,
|
59 |
+
name VARCHAR(255) NOT NULL DEFAULT '',
|
60 |
+
path TEXT,
|
61 |
+
registered_on DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
|
62 |
+
PRIMARY KEY (id)
|
63 |
+
) $charset_collate;
|
64 |
+
SCHEMA;
|
screenshot-1.png
ADDED
Binary file
|
screenshot-2.png
ADDED
Binary file
|
screenshot-3.png
ADDED
Binary file
|
screenshot-4.png
ADDED
Binary file
|
static/css/admin-ie.css
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.pmxi_plugin .load-template a.help {
|
2 |
+
margin-top: -10px;
|
3 |
+
}
|
4 |
+
.pmxi_plugin .wrap {
|
5 |
+
margin-top: 5px;
|
6 |
+
}
|
7 |
+
.pmxi_plugin input.button-primary {
|
8 |
+
vertical-align: middle;
|
9 |
+
}
|
10 |
+
|
11 |
+
.xml-expander {
|
12 |
+
display: inline;
|
13 |
+
}
|
static/css/admin.css
ADDED
@@ -0,0 +1,469 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*@+ common */
|
2 |
+
.pmxi_plugin hr {
|
3 |
+
height: 1px;
|
4 |
+
border-width: 0px;
|
5 |
+
color: #ddd;
|
6 |
+
background-color: #ddd;
|
7 |
+
margin-bottom: 15px;
|
8 |
+
}
|
9 |
+
.pmxi_plugin a.button {
|
10 |
+
padding: 5px 10px;
|
11 |
+
margin: 1px;
|
12 |
+
font-weight: bold;
|
13 |
+
}
|
14 |
+
.pmxi_plugin a.help {
|
15 |
+
overflow: hidden;
|
16 |
+
text-indent: -99999px;
|
17 |
+
display: inline-block;
|
18 |
+
width: 16px;
|
19 |
+
height: 16px;
|
20 |
+
background-repeat: no-repeat;
|
21 |
+
background-image: url("../img/help.png");
|
22 |
+
vertical-align: middle;
|
23 |
+
margin-left: 5px;
|
24 |
+
}
|
25 |
+
.pmxi_plugin input.datepicker {
|
26 |
+
width: 8em;
|
27 |
+
}
|
28 |
+
.pmxi_plugin button.ui-datepicker-trigger {
|
29 |
+
background-image: url("../img/date-picker.gif");
|
30 |
+
background-repeat: no-repeat;
|
31 |
+
cursor: pointer;
|
32 |
+
border: none;
|
33 |
+
margin: 1px;
|
34 |
+
width: 21px;
|
35 |
+
height: 18px;
|
36 |
+
vertical-align: middle;
|
37 |
+
}
|
38 |
+
.pmxi_plugin .progress-msg {
|
39 |
+
font-style: italic;
|
40 |
+
}
|
41 |
+
.pmxi_plugin .inner-content {
|
42 |
+
max-width: 700px;
|
43 |
+
}
|
44 |
+
.pmxi_plugin .loading {
|
45 |
+
cursor: progress;
|
46 |
+
background-repeat: no-repeat;
|
47 |
+
background-position: center;
|
48 |
+
}
|
49 |
+
.pmxi_plugin a.add-new {
|
50 |
+
font-size: 18px;
|
51 |
+
background-color: #eee;
|
52 |
+
cursor: pointer;
|
53 |
+
padding: 6px 10px 6px 10px;
|
54 |
+
line-height: normal;
|
55 |
+
font-style: normal;
|
56 |
+
color: #464646;
|
57 |
+
border-color: #bbb;
|
58 |
+
-moz-border-radius: 4px 4px 4px 4px;
|
59 |
+
border-radius: 4px;
|
60 |
+
border-style: solid;
|
61 |
+
border-width: 1px;
|
62 |
+
font-family: "Lucida Grande",Verdana,Arial,"Bitstream Vera Sans",sans-serif;
|
63 |
+
text-decoration: none;
|
64 |
+
}
|
65 |
+
.pmxi_plugin a.add-new:hover {
|
66 |
+
border-color: #666666;
|
67 |
+
color: #000;
|
68 |
+
}
|
69 |
+
.pmxi_plugin div.input {
|
70 |
+
line-height: 25px;
|
71 |
+
height: 25px;
|
72 |
+
}
|
73 |
+
.pmxi_plugin div.input > * {
|
74 |
+
vertical-align: middle;
|
75 |
+
}
|
76 |
+
.pmxi_plugin .note {
|
77 |
+
color: #666666;
|
78 |
+
font-size: 11px;
|
79 |
+
}
|
80 |
+
.pmxi_plugin div.sub {
|
81 |
+
padding-left: 20px;
|
82 |
+
font-size: 12px;
|
83 |
+
}
|
84 |
+
/* 2 column layout */
|
85 |
+
.pmxi_plugin table.layout {
|
86 |
+
clear: both;
|
87 |
+
border-collapse: collapse;
|
88 |
+
min-width: 770px;
|
89 |
+
width: 100%;
|
90 |
+
}
|
91 |
+
.pmxi_plugin table.layout td {
|
92 |
+
vertical-align: top;
|
93 |
+
border: none;
|
94 |
+
}
|
95 |
+
.pmxi_plugin table.layout td.left {
|
96 |
+
min-width: 490px;
|
97 |
+
width: 70%;
|
98 |
+
}
|
99 |
+
.pmxi_plugin table.layout td.right {
|
100 |
+
padding: 0px 0 16px 20px;
|
101 |
+
width: 30%;
|
102 |
+
min-width: 260px;
|
103 |
+
}
|
104 |
+
.pmxi_plugin table.layout td.left > h2:first-child {
|
105 |
+
margin-top: -22px;
|
106 |
+
padding: 14px 0 3px 0;
|
107 |
+
}
|
108 |
+
.pmxi_plugin table.layout td.left hr {
|
109 |
+
clear: both;
|
110 |
+
}
|
111 |
+
.pmxi_plugin.no-js table.layout td.left > h2:first-child {
|
112 |
+
margin-top: 0px;
|
113 |
+
}
|
114 |
+
/*@*/
|
115 |
+
|
116 |
+
/*@+ Setting Form */
|
117 |
+
.pmxi_plugin form.settings {
|
118 |
+
width: 700px;
|
119 |
+
}
|
120 |
+
/*@*/
|
121 |
+
|
122 |
+
/*@+ Choose File forms */
|
123 |
+
.pmxi_plugin form.choose-file {
|
124 |
+
padding-top: 10px;
|
125 |
+
}
|
126 |
+
.pmxi_plugin form.choose-file h3 {
|
127 |
+
margin-bottom: 5px;
|
128 |
+
}
|
129 |
+
.pmxi_plugin form.choose-file .label {
|
130 |
+
font-size: 15px;
|
131 |
+
}
|
132 |
+
.pmxi_plugin form.choose-file input[type="text"],
|
133 |
+
.pmxi_plugin form.choose-file input[type="password"] {
|
134 |
+
width: 80px;
|
135 |
+
font-size: 12px;
|
136 |
+
}
|
137 |
+
.pmxi_plugin form.choose-file input.regular-text,
|
138 |
+
.pmxi_plugin form.choose-file select.regular-text {
|
139 |
+
width: 300px;
|
140 |
+
}
|
141 |
+
.pmxi_plugin #wpcontent form.choose-file select[name="file"],
|
142 |
+
.pmxi_plugin #wpcontent form.choose-file select[name="reimport"] {
|
143 |
+
font-size: 12px;
|
144 |
+
}
|
145 |
+
.pmxi_plugin form.choose-file input[type="submit"].button {
|
146 |
+
width: 150px;
|
147 |
+
}
|
148 |
+
.pmxi_plugin form.choose-file div.input {
|
149 |
+
margin-top: 20px;
|
150 |
+
}
|
151 |
+
/*@*/
|
152 |
+
|
153 |
+
/*@+ Choose Elements form */
|
154 |
+
.pmxi_plugin form.choose-elements input[type="text"] {
|
155 |
+
width: 100%;
|
156 |
+
max-width: 300px;
|
157 |
+
min-width: 150px;
|
158 |
+
}
|
159 |
+
/*@*/
|
160 |
+
|
161 |
+
/*@+ Template form */
|
162 |
+
.pmxi_plugin form.template.edit {
|
163 |
+
/*width: 700px;*/
|
164 |
+
}
|
165 |
+
.pmxi_plugin form.template .load-template {
|
166 |
+
display: block;
|
167 |
+
float: right;
|
168 |
+
padding-top: 7px;
|
169 |
+
font-size: 12px;
|
170 |
+
}
|
171 |
+
.pmxi_plugin form.template .load-template select {
|
172 |
+
width: 126px;
|
173 |
+
}
|
174 |
+
|
175 |
+
.pmxi_plugin #poststuff form.template h3 {
|
176 |
+
margin: 1em 0 5px 2px;
|
177 |
+
font-size: 1.17em;
|
178 |
+
padding: 0px;
|
179 |
+
}
|
180 |
+
.pmxi_plugin #post-preview {
|
181 |
+
font-size: 12px;
|
182 |
+
}
|
183 |
+
.pmxi_plugin #post-preview .error {
|
184 |
+
margin: 5px 0;
|
185 |
+
}
|
186 |
+
.pmxi_plugin h3 .header-option {
|
187 |
+
display: block;
|
188 |
+
float: right;
|
189 |
+
font-size: 12px;
|
190 |
+
font-weight: normal;
|
191 |
+
}
|
192 |
+
/*@*/
|
193 |
+
|
194 |
+
/*@+ Options form */
|
195 |
+
.pmxi_plugin form.options.edit {
|
196 |
+
/*width: 700px;*/
|
197 |
+
}
|
198 |
+
|
199 |
+
.pmxi_plugin .post-type-container,
|
200 |
+
.pmxi_plugin .file-type-container {
|
201 |
+
padding: 4px 10px 5px;
|
202 |
+
border: 1px solid transparent;
|
203 |
+
}
|
204 |
+
.pmxi_plugin .post-type-container.selected,
|
205 |
+
.pmxi_plugin .file-type-container.selected {
|
206 |
+
background-color: #F2FBD9;
|
207 |
+
border: 1px solid #B5E61D;
|
208 |
+
padding-bottom: 10px;
|
209 |
+
}
|
210 |
+
.pmxi_plugin .post-type-container h3,
|
211 |
+
.pmxi_plugin .file-type-container h3 {
|
212 |
+
margin: 0px 0px;
|
213 |
+
}
|
214 |
+
.pmxi_plugin .post-type-container .post-type-options,
|
215 |
+
.pmxi_plugin .file-type-container .file-type-options {
|
216 |
+
margin: 5px 0 0 20px;
|
217 |
+
}
|
218 |
+
|
219 |
+
.pmxi_plugin table.form-table {
|
220 |
+
clear: none;
|
221 |
+
margin-top: 0px;
|
222 |
+
}
|
223 |
+
.pmxi_plugin .post-type-options table.form-table {
|
224 |
+
max-width: 500px;
|
225 |
+
}
|
226 |
+
.pmxi_plugin .post-type-options table.form-table td.delim {
|
227 |
+
width: 50px;
|
228 |
+
}
|
229 |
+
.pmxi_plugin .post-type-options table.form-table td.delim input {
|
230 |
+
width: 20px;
|
231 |
+
}
|
232 |
+
.pmxi_plugin table.form-table.custom-params {
|
233 |
+
max-width: 700px;
|
234 |
+
}
|
235 |
+
.pmxi_plugin table.form-table td,
|
236 |
+
.pmxi_plugin table.form-table th {
|
237 |
+
padding: 2px 4px;
|
238 |
+
vertical-align: top;
|
239 |
+
}
|
240 |
+
.pmxi_plugin .post-type-options table.form-table th {
|
241 |
+
width: 100px;
|
242 |
+
}
|
243 |
+
.pmxi_plugin .post-type-options table.form-table select {
|
244 |
+
max-width: 300px;
|
245 |
+
}
|
246 |
+
.pmxi_plugin table.form-table thead td {
|
247 |
+
font-weight: bold;
|
248 |
+
}
|
249 |
+
.pmxi_plugin table.form-table.custom-params input {
|
250 |
+
margin-left: 0;
|
251 |
+
}
|
252 |
+
.pmxi_plugin table.form-table tr.template {
|
253 |
+
display: none;
|
254 |
+
}
|
255 |
+
/*@*/
|
256 |
+
|
257 |
+
/*@+ XML representation */
|
258 |
+
.pmxi_plugin .tag {
|
259 |
+
border: 1px solid #DFDFDF;
|
260 |
+
background-color: #fff;
|
261 |
+
-moz-border-radius: 4px;
|
262 |
+
-khtml-border-radius: 4px;
|
263 |
+
-webkit-border-radius: 4px;
|
264 |
+
border-radius: 4px;
|
265 |
+
}
|
266 |
+
.pmxi_plugin .tag .title {
|
267 |
+
font-weight: bold;
|
268 |
+
padding: 6px 8px;
|
269 |
+
color: #464646;
|
270 |
+
background: url("../../../../../wp-admin/images/gray-grad.png") repeat-x scroll left top #DFDFDF;
|
271 |
+
font-size: 12px;
|
272 |
+
}
|
273 |
+
.pmxi_plugin .tag .xml {
|
274 |
+
max-height: 600px;
|
275 |
+
overflow: auto;
|
276 |
+
}
|
277 |
+
.pmxi_plugin .tag .navigation {
|
278 |
+
float: right;
|
279 |
+
margin: -2px -12px 0 0;
|
280 |
+
}
|
281 |
+
.pmxi_plugin .tag .navigation a,
|
282 |
+
.pmxi_plugin .tag .navigation span {
|
283 |
+
font-weight: bold;
|
284 |
+
padding: 0 12px;
|
285 |
+
text-decoration: none;
|
286 |
+
}
|
287 |
+
|
288 |
+
.xml {
|
289 |
+
padding-left: 15px;
|
290 |
+
}
|
291 |
+
.xml-element {
|
292 |
+
border: 1px solid transparent;
|
293 |
+
margin: 1px 1px 1px 0;
|
294 |
+
}
|
295 |
+
.xml-element.selected > .xml-tag.opening .xml-tag-name {
|
296 |
+
background-color: #B5E61D;
|
297 |
+
}
|
298 |
+
.xml-content {
|
299 |
+
padding-left: 14px;
|
300 |
+
}
|
301 |
+
.xml-content.collapsed {
|
302 |
+
display: none;
|
303 |
+
}
|
304 |
+
.xml-content.textonly.short {
|
305 |
+
padding-left: 0px;
|
306 |
+
display: inline;
|
307 |
+
}
|
308 |
+
.xml-tag {
|
309 |
+
display: inline;
|
310 |
+
}
|
311 |
+
.xml-tag-name {
|
312 |
+
color: #906;
|
313 |
+
font-weight: bold;
|
314 |
+
}
|
315 |
+
.xml-tag.opening .xml-tag-name {
|
316 |
+
cursor: pointer;
|
317 |
+
}
|
318 |
+
.xml-attr-name {
|
319 |
+
font-weight: bold;
|
320 |
+
cursor: pointer;
|
321 |
+
}
|
322 |
+
.xml-attr-value {
|
323 |
+
color: blue;
|
324 |
+
}
|
325 |
+
.xml-expander {
|
326 |
+
display: inline-block;
|
327 |
+
width: 12px;
|
328 |
+
margin-left: -12px;
|
329 |
+
-moz-user-select: none;
|
330 |
+
-khtml-user-select: none;
|
331 |
+
-webkit-user-select: none;
|
332 |
+
user-select: none;
|
333 |
+
cursor: pointer;
|
334 |
+
font-family: monospace;
|
335 |
+
line-height: 100%;
|
336 |
+
text-align: left;
|
337 |
+
color: red;
|
338 |
+
}
|
339 |
+
.xml-more {
|
340 |
+
color: red;
|
341 |
+
font-size: 80%;
|
342 |
+
}
|
343 |
+
.xml.resetable .xml-element.lvl-mod4-3 > .xml-content {
|
344 |
+
margin-left: -59px;
|
345 |
+
margin-right: -8px;
|
346 |
+
background-color: #fff;
|
347 |
+
border: 1px dashed #906;
|
348 |
+
border-left: 2px solid #906;
|
349 |
+
border-right: none;
|
350 |
+
}
|
351 |
+
.xml.resetable .xml-element.lvl-mod4-3 > .xml-content.short {
|
352 |
+
margin-left: 0;
|
353 |
+
margin-right: 0;
|
354 |
+
border: none;
|
355 |
+
background-color: inherit;
|
356 |
+
}
|
357 |
+
/* xml table representation */
|
358 |
+
tr.xml-element.selected .xml-tag.opening .xml-tag-name {
|
359 |
+
background-color: #B5E61D;
|
360 |
+
}
|
361 |
+
table.xml td {
|
362 |
+
padding-left: 20px;
|
363 |
+
}
|
364 |
+
table.xml td:first-child {
|
365 |
+
width: 1px;
|
366 |
+
padding-left: 0px;
|
367 |
+
}
|
368 |
+
|
369 |
+
table.xml,
|
370 |
+
table.xml table {
|
371 |
+
width: 100%;
|
372 |
+
border-collapse:collapse;
|
373 |
+
border-spacing:0;
|
374 |
+
}
|
375 |
+
/*@*/
|
376 |
+
|
377 |
+
/*@+ table list */
|
378 |
+
.pmxi_plugin table.widefat th {
|
379 |
+
white-space: nowrap;
|
380 |
+
}
|
381 |
+
.pmxi_plugin table.widefat th.ASC a {
|
382 |
+
background-image: url("../img/screen-options-right-up.gif");
|
383 |
+
background-repeat: no-repeat;
|
384 |
+
background-position: right center;
|
385 |
+
padding-right: 19px;
|
386 |
+
}
|
387 |
+
.pmxi_plugin table.widefat th.DESC a {
|
388 |
+
background-image: url("../img/screen-options-right.gif");
|
389 |
+
background-repeat: no-repeat;
|
390 |
+
background-position: right center;
|
391 |
+
padding-right: 19px;
|
392 |
+
}
|
393 |
+
|
394 |
+
.pmxi_plugin table.widefat.pmxi-admin-imports th.column-id {
|
395 |
+
width: 35px;
|
396 |
+
}
|
397 |
+
.pmxi_plugin table.widefat.pmxi-admin-imports th.column-scheduled {
|
398 |
+
width: 85px;
|
399 |
+
}
|
400 |
+
.pmxi_plugin table.widefat.pmxi-admin-imports th.column-registered_on {
|
401 |
+
width: 130px;
|
402 |
+
}
|
403 |
+
.pmxi_plugin table.widefat.pmxi-admin-imports th.column-post_count {
|
404 |
+
width: 105px;
|
405 |
+
}
|
406 |
+
/*@*/
|
407 |
+
|
408 |
+
/*@+ fixes */
|
409 |
+
.pmxi_plugin input[type="file"] {
|
410 |
+
padding: 0; /* FIX height or <input type="file" /> for Safari & Chrome */
|
411 |
+
}
|
412 |
+
.pmxi_plugin .ui-widget-overlay {
|
413 |
+
position: fixed !important; /* FIX: modal dialog overlay in IE 8 */
|
414 |
+
background-color: #aaa !important; /* FIX: overlay color */
|
415 |
+
}
|
416 |
+
.pmxi_plugin .ui-dialog {
|
417 |
+
position: absolute !important; /* FIX: for wordpress 3.1 not to add empty space */
|
418 |
+
}
|
419 |
+
.pmxi_plugin .ui-autocomplete {
|
420 |
+
position: absolute;
|
421 |
+
border-color: #ccc #444 #444 #aaa;
|
422 |
+
cursor: default;
|
423 |
+
-moz-border-radius: 0;
|
424 |
+
border-radius: 0;
|
425 |
+
max-height: 200px;
|
426 |
+
max-width: 700px;
|
427 |
+
overflow-y: auto;
|
428 |
+
overflow-x: hidden;
|
429 |
+
white-space: nowrap;
|
430 |
+
font-size: 11px;
|
431 |
+
}
|
432 |
+
.pmxi_plugin .ui-autocomplete .ui-menu-item {
|
433 |
+
padding: 0;
|
434 |
+
margin: 0;
|
435 |
+
}
|
436 |
+
.pmxi_plugin .ui-autocomplete .ui-menu-item a {
|
437 |
+
display: block;
|
438 |
+
padding: 2px;
|
439 |
+
padding-right: 20px; /* space for scroll bar */
|
440 |
+
font-size: 11px;
|
441 |
+
border: none;
|
442 |
+
-moz-border-radius: 0;
|
443 |
+
border-radius: 0;
|
444 |
+
}
|
445 |
+
.pmxi_plugin .ui-autocomplete .ui-menu-item a.ui-state-hover {
|
446 |
+
background: #39f;
|
447 |
+
color: #fff;
|
448 |
+
}
|
449 |
+
.pmxi_plugin input.autocomplete {
|
450 |
+
background: url("../img/down.gif") no-repeat right top #fff;
|
451 |
+
padding-right: 20px;
|
452 |
+
cursor: default;
|
453 |
+
font-size: 11px !important;
|
454 |
+
}
|
455 |
+
/*@*/
|
456 |
+
|
457 |
+
.pmxi_plugin .taglines{
|
458 |
+
font-size:13px;
|
459 |
+
font-style: italic;
|
460 |
+
display: block;
|
461 |
+
color: #777;
|
462 |
+
}
|
463 |
+
|
464 |
+
.pmxi_plugin #process{ display:none; }
|
465 |
+
.pmxi_plugin .load-options{ float:right; font-size:13px; }
|
466 |
+
.pmxi_plugin form.options table.layout td.right{
|
467 |
+
position: fixed;
|
468 |
+
width: 25%;
|
469 |
+
}
|
static/img/date-picker.gif
ADDED
Binary file
|
static/img/down.gif
ADDED
Binary file
|
static/img/help.png
ADDED
Binary file
|
static/img/loading.gif
ADDED
Binary file
|
static/img/screen-options-right-up.gif
ADDED
Binary file
|
static/img/screen-options-right.gif
ADDED
Binary file
|
static/img/xmlicon.png
ADDED
Binary file
|
static/js/admin.js
ADDED
@@ -0,0 +1,421 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/**
|
2 |
+
* plugin admin area javascript
|
3 |
+
*/
|
4 |
+
(function($){$(function () {
|
5 |
+
if ( ! $('body.pmxi_plugin').length) return; // do not execute any code if we are not on plugin page
|
6 |
+
|
7 |
+
// fix layout position
|
8 |
+
setTimeout(function () {
|
9 |
+
$('table.layout').length && $('table.layout td.left h2:first-child').css('margin-top', $('.wrap').offset().top - $('table.layout').offset().top);
|
10 |
+
}, 10);
|
11 |
+
|
12 |
+
|
13 |
+
// help icons
|
14 |
+
$('a.help').tipsy({
|
15 |
+
gravity: function() {
|
16 |
+
var ver = 'n';
|
17 |
+
if ($(document).scrollTop() < $(this).offset().top - $('.tipsy').height() - 2) {
|
18 |
+
ver = 's';
|
19 |
+
}
|
20 |
+
var hor = '';
|
21 |
+
if ($(this).offset().left + $('.tipsy').width() < $(window).width() + $(document).scrollLeft()) {
|
22 |
+
hor = 'w';
|
23 |
+
} else if ($(this).offset().left - $('.tipsy').width() > $(document).scrollLeft()) {
|
24 |
+
hor = 'e';
|
25 |
+
}
|
26 |
+
return ver + hor;
|
27 |
+
},
|
28 |
+
live: true,
|
29 |
+
html: true,
|
30 |
+
opacity: 1
|
31 |
+
}).live('click', function () {
|
32 |
+
return false;
|
33 |
+
}).each(function () { // fix tipsy title for IE
|
34 |
+
$(this).attr('original-title', $(this).attr('title'));
|
35 |
+
$(this).removeAttr('title');
|
36 |
+
});
|
37 |
+
|
38 |
+
// swither show/hide logic
|
39 |
+
$('input.switcher').change(function (e) {
|
40 |
+
if ($(this).is(':radio:checked')) {
|
41 |
+
$(this).parents('form').find('input.switcher:radio[name="' + $(this).attr('name') + '"]').not(this).change();
|
42 |
+
}
|
43 |
+
var $targets = $('.switcher-target-' + $(this).attr('id'));
|
44 |
+
var is_show = $(this).is(':checked'); if ($(this).is('.switcher-reversed')) is_show = ! is_show;
|
45 |
+
if (is_show) {
|
46 |
+
$targets.fadeIn();
|
47 |
+
} else {
|
48 |
+
$targets.hide().find('.clear-on-switch').add($targets.filter('.clear-on-switch')).val('');
|
49 |
+
}
|
50 |
+
}).change();
|
51 |
+
|
52 |
+
// autoselect input content on click
|
53 |
+
$('input.selectable').live('click', function () {
|
54 |
+
$(this).select();
|
55 |
+
});
|
56 |
+
|
57 |
+
// input tags with title
|
58 |
+
$('input[title]').each(function () {
|
59 |
+
var $this = $(this);
|
60 |
+
$this.bind('focus', function () {
|
61 |
+
if ('' == $(this).val() || $(this).val() == $(this).attr('title')) {
|
62 |
+
$(this).removeClass('note').val('');
|
63 |
+
}
|
64 |
+
}).bind('blur', function () {
|
65 |
+
if ('' == $(this).val() || $(this).val() == $(this).attr('title')) {
|
66 |
+
$(this).addClass('note').val($(this).attr('title'));
|
67 |
+
}
|
68 |
+
}).blur();
|
69 |
+
$this.parents('form').bind('submit', function () {
|
70 |
+
if ($this.val() == $this.attr('title')) {
|
71 |
+
$this.val('');
|
72 |
+
}
|
73 |
+
});
|
74 |
+
});
|
75 |
+
|
76 |
+
// datepicker
|
77 |
+
$('input.datepicker').datepicker({
|
78 |
+
dateFormat: 'yy-mm-dd',
|
79 |
+
showOn: 'button',
|
80 |
+
buttonText: '',
|
81 |
+
constrainInput: false,
|
82 |
+
showAnim: 'fadeIn',
|
83 |
+
showOptions: 'fast'
|
84 |
+
}).bind('change', function () {
|
85 |
+
var selectedDate = $(this).val();
|
86 |
+
var instance = $(this).data('datepicker');
|
87 |
+
var date = null;
|
88 |
+
if ('' != selectedDate) {
|
89 |
+
try {
|
90 |
+
date = $.datepicker.parseDate(instance.settings.dateFormat || $.datepicker._defaults.dateFormat, selectedDate, instance.settings);
|
91 |
+
} catch (e) {
|
92 |
+
date = null;
|
93 |
+
}
|
94 |
+
}
|
95 |
+
if ($(this).hasClass('range-from')) {
|
96 |
+
$(this).parent().find('.datepicker.range-to').datepicker("option", "minDate", date);
|
97 |
+
}
|
98 |
+
if ($(this).hasClass('range-to')) {
|
99 |
+
$(this).parent().find('.datepicker.range-from').datepicker("option", "maxDate", date);
|
100 |
+
}
|
101 |
+
}).change();
|
102 |
+
$('.ui-datepicker').hide(); // fix: make sure datepicker doesn't break wordpress layout upon initialization
|
103 |
+
|
104 |
+
// no-enter-submit forms
|
105 |
+
$('form.no-enter-submit').find('input,select,textarea').not('*[type="submit"]').keydown(function (e) {
|
106 |
+
if (13 == e.keyCode) e.preventDefault();
|
107 |
+
});
|
108 |
+
|
109 |
+
// choose file form: option selection dynamic
|
110 |
+
// options form: highlight options of selected post type
|
111 |
+
$('form.choose-file input[name="type"]').click(function() {
|
112 |
+
var $container = $(this).parents('.file-type-container');
|
113 |
+
$('.file-type-container').not($container).removeClass('selected').find('.file-type-options').hide();
|
114 |
+
$container.addClass('selected').find('.file-type-options').show();
|
115 |
+
}).filter(':checked').click();
|
116 |
+
|
117 |
+
// template form: auto submit when `load template` list value is picked
|
118 |
+
$('form.template').find('select[name="load_template"]').change(function () {
|
119 |
+
$(this).parents('form').submit();
|
120 |
+
});
|
121 |
+
// template form: preview button
|
122 |
+
$('form.template').each(function () {
|
123 |
+
var $form = $(this);
|
124 |
+
var $modal = $('<div></div>').dialog({
|
125 |
+
autoOpen: false,
|
126 |
+
modal: true,
|
127 |
+
title: 'Preview Post',
|
128 |
+
width: 760,
|
129 |
+
maxHeight: 600,
|
130 |
+
open: function(event, ui) {
|
131 |
+
$(this).dialog('option', 'height', 'auto').css({'max-height': $(this).dialog('option', 'maxHeight') - $(this).prev().height() - 24, 'overflow-y': 'auto'});
|
132 |
+
}
|
133 |
+
});
|
134 |
+
$form.find('.preview').click(function () {
|
135 |
+
$modal.addClass('loading').empty().dialog('open').dialog('option', 'position', 'center');
|
136 |
+
tinyMCE.triggerSave(false, false);
|
137 |
+
$.post('admin.php?page=pmxi-admin-import&action=preview', $form.serialize(), function (response) {
|
138 |
+
$modal.removeClass('loading').html(response).dialog('option', 'position', 'center');
|
139 |
+
});
|
140 |
+
return false;
|
141 |
+
});
|
142 |
+
});
|
143 |
+
|
144 |
+
// options form: highlight options of selected post type
|
145 |
+
$('form.options input[name="type"]').click(function() {
|
146 |
+
var $container = $(this).parents('.post-type-container');
|
147 |
+
$('.post-type-container').not($container).removeClass('selected').find('.post-type-options').hide();
|
148 |
+
$container.addClass('selected').find('.post-type-options').show();
|
149 |
+
}).filter(':checked').click();
|
150 |
+
// options form: add / remove custom params
|
151 |
+
$('.form-table a.action[href="#add"]').live('click', function () {
|
152 |
+
var $template = $(this).parents('table').first().find('tr.template');
|
153 |
+
$template.clone(true).insertBefore($template).css('display', 'none').removeClass('template').fadeIn();
|
154 |
+
return false;
|
155 |
+
});
|
156 |
+
// options form: auto submit when `load options` checkbox is checked
|
157 |
+
$('form.options').find('input[name="load_options"]').click(function () {
|
158 |
+
if ($(this).is(':checked')) $(this).parents('form').submit();
|
159 |
+
});
|
160 |
+
// options form: auto submit when `reset options` checkbox is checked
|
161 |
+
$('form.options').find('input[name="reset_options"]').click(function () {
|
162 |
+
if ($(this).is(':checked')) $(this).parents('form').submit();
|
163 |
+
});
|
164 |
+
$('.form-table .action.remove a').live('click', function () {
|
165 |
+
$(this).parents('tr').first().remove();
|
166 |
+
return false;
|
167 |
+
});
|
168 |
+
|
169 |
+
var dblclickbuf = {
|
170 |
+
'selected':false,
|
171 |
+
'value':''
|
172 |
+
};
|
173 |
+
|
174 |
+
// [xml representation dynamic]
|
175 |
+
$.fn.xml = function (opt) {
|
176 |
+
if ( ! this.length) return this;
|
177 |
+
|
178 |
+
var $self = this;
|
179 |
+
var opt = opt || {};
|
180 |
+
var action = {};
|
181 |
+
if ('object' == typeof opt) {
|
182 |
+
action = opt;
|
183 |
+
} else {
|
184 |
+
action[opt] = true;
|
185 |
+
}
|
186 |
+
action = $.extend({init: ! this.data('initialized')}, action);
|
187 |
+
|
188 |
+
if (action.init) {
|
189 |
+
this.data('initialized', true);
|
190 |
+
// add expander
|
191 |
+
this.find('.xml-expander').click(function () {
|
192 |
+
var method;
|
193 |
+
if ('-' == $(this).text()) {
|
194 |
+
$(this).text('+');
|
195 |
+
method = 'addClass';
|
196 |
+
} else {
|
197 |
+
$(this).text('-');
|
198 |
+
method = 'removeClass';
|
199 |
+
}
|
200 |
+
// for nested representation based on div
|
201 |
+
$(this).parent().find('> .xml-content')[method]('collapsed');
|
202 |
+
// for nested representation based on tr
|
203 |
+
var $tr = $(this).parent().parent().filter('tr.xml-element').next()[method]('collapsed');
|
204 |
+
});
|
205 |
+
}
|
206 |
+
if (action.dragable) { // drag & drop
|
207 |
+
var _w; var _dbl = 0;
|
208 |
+
var $drag = $('__drag'); $drag.length || ($drag = $('<input type="text" id="__drag" readonly="readonly" />'));
|
209 |
+
|
210 |
+
$drag.css({
|
211 |
+
position: 'absolute',
|
212 |
+
background: 'transparent',
|
213 |
+
top: -50,
|
214 |
+
left: 0,
|
215 |
+
margin: 0,
|
216 |
+
border: 'none',
|
217 |
+
lineHeight: 1,
|
218 |
+
opacity: 0,
|
219 |
+
cursor: 'pointer',
|
220 |
+
borderRadius: 0
|
221 |
+
}).appendTo(document.body).mousedown(function (e) {
|
222 |
+
if (_dbl) return;
|
223 |
+
var _x = e.pageX - $drag.offset().left;
|
224 |
+
var _y = e.pageY - $drag.offset().top;
|
225 |
+
if (_x < 4 || _y < 4 || $drag.width() - _x < 0 || $drag.height() - _y < 0) {
|
226 |
+
return;
|
227 |
+
}
|
228 |
+
$drag.width($(document.body).width() - $drag.offset().left - 5).css('opacity', 1);
|
229 |
+
$drag.select();
|
230 |
+
_dbl = true; setTimeout(function () {_dbl = false;}, 400);
|
231 |
+
}).mouseup(function () {
|
232 |
+
$drag.css('opacity', 0).css('width', _w);
|
233 |
+
$drag.blur();
|
234 |
+
}).dblclick(function(){
|
235 |
+
if (dblclickbuf.selected)
|
236 |
+
{
|
237 |
+
$('.xml-element[title*="/'+dblclickbuf.value.replace('{','').replace('}','')+'"]').removeClass('selected');
|
238 |
+
|
239 |
+
if ($(this).val() == dblclickbuf.value)
|
240 |
+
{
|
241 |
+
dblclickbuf.value = '';
|
242 |
+
dblclickbuf.selected = false;
|
243 |
+
}
|
244 |
+
else
|
245 |
+
{
|
246 |
+
dblclickbuf.selected = true;
|
247 |
+
dblclickbuf.value = $(this).val();
|
248 |
+
$('.xml-element[title*="/'+$(this).val().replace('{','').replace('}','')+'"]').addClass('selected');
|
249 |
+
}
|
250 |
+
}
|
251 |
+
else
|
252 |
+
{
|
253 |
+
dblclickbuf.selected = true;
|
254 |
+
dblclickbuf.value = $(this).val();
|
255 |
+
$('.xml-element[title*="/'+$(this).val().replace('{','').replace('}','')+'"]').addClass('selected');
|
256 |
+
}
|
257 |
+
});
|
258 |
+
|
259 |
+
var insertxpath = function(){
|
260 |
+
if (dblclickbuf.selected)
|
261 |
+
{
|
262 |
+
$(this).val($(this).val() + dblclickbuf.value);
|
263 |
+
$('.xml-element[title*="/'+dblclickbuf.value.replace('{','').replace('}','')+'"]').removeClass('selected');
|
264 |
+
dblclickbuf.value = '';
|
265 |
+
dblclickbuf.selected = false;
|
266 |
+
}
|
267 |
+
}
|
268 |
+
$('#title, #content, .widefat, input[name^=custom_name], textarea[name^=custom_value], input[name^=featured_image], input[name^=unique_key]').bind('focus', insertxpath );
|
269 |
+
|
270 |
+
$(document).mousemove(function () {
|
271 |
+
if (parseInt($drag.css('opacity')) != 0) {
|
272 |
+
setTimeout(function () {
|
273 |
+
$drag.css('opacity', 0);
|
274 |
+
}, 50);
|
275 |
+
setTimeout(function () {
|
276 |
+
$drag.css('width', _w);
|
277 |
+
}, 500);
|
278 |
+
}
|
279 |
+
});
|
280 |
+
|
281 |
+
if ($('#content').length) tinymce.dom.Event.add('wp-content-editor-container', 'click', function(e) {
|
282 |
+
if (dblclickbuf.selected)
|
283 |
+
{
|
284 |
+
tinyMCE.activeEditor.selection.setContent(dblclickbuf.value);
|
285 |
+
$('.xml-element[title*="'+dblclickbuf.value.replace('{','').replace('}','')+'"]').removeClass('selected');
|
286 |
+
dblclickbuf.value = '';
|
287 |
+
dblclickbuf.selected = false;
|
288 |
+
}
|
289 |
+
});
|
290 |
+
|
291 |
+
this.find('.xml-tag.opening > .xml-tag-name, .xml-attr-name').each(function () {
|
292 |
+
var $this = $(this);
|
293 |
+
var xpath = '{' + (($this.is('.xml-attr-name') ? $this.parent() : $this.parent().parent()).attr('title').replace(/^\/[^\/]+\/?/, '') || '.') + '}';
|
294 |
+
|
295 |
+
$this.mouseover(function (e) {
|
296 |
+
$drag.val(xpath).offset({left: $this.offset().left - 2, top: $this.offset().top - 2}).width(_w = $this.width() + 4).height($this.height() + 4);
|
297 |
+
});
|
298 |
+
}).eq(0).mouseover();
|
299 |
+
}
|
300 |
+
return this;
|
301 |
+
};
|
302 |
+
|
303 |
+
// selection logic
|
304 |
+
$('form.choose-elements').each(function () {
|
305 |
+
var $form = $(this);
|
306 |
+
$form.find('.xml').xml();
|
307 |
+
var $input = $form.find('input[name="xpath"]');
|
308 |
+
$form.find('.xml-tag.opening').bind('mousedown', function () {return false;}).dblclick(function () {
|
309 |
+
if ($form.hasClass('loading')) return; // do nothing if selecting operation is currently under way
|
310 |
+
$input.val($(this).parents('.xml-element').first().attr('title').replace(/\[\d+\]$/, '')).change();
|
311 |
+
});
|
312 |
+
var xpathChanged = function () {
|
313 |
+
if ($input.val() == $input.data('checkedValue')) return;
|
314 |
+
$form.addClass('loading');
|
315 |
+
$form.find('.xml-element.selected').removeClass('selected'); // clear current selection
|
316 |
+
// request server to return elements which correspond to xpath entered
|
317 |
+
$input.attr('readonly', true).unbind('change', xpathChanged).data('checkedValue', $input.val());
|
318 |
+
$('.ajax-console').load('admin.php?page=pmxi-admin-import&action=evaluate', {xpath: $input.val()}, function () {
|
319 |
+
$input.attr('readonly', false).change(xpathChanged);
|
320 |
+
$form.removeClass('loading');
|
321 |
+
});
|
322 |
+
};
|
323 |
+
$input.change(xpathChanged).change();
|
324 |
+
$input.keyup(function (e) {
|
325 |
+
if (13 == e.keyCode) $(this).change();
|
326 |
+
});
|
327 |
+
});
|
328 |
+
|
329 |
+
// tag preview
|
330 |
+
$.fn.tag = function () {
|
331 |
+
this.each(function () {
|
332 |
+
var $tag = $(this);
|
333 |
+
$tag.xml('dragable');
|
334 |
+
var tagno = parseInt($tag.find('input[name="tagno"]').val());
|
335 |
+
$tag.find('.navigation a').click(function () {
|
336 |
+
tagno += '#prev' == $(this).attr('href') ? -1 : 1;
|
337 |
+
$tag.addClass('loading').css('opacity', 0.7);
|
338 |
+
$.post('admin.php?page=pmxi-admin-import&action=tag', {tagno: tagno}, function (data) {
|
339 |
+
var $indicator = $('<span />').insertBefore($tag);
|
340 |
+
$tag.replaceWith(data);
|
341 |
+
$indicator.next().tag().prevObject.remove();
|
342 |
+
}, 'html');
|
343 |
+
return false;
|
344 |
+
});
|
345 |
+
});
|
346 |
+
return this;
|
347 |
+
};
|
348 |
+
$('.tag').tag();
|
349 |
+
// [/xml representation dynamic]
|
350 |
+
|
351 |
+
$('input.autocomplete').each(function () {
|
352 |
+
$(this).autocomplete({
|
353 |
+
source: eval($(this).attr('id')),
|
354 |
+
minLength: 0
|
355 |
+
}).click(function () {
|
356 |
+
$(this).autocomplete('search', '');
|
357 |
+
});
|
358 |
+
});
|
359 |
+
|
360 |
+
function process(first_chank, update_previous){
|
361 |
+
$('input[name=is_first_chank],input[name=is_update_previous]').remove();
|
362 |
+
$("form").append('<input type="hidden" name="is_first_chank" value="'+((first_chank) ? 1 : 0)+'"/>');
|
363 |
+
$("form").append('<input type="hidden" name="is_update_previous" value="'+update_previous+'"/>');
|
364 |
+
|
365 |
+
$.ajax({
|
366 |
+
type: 'POST',
|
367 |
+
url: 'admin.php?page=pmxi-admin-import&action=options',
|
368 |
+
data: $("form").serialize(),
|
369 |
+
success: function(data) {
|
370 |
+
switch (data.status)
|
371 |
+
{
|
372 |
+
case 'process':
|
373 |
+
$('.wrap').append(data.log);
|
374 |
+
$(window).scrollTop($(document).height());
|
375 |
+
process(false, data.update_previous);
|
376 |
+
break;
|
377 |
+
case 'stop':
|
378 |
+
$('form.options').remove();
|
379 |
+
var import_stats = '';
|
380 |
+
import_stats = '<p><b> Import started at</b> ' + data.start_time + '</p>';
|
381 |
+
import_stats += '<p><b> Import ended at</b> ' + data.end_time + '</p>';
|
382 |
+
import_stats += '<p><b> Import time</b> ' + data.import_time + '</p>';
|
383 |
+
$('.wrap').append(data.log + import_stats);
|
384 |
+
break;
|
385 |
+
}
|
386 |
+
},
|
387 |
+
error: function(request, status, error){
|
388 |
+
$('form.options').hide();
|
389 |
+
var error = "<script type='text/javascript'>(function($){$('#status').html('Error');window.onbeforeunload = false;})(jQuery);</script>";
|
390 |
+
if (request.responseText.indexOf('options') === -1) $('.wrap').append(error + request.responseText); else $('form.options').append(error).submit();
|
391 |
+
},
|
392 |
+
dataType: "json"
|
393 |
+
});
|
394 |
+
}
|
395 |
+
|
396 |
+
$('.ajax-import').click(function(e){
|
397 |
+
e.preventDefault();
|
398 |
+
$('form').hide();
|
399 |
+
$('#process').show();
|
400 |
+
$('#status').each(function () {
|
401 |
+
var $this = $(this);
|
402 |
+
if ($this.html().match(/\.{3}$/)) {
|
403 |
+
var dots = 0;
|
404 |
+
var status = $this.html().replace(/\.{3}$/, '');
|
405 |
+
var interval ;
|
406 |
+
interval = setInterval(function () {
|
407 |
+
if ($this.html().match(new RegExp(status + '\\.{1,3}$', ''))) {
|
408 |
+
$this.html(status + '...'.substr(0, dots++ % 3 + 1));
|
409 |
+
} else {
|
410 |
+
clearInterval(interval);
|
411 |
+
}
|
412 |
+
}, 1000);
|
413 |
+
}
|
414 |
+
});
|
415 |
+
window.onbeforeunload = function () {
|
416 |
+
return 'WARNING:\nImport process in under way, leaving the page will interrupt\nthe operation and most likely to cause leftovers in posts.';
|
417 |
+
};
|
418 |
+
process(true, 0);
|
419 |
+
});
|
420 |
+
|
421 |
+
});})(jQuery);
|
static/js/jquery/css/smoothness/images/tipsy.gif
ADDED
Binary file
|
static/js/jquery/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png
ADDED
Binary file
|
static/js/jquery/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png
ADDED
Binary file
|
static/js/jquery/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png
ADDED
Binary file
|
static/js/jquery/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png
ADDED
Binary file
|
static/js/jquery/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png
ADDED
Binary file
|
static/js/jquery/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png
ADDED
Binary file
|
static/js/jquery/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png
ADDED
Binary file
|
static/js/jquery/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png
ADDED
Binary file
|
static/js/jquery/css/smoothness/images/ui-icons_222222_256x240.png
ADDED
Binary file
|
static/js/jquery/css/smoothness/images/ui-icons_2e83ff_256x240.png
ADDED
Binary file
|
static/js/jquery/css/smoothness/images/ui-icons_454545_256x240.png
ADDED
Binary file
|
static/js/jquery/css/smoothness/images/ui-icons_888888_256x240.png
ADDED
Binary file
|
static/js/jquery/css/smoothness/images/ui-icons_cd0a0a_256x240.png
ADDED
Binary file
|
static/js/jquery/css/smoothness/jquery-ui.css
ADDED
@@ -0,0 +1,405 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
* jQuery UI CSS Framework
|
3 |
+
* Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
|
4 |
+
* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
|
5 |
+
*/
|
6 |
+
|
7 |
+
/* Layout helpers
|
8 |
+
----------------------------------*/
|
9 |
+
.ui-helper-hidden { display: none; }
|
10 |
+
.ui-helper-hidden-accessible { position: absolute; left: -99999999px; }
|
11 |
+
.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
|
12 |
+
.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
|
13 |
+
.ui-helper-clearfix { display: inline-block; }
|
14 |
+
/* required comment for clearfix to work in Opera \*/
|
15 |
+
* html .ui-helper-clearfix { height:1%; }
|
16 |
+
.ui-helper-clearfix { display:block; }
|
17 |
+
/* end clearfix */
|
18 |
+
.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
|
19 |
+
|
20 |
+
|
21 |
+
/* Interaction Cues
|
22 |
+
----------------------------------*/
|
23 |
+
.ui-state-disabled { cursor: default !important; }
|
24 |
+
|
25 |
+
|
26 |
+
/* Icons
|
27 |
+
----------------------------------*/
|
28 |
+
|
29 |
+
/* states and images */
|
30 |
+
.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
|
31 |
+
|
32 |
+
|
33 |
+
/* Misc visuals
|
34 |
+
----------------------------------*/
|
35 |
+
|
36 |
+
/* Overlays */
|
37 |
+
.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
|
38 |
+
|
39 |
+
/*
|
40 |
+
* jQuery UI CSS Framework
|
41 |
+
* Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
|
42 |
+
* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
|
43 |
+
* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana,Arial,sans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=03_highlight_soft.png&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=01_flat.png&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=75&borderColorDefault=d3d3d3&fcDefault=555555&iconColorDefault=888888&bgColorHover=dadada&bgTextureHover=02_glass.png&bgImgOpacityHover=75&borderColorHover=999999&fcHover=212121&iconColorHover=454545&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=aaaaaa&fcActive=212121&iconColorActive=454545&bgColorHighlight=fbf9ee&bgTextureHighlight=02_glass.png&bgImgOpacityHighlight=55&borderColorHighlight=fcefa1&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=02_glass.png&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=01_flat.png&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px
|
44 |
+
*/
|
45 |
+
|
46 |
+
|
47 |
+
/* Component containers
|
48 |
+
----------------------------------*/
|
49 |
+
.ui-widget { font-family: Verdana,Arial,sans-serif; font-size: 0.85em; }
|
50 |
+
.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif; font-size: 1em; }
|
51 |
+
.ui-widget-content { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x; color: #222222; }
|
52 |
+
.ui-widget-content a { color: #222222; }
|
53 |
+
.ui-widget-header { border: 1px solid #aaaaaa; background: #cccccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x; color: #222222; font-weight: bold; }
|
54 |
+
.ui-widget-header a { color: #222222; }
|
55 |
+
|
56 |
+
/* Interaction states
|
57 |
+
----------------------------------*/
|
58 |
+
.ui-state-default, .ui-widget-content .ui-state-default { border: 1px solid #d3d3d3; background: #e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #555555; outline: none; }
|
59 |
+
.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #555555; text-decoration: none; outline: none; }
|
60 |
+
.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus { border: 1px solid #999999; background: #dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; outline: none; }
|
61 |
+
.ui-state-hover a, .ui-state-hover a:hover { color: #212121; text-decoration: none; outline: none; }
|
62 |
+
.ui-state-active, .ui-widget-content .ui-state-active { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; outline: none; }
|
63 |
+
.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #212121; outline: none; text-decoration: none; }
|
64 |
+
|
65 |
+
/* Interaction Cues
|
66 |
+
----------------------------------*/
|
67 |
+
.ui-state-highlight, .ui-widget-content .ui-state-highlight {border: 1px solid #fcefa1; background: #fbf9ee url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x; color: #363636; }
|
68 |
+
.ui-state-highlight a, .ui-widget-content .ui-state-highlight a { color: #363636; }
|
69 |
+
.ui-state-error, .ui-widget-content .ui-state-error {border: 1px solid #cd0a0a; background: #fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x; color: #cd0a0a; }
|
70 |
+
.ui-state-error a, .ui-widget-content .ui-state-error a { color: #cd0a0a; }
|
71 |
+
.ui-state-error-text, .ui-widget-content .ui-state-error-text { color: #cd0a0a; }
|
72 |
+
.ui-state-disabled, .ui-widget-content .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
|
73 |
+
.ui-priority-primary, .ui-widget-content .ui-priority-primary { font-weight: bold; }
|
74 |
+
.ui-priority-secondary, .ui-widget-content .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
|
75 |
+
|
76 |
+
/* Icons
|
77 |
+
----------------------------------*/
|
78 |
+
|
79 |
+
/* states and images */
|
80 |
+
.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); }
|
81 |
+
.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
|
82 |
+
.ui-widget-header .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
|
83 |
+
.ui-state-default .ui-icon { background-image: url(images/ui-icons_888888_256x240.png); }
|
84 |
+
.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); }
|
85 |
+
.ui-state-active .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); }
|
86 |
+
.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png); }
|
87 |
+
.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png); }
|
88 |
+
|
89 |
+
/* positioning */
|
90 |
+
.ui-icon-carat-1-n { background-position: 0 0; }
|
91 |
+
.ui-icon-carat-1-ne { background-position: -16px 0; }
|
92 |
+
.ui-icon-carat-1-e { background-position: -32px 0; }
|
93 |
+
.ui-icon-carat-1-se { background-position: -48px 0; }
|
94 |
+
.ui-icon-carat-1-s { background-position: -64px 0; }
|
95 |
+
.ui-icon-carat-1-sw { background-position: -80px 0; }
|
96 |
+
.ui-icon-carat-1-w { background-position: -96px 0; }
|
97 |
+
.ui-icon-carat-1-nw { background-position: -112px 0; }
|
98 |
+
.ui-icon-carat-2-n-s { background-position: -128px 0; }
|
99 |
+
.ui-icon-carat-2-e-w { background-position: -144px 0; }
|
100 |
+
.ui-icon-triangle-1-n { background-position: 0 -16px; }
|
101 |
+
.ui-icon-triangle-1-ne { background-position: -16px -16px; }
|
102 |
+
.ui-icon-triangle-1-e { background-position: -32px -16px; }
|
103 |
+
.ui-icon-triangle-1-se { background-position: -48px -16px; }
|
104 |
+
.ui-icon-triangle-1-s { background-position: -64px -16px; }
|
105 |
+
.ui-icon-triangle-1-sw { background-position: -80px -16px; }
|
106 |
+
.ui-icon-triangle-1-w { background-position: -96px -16px; }
|
107 |
+
.ui-icon-triangle-1-nw { background-position: -112px -16px; }
|
108 |
+
.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
|
109 |
+
.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
|
110 |
+
.ui-icon-arrow-1-n { background-position: 0 -32px; }
|
111 |
+
.ui-icon-arrow-1-ne { background-position: -16px -32px; }
|
112 |
+
.ui-icon-arrow-1-e { background-position: -32px -32px; }
|
113 |
+
.ui-icon-arrow-1-se { background-position: -48px -32px; }
|
114 |
+
.ui-icon-arrow-1-s { background-position: -64px -32px; }
|
115 |
+
.ui-icon-arrow-1-sw { background-position: -80px -32px; }
|
116 |
+
.ui-icon-arrow-1-w { background-position: -96px -32px; }
|
117 |
+
.ui-icon-arrow-1-nw { background-position: -112px -32px; }
|
118 |
+
.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
|
119 |
+
.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
|
120 |
+
.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
|
121 |
+
.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
|
122 |
+
.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
|
123 |
+
.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
|
124 |
+
.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
|
125 |
+
.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
|
126 |
+
.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
|
127 |
+
.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
|
128 |
+
.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
|
129 |
+
.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
|
130 |
+
.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
|
131 |
+
.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
|
132 |
+
.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
|
133 |
+
.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
|
134 |
+
.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
|
135 |
+
.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
|
136 |
+
.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
|
137 |
+
.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
|
138 |
+
.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
|
139 |
+
.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
|
140 |
+
.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
|
141 |
+
.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
|
142 |
+
.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
|
143 |
+
.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
|
144 |
+
.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
|
145 |
+
.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
|
146 |
+
.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
|
147 |
+
.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
|
148 |
+
.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
|
149 |
+
.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
|
150 |
+
.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
|
151 |
+
.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
|
152 |
+
.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
|
153 |
+
.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
|
154 |
+
.ui-icon-arrow-4 { background-position: 0 -80px; }
|
155 |
+
.ui-icon-arrow-4-diag { background-position: -16px -80px; }
|
156 |
+
.ui-icon-extlink { background-position: -32px -80px; }
|
157 |
+
.ui-icon-newwin { background-position: -48px -80px; }
|
158 |
+
.ui-icon-refresh { background-position: -64px -80px; }
|
159 |
+
.ui-icon-shuffle { background-position: -80px -80px; }
|
160 |
+
.ui-icon-transfer-e-w { background-position: -96px -80px; }
|
161 |
+
.ui-icon-transferthick-e-w { background-position: -112px -80px; }
|
162 |
+
.ui-icon-folder-collapsed { background-position: 0 -96px; }
|
163 |
+
.ui-icon-folder-open { background-position: -16px -96px; }
|
164 |
+
.ui-icon-document { background-position: -32px -96px; }
|
165 |
+
.ui-icon-document-b { background-position: -48px -96px; }
|
166 |
+
.ui-icon-note { background-position: -64px -96px; }
|
167 |
+
.ui-icon-mail-closed { background-position: -80px -96px; }
|
168 |
+
.ui-icon-mail-open { background-position: -96px -96px; }
|
169 |
+
.ui-icon-suitcase { background-position: -112px -96px; }
|
170 |
+
.ui-icon-comment { background-position: -128px -96px; }
|
171 |
+
.ui-icon-person { background-position: -144px -96px; }
|
172 |
+
.ui-icon-print { background-position: -160px -96px; }
|
173 |
+
.ui-icon-trash { background-position: -176px -96px; }
|
174 |
+
.ui-icon-locked { background-position: -192px -96px; }
|
175 |
+
.ui-icon-unlocked { background-position: -208px -96px; }
|
176 |
+
.ui-icon-bookmark { background-position: -224px -96px; }
|
177 |
+
.ui-icon-tag { background-position: -240px -96px; }
|
178 |
+
.ui-icon-home { background-position: 0 -112px; }
|
179 |
+
.ui-icon-flag { background-position: -16px -112px; }
|
180 |
+
.ui-icon-calendar { background-position: -32px -112px; }
|
181 |
+
.ui-icon-cart { background-position: -48px -112px; }
|
182 |
+
.ui-icon-pencil { background-position: -64px -112px; }
|
183 |
+
.ui-icon-clock { background-position: -80px -112px; }
|
184 |
+
.ui-icon-disk { background-position: -96px -112px; }
|
185 |
+
.ui-icon-calculator { background-position: -112px -112px; }
|
186 |
+
.ui-icon-zoomin { background-position: -128px -112px; }
|
187 |
+
.ui-icon-zoomout { background-position: -144px -112px; }
|
188 |
+
.ui-icon-search { background-position: -160px -112px; }
|
189 |
+
.ui-icon-wrench { background-position: -176px -112px; }
|
190 |
+
.ui-icon-gear { background-position: -192px -112px; }
|
191 |
+
.ui-icon-heart { background-position: -208px -112px; }
|
192 |
+
.ui-icon-star { background-position: -224px -112px; }
|
193 |
+
.ui-icon-link { background-position: -240px -112px; }
|
194 |
+
.ui-icon-cancel { background-position: 0 -128px; }
|
195 |
+
.ui-icon-plus { background-position: -16px -128px; }
|
196 |
+
.ui-icon-plusthick { background-position: -32px -128px; }
|
197 |
+
.ui-icon-minus { background-position: -48px -128px; }
|
198 |
+
.ui-icon-minusthick { background-position: -64px -128px; }
|
199 |
+
.ui-icon-close { background-position: -80px -128px; }
|
200 |
+
.ui-icon-closethick { background-position: -96px -128px; }
|
201 |
+
.ui-icon-key { background-position: -112px -128px; }
|
202 |
+
.ui-icon-lightbulb { background-position: -128px -128px; }
|
203 |
+
.ui-icon-scissors { background-position: -144px -128px; }
|
204 |
+
.ui-icon-clipboard { background-position: -160px -128px; }
|
205 |
+
.ui-icon-copy { background-position: -176px -128px; }
|
206 |
+
.ui-icon-contact { background-position: -192px -128px; }
|
207 |
+
.ui-icon-image { background-position: -208px -128px; }
|
208 |
+
.ui-icon-video { background-position: -224px -128px; }
|
209 |
+
.ui-icon-script { background-position: -240px -128px; }
|
210 |
+
.ui-icon-alert { background-position: 0 -144px; }
|
211 |
+
.ui-icon-info { background-position: -16px -144px; }
|
212 |
+
.ui-icon-notice { background-position: -32px -144px; }
|
213 |
+
.ui-icon-help { background-position: -48px -144px; }
|
214 |
+
.ui-icon-check { background-position: -64px -144px; }
|
215 |
+
.ui-icon-bullet { background-position: -80px -144px; }
|
216 |
+
.ui-icon-radio-off { background-position: -96px -144px; }
|
217 |
+
.ui-icon-radio-on { background-position: -112px -144px; }
|
218 |
+
.ui-icon-pin-w { background-position: -128px -144px; }
|
219 |
+
.ui-icon-pin-s { background-position: -144px -144px; }
|
220 |
+
.ui-icon-play { background-position: 0 -160px; }
|
221 |
+
.ui-icon-pause { background-position: -16px -160px; }
|
222 |
+
.ui-icon-seek-next { background-position: -32px -160px; }
|
223 |
+
.ui-icon-seek-prev { background-position: -48px -160px; }
|
224 |
+
.ui-icon-seek-end { background-position: -64px -160px; }
|
225 |
+
.ui-icon-seek-first { background-position: -80px -160px; }
|
226 |
+
.ui-icon-stop { background-position: -96px -160px; }
|
227 |
+
.ui-icon-eject { background-position: -112px -160px; }
|
228 |
+
.ui-icon-volume-off { background-position: -128px -160px; }
|
229 |
+
.ui-icon-volume-on { background-position: -144px -160px; }
|
230 |
+
.ui-icon-power { background-position: 0 -176px; }
|
231 |
+
.ui-icon-signal-diag { background-position: -16px -176px; }
|
232 |
+
.ui-icon-signal { background-position: -32px -176px; }
|
233 |
+
.ui-icon-battery-0 { background-position: -48px -176px; }
|
234 |
+
.ui-icon-battery-1 { background-position: -64px -176px; }
|
235 |
+
.ui-icon-battery-2 { background-position: -80px -176px; }
|
236 |
+
.ui-icon-battery-3 { background-position: -96px -176px; }
|
237 |
+
.ui-icon-circle-plus { background-position: 0 -192px; }
|
238 |
+
.ui-icon-circle-minus { background-position: -16px -192px; }
|
239 |
+
.ui-icon-circle-close { background-position: -32px -192px; }
|
240 |
+
.ui-icon-circle-triangle-e { background-position: -48px -192px; }
|
241 |
+
.ui-icon-circle-triangle-s { background-position: -64px -192px; }
|
242 |
+
.ui-icon-circle-triangle-w { background-position: -80px -192px; }
|
243 |
+
.ui-icon-circle-triangle-n { background-position: -96px -192px; }
|
244 |
+
.ui-icon-circle-arrow-e { background-position: -112px -192px; }
|
245 |
+
.ui-icon-circle-arrow-s { background-position: -128px -192px; }
|
246 |
+
.ui-icon-circle-arrow-w { background-position: -144px -192px; }
|
247 |
+
.ui-icon-circle-arrow-n { background-position: -160px -192px; }
|
248 |
+
.ui-icon-circle-zoomin { background-position: -176px -192px; }
|
249 |
+
.ui-icon-circle-zoomout { background-position: -192px -192px; }
|
250 |
+
.ui-icon-circle-check { background-position: -208px -192px; }
|
251 |
+
.ui-icon-circlesmall-plus { background-position: 0 -208px; }
|
252 |
+
.ui-icon-circlesmall-minus { background-position: -16px -208px; }
|
253 |
+
.ui-icon-circlesmall-close { background-position: -32px -208px; }
|
254 |
+
.ui-icon-squaresmall-plus { background-position: -48px -208px; }
|
255 |
+
.ui-icon-squaresmall-minus { background-position: -64px -208px; }
|
256 |
+
.ui-icon-squaresmall-close { background-position: -80px -208px; }
|
257 |
+
.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
|
258 |
+
.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
|
259 |
+
.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
|
260 |
+
.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
|
261 |
+
.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
|
262 |
+
.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
|
263 |
+
|
264 |
+
|
265 |
+
/* Misc visuals
|
266 |
+
----------------------------------*/
|
267 |
+
|
268 |
+
/* Corner radius */
|
269 |
+
.ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; }
|
270 |
+
.ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; }
|
271 |
+
.ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; }
|
272 |
+
.ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; }
|
273 |
+
.ui-corner-top { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; }
|
274 |
+
.ui-corner-bottom { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; }
|
275 |
+
.ui-corner-right { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; }
|
276 |
+
.ui-corner-left { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; }
|
277 |
+
.ui-corner-all { -moz-border-radius: 4px; -webkit-border-radius: 4px; }
|
278 |
+
|
279 |
+
/* Overlays */
|
280 |
+
.ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); }
|
281 |
+
.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -webkit-border-radius: 8px; }/* Resizable
|
282 |
+
----------------------------------*/
|
283 |
+
.ui-resizable { position: relative;}
|
284 |
+
.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;}
|
285 |
+
.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
|
286 |
+
.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0px; }
|
287 |
+
.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0px; }
|
288 |
+
.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0px; height: 100%; }
|
289 |
+
.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0px; height: 100%; }
|
290 |
+
.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
|
291 |
+
.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
|
292 |
+
.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
|
293 |
+
.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/* Accordion
|
294 |
+
----------------------------------*/
|
295 |
+
.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; }
|
296 |
+
.ui-accordion .ui-accordion-li-fix { display: inline; }
|
297 |
+
.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; }
|
298 |
+
.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em 2.2em; }
|
299 |
+
.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; }
|
300 |
+
.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; }
|
301 |
+
.ui-accordion .ui-accordion-content-active { display: block; }/* Dialog
|
302 |
+
----------------------------------*/
|
303 |
+
.ui-dialog { position: relative; padding: .2em; width: 300px; }
|
304 |
+
.ui-dialog .ui-dialog-titlebar { padding: .5em .3em .3em 1em; position: relative; }
|
305 |
+
.ui-dialog .ui-dialog-title { float: left; margin: .1em 0 .2em; }
|
306 |
+
.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; }
|
307 |
+
.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }
|
308 |
+
.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; }
|
309 |
+
.ui-dialog .ui-dialog-content { border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; }
|
310 |
+
.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; }
|
311 |
+
.ui-dialog .ui-dialog-buttonpane button { float: right; margin: .5em .4em .5em 0; cursor: pointer; padding: .2em .6em .3em .6em; line-height: 1.4em; width:auto; overflow:visible; }
|
312 |
+
.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; }
|
313 |
+
.ui-draggable .ui-dialog-titlebar { cursor: move; }
|
314 |
+
/* Slider
|
315 |
+
----------------------------------*/
|
316 |
+
.ui-slider { position: relative; text-align: left; }
|
317 |
+
.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; }
|
318 |
+
.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; }
|
319 |
+
|
320 |
+
.ui-slider-horizontal { height: .8em; }
|
321 |
+
.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; }
|
322 |
+
.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; }
|
323 |
+
.ui-slider-horizontal .ui-slider-range-min { left: 0; }
|
324 |
+
.ui-slider-horizontal .ui-slider-range-max { right: 0; }
|
325 |
+
|
326 |
+
.ui-slider-vertical { width: .8em; height: 100px; }
|
327 |
+
.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; }
|
328 |
+
.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; }
|
329 |
+
.ui-slider-vertical .ui-slider-range-min { bottom: 0; }
|
330 |
+
.ui-slider-vertical .ui-slider-range-max { top: 0; }/* Tabs
|
331 |
+
----------------------------------*/
|
332 |
+
.ui-tabs { padding: .2em; zoom: 1; }
|
333 |
+
.ui-tabs .ui-tabs-nav { list-style: none; position: relative; padding: .2em .2em 0; }
|
334 |
+
.ui-tabs .ui-tabs-nav li { position: relative; float: left; border-bottom-width: 0 !important; margin: 0 .2em -1px 0; padding: 0; }
|
335 |
+
.ui-tabs .ui-tabs-nav li a { float: left; text-decoration: none; padding: .5em 1em; }
|
336 |
+
.ui-tabs .ui-tabs-nav li.ui-tabs-selected { padding-bottom: 1px; border-bottom-width: 0; }
|
337 |
+
.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; }
|
338 |
+
.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
|
339 |
+
.ui-tabs .ui-tabs-panel { padding: 1em 1.4em; display: block; border-width: 0; background: none; }
|
340 |
+
.ui-tabs .ui-tabs-hide { display: none !important; }
|
341 |
+
/* Datepicker
|
342 |
+
----------------------------------*/
|
343 |
+
.ui-datepicker { width: 17em; padding: .2em .2em 0; }
|
344 |
+
.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
|
345 |
+
.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
|
346 |
+
.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
|
347 |
+
.ui-datepicker .ui-datepicker-prev { left:2px; }
|
348 |
+
.ui-datepicker .ui-datepicker-next { right:2px; }
|
349 |
+
.ui-datepicker .ui-datepicker-prev-hover { left:1px; }
|
350 |
+
.ui-datepicker .ui-datepicker-next-hover { right:1px; }
|
351 |
+
.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; }
|
352 |
+
.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
|
353 |
+
.ui-datepicker .ui-datepicker-title select { float:left; font-size:1em; margin:1px 0; }
|
354 |
+
.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
|
355 |
+
.ui-datepicker select.ui-datepicker-month,
|
356 |
+
.ui-datepicker select.ui-datepicker-year { width: 49%;}
|
357 |
+
.ui-datepicker .ui-datepicker-title select.ui-datepicker-year { float: right; }
|
358 |
+
.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
|
359 |
+
.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; }
|
360 |
+
.ui-datepicker td { border: 0; padding: 1px; }
|
361 |
+
.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
|
362 |
+
.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
|
363 |
+
.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
|
364 |
+
.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
|
365 |
+
|
366 |
+
/* with multiple calendars */
|
367 |
+
.ui-datepicker.ui-datepicker-multi { width:auto; }
|
368 |
+
.ui-datepicker-multi .ui-datepicker-group { float:left; }
|
369 |
+
.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
|
370 |
+
.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
|
371 |
+
.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
|
372 |
+
.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
|
373 |
+
.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
|
374 |
+
.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
|
375 |
+
.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
|
376 |
+
.ui-datepicker-row-break { clear:both; width:100%; }
|
377 |
+
|
378 |
+
/* RTL support */
|
379 |
+
.ui-datepicker-rtl { direction: rtl; }
|
380 |
+
.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
|
381 |
+
.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
|
382 |
+
.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
|
383 |
+
.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
|
384 |
+
.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
|
385 |
+
.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
|
386 |
+
.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
|
387 |
+
.ui-datepicker-rtl .ui-datepicker-group { float:right; }
|
388 |
+
.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
|
389 |
+
.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
|
390 |
+
|
391 |
+
/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */
|
392 |
+
.ui-datepicker-cover {
|
393 |
+
display: none; /*sorry for IE5*/
|
394 |
+
display/**/: block; /*sorry for IE5*/
|
395 |
+
position: absolute; /*must have*/
|
396 |
+
z-index: -1; /*must have*/
|
397 |
+
filter: mask(); /*must have*/
|
398 |
+
top: -4px; /*must have*/
|
399 |
+
left: -4px; /*must have*/
|
400 |
+
width: 200px; /*must have*/
|
401 |
+
height: 200px; /*must have*/
|
402 |
+
}/* Progressbar
|
403 |
+
----------------------------------*/
|
404 |
+
.ui-progressbar { height:2em; text-align: left; }
|
405 |
+
.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; }
|
static/js/jquery/css/smoothness/jquery.tipsy.css
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.tipsy { padding: 5px; font-size: 0.85em; position: absolute; z-index: 100000; line-height: 1.3em; max-width: 320px;}
|
2 |
+
.tipsy-inner { padding: 2px 4px 2px 4px; background-color: white; border: 1px solid #333;}
|
3 |
+
.tipsy-arrow { position: absolute; background: url('images/tipsy.gif') no-repeat top left; width: 9px; height: 5px; }
|
4 |
+
.tipsy-n .tipsy-arrow { top: 0; left: 50%; margin-left: -4px; }
|
5 |
+
.tipsy-nw .tipsy-arrow { top: 0; left: 10px; }
|
6 |
+
.tipsy-ne .tipsy-arrow { top: 0; right: 10px; }
|
7 |
+
.tipsy-s .tipsy-arrow { bottom: 0; left: 50%; margin-left: -4px; background-position: bottom left; }
|
8 |
+
.tipsy-sw .tipsy-arrow { bottom: 0; left: 10px; background-position: bottom left; }
|
9 |
+
.tipsy-se .tipsy-arrow { bottom: 0; right: 10px; background-position: bottom left; }
|
10 |
+
.tipsy-e .tipsy-arrow { top: 50%; margin-top: -4px; right: 0; width: 5px; height: 9px; background-position: top right; }
|
11 |
+
.tipsy-w .tipsy-arrow { top: 50%; margin-top: -4px; left: 0; width: 5px; height: 9px; }
|
static/js/jquery/jquery.tipsy.js
ADDED
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// tipsy, facebook style tooltips for jquery
|
2 |
+
// version 1.0.0a
|
3 |
+
// (c) 2008-2010 jason frame [jason@onehackoranother.com]
|
4 |
+
// releated under the MIT license
|
5 |
+
|
6 |
+
(function($) {
|
7 |
+
|
8 |
+
function fixTitle($ele) {
|
9 |
+
if ($ele.attr('title') || typeof($ele.attr('original-title')) != 'string') {
|
10 |
+
$ele.attr('original-title', $ele.attr('title') || '').removeAttr('title');
|
11 |
+
}
|
12 |
+
}
|
13 |
+
|
14 |
+
function Tipsy(element, options) {
|
15 |
+
this.$element = $(element);
|
16 |
+
this.options = options;
|
17 |
+
this.enabled = true;
|
18 |
+
fixTitle(this.$element);
|
19 |
+
}
|
20 |
+
|
21 |
+
Tipsy.prototype = {
|
22 |
+
show: function() {
|
23 |
+
var title = this.getTitle();
|
24 |
+
if (title && this.enabled) {
|
25 |
+
var $tip = this.tip();
|
26 |
+
|
27 |
+
$tip.find('.tipsy-inner')[this.options.html ? 'html' : 'text'](title);
|
28 |
+
$tip[0].className = 'tipsy'; // reset classname in case of dynamic gravity
|
29 |
+
$tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).appendTo(document.body);
|
30 |
+
|
31 |
+
var pos = $.extend({}, this.$element.offset(), {
|
32 |
+
width: this.$element[0].offsetWidth,
|
33 |
+
height: this.$element[0].offsetHeight
|
34 |
+
});
|
35 |
+
|
36 |
+
var actualWidth = $tip[0].offsetWidth, actualHeight = $tip[0].offsetHeight;
|
37 |
+
var gravity = (typeof this.options.gravity == 'function')
|
38 |
+
? this.options.gravity.call(this.$element[0])
|
39 |
+
: this.options.gravity;
|
40 |
+
|
41 |
+
var tp;
|
42 |
+
switch (gravity.charAt(0)) {
|
43 |
+
case 'n':
|
44 |
+
tp = {top: pos.top + pos.height + this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
|
45 |
+
break;
|
46 |
+
case 's':
|
47 |
+
tp = {top: pos.top - actualHeight - this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
|
48 |
+
break;
|
49 |
+
case 'e':
|
50 |
+
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth - this.options.offset};
|
51 |
+
break;
|
52 |
+
case 'w':
|
53 |
+
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width + this.options.offset};
|
54 |
+
break;
|
55 |
+
}
|
56 |
+
|
57 |
+
if (gravity.length == 2) {
|
58 |
+
if (gravity.charAt(1) == 'w') {
|
59 |
+
tp.left = pos.left + pos.width / 2 - 15;
|
60 |
+
} else {
|
61 |
+
tp.left = pos.left + pos.width / 2 - actualWidth + 15;
|
62 |
+
}
|
63 |
+
}
|
64 |
+
|
65 |
+
$tip.css(tp).addClass('tipsy-' + gravity);
|
66 |
+
|
67 |
+
if (this.options.fade) {
|
68 |
+
$tip.stop().css({opacity: 0, display: 'block', visibility: 'visible'}).animate({opacity: this.options.opacity});
|
69 |
+
} else {
|
70 |
+
$tip.css({visibility: 'visible', opacity: this.options.opacity});
|
71 |
+
}
|
72 |
+
}
|
73 |
+
},
|
74 |
+
|
75 |
+
hide: function() {
|
76 |
+
if (this.options.fade) {
|
77 |
+
this.tip().stop().fadeOut(function() { $(this).remove(); });
|
78 |
+
} else {
|
79 |
+
this.tip().remove();
|
80 |
+
}
|
81 |
+
},
|
82 |
+
|
83 |
+
getTitle: function() {
|
84 |
+
var title, $e = this.$element, o = this.options;
|
85 |
+
fixTitle($e);
|
86 |
+
var title, o = this.options;
|
87 |
+
if (typeof o.title == 'string') {
|
88 |
+
title = $e.attr(o.title == 'title' ? 'original-title' : o.title);
|
89 |
+
} else if (typeof o.title == 'function') {
|
90 |
+
title = o.title.call($e[0]);
|
91 |
+
}
|
92 |
+
title = ('' + title).replace(/(^\s*|\s*$)/, "");
|
93 |
+
return title || o.fallback;
|
94 |
+
},
|
95 |
+
|
96 |
+
tip: function() {
|
97 |
+
if (!this.$tip) {
|
98 |
+
this.$tip = $('<div class="tipsy"></div>').html('<div class="tipsy-arrow"></div><div class="tipsy-inner"/></div>');
|
99 |
+
}
|
100 |
+
return this.$tip;
|
101 |
+
},
|
102 |
+
|
103 |
+
validate: function() {
|
104 |
+
if (!this.$element[0].parentNode) {
|
105 |
+
this.hide();
|
106 |
+
this.$element = null;
|
107 |
+
this.options = null;
|
108 |
+
}
|
109 |
+
},
|
110 |
+
|
111 |
+
enable: function() { this.enabled = true; },
|
112 |
+
disable: function() { this.enabled = false; },
|
113 |
+
toggleEnabled: function() { this.enabled = !this.enabled; }
|
114 |
+
};
|
115 |
+
|
116 |
+
$.fn.tipsy = function(options) {
|
117 |
+
|
118 |
+
if (options === true) {
|
119 |
+
return this.data('tipsy');
|
120 |
+
} else if (typeof options == 'string') {
|
121 |
+
return this.data('tipsy')[options]();
|
122 |
+
}
|
123 |
+
|
124 |
+
options = $.extend({}, $.fn.tipsy.defaults, options);
|
125 |
+
|
126 |
+
function get(ele) {
|
127 |
+
var tipsy = $.data(ele, 'tipsy');
|
128 |
+
if (!tipsy) {
|
129 |
+
tipsy = new Tipsy(ele, $.fn.tipsy.elementOptions(ele, options));
|
130 |
+
$.data(ele, 'tipsy', tipsy);
|
131 |
+
}
|
132 |
+
return tipsy;
|
133 |
+
}
|
134 |
+
|
135 |
+
function enter() {
|
136 |
+
var tipsy = get(this);
|
137 |
+
tipsy.hoverState = 'in';
|
138 |
+
if (options.delayIn == 0) {
|
139 |
+
tipsy.show();
|
140 |
+
} else {
|
141 |
+
setTimeout(function() { if (tipsy.hoverState == 'in') tipsy.show(); }, options.delayIn);
|
142 |
+
}
|
143 |
+
};
|
144 |
+
|
145 |
+
function leave() {
|
146 |
+
var tipsy = get(this);
|
147 |
+
tipsy.hoverState = 'out';
|
148 |
+
if (options.delayOut == 0) {
|
149 |
+
tipsy.hide();
|
150 |
+
} else {
|
151 |
+
setTimeout(function() { if (tipsy.hoverState == 'out') tipsy.hide(); }, options.delayOut);
|
152 |
+
}
|
153 |
+
};
|
154 |
+
|
155 |
+
if (!options.live) this.each(function() { get(this); });
|
156 |
+
|
157 |
+
if (options.trigger != 'manual') {
|
158 |
+
var binder = options.live ? 'live' : 'bind',
|
159 |
+
eventIn = options.trigger == 'hover' ? 'mouseenter' : 'focus',
|
160 |
+
eventOut = options.trigger == 'hover' ? 'mouseleave' : 'blur';
|
161 |
+
this[binder](eventIn, enter)[binder](eventOut, leave);
|
162 |
+
}
|
163 |
+
|
164 |
+
return this;
|
165 |
+
|
166 |
+
};
|
167 |
+
|
168 |
+
$.fn.tipsy.defaults = {
|
169 |
+
delayIn: 0,
|
170 |
+
delayOut: 0,
|
171 |
+
fade: false,
|
172 |
+
fallback: '',
|
173 |
+
gravity: 'n',
|
174 |
+
html: false,
|
175 |
+
live: false,
|
176 |
+
offset: 0,
|
177 |
+
opacity: 0.8,
|
178 |
+
title: 'title',
|
179 |
+
trigger: 'hover'
|
180 |
+
};
|
181 |
+
|
182 |
+
// Overwrite this method to provide options on a per-element basis.
|
183 |
+
// For example, you could store the gravity in a 'tipsy-gravity' attribute:
|
184 |
+
// return $.extend({}, options, {gravity: $(ele).attr('tipsy-gravity') || 'n' });
|
185 |
+
// (remember - do not modify 'options' in place!)
|
186 |
+
$.fn.tipsy.elementOptions = function(ele, options) {
|
187 |
+
return $.metadata ? $.extend({}, options, $(ele).metadata()) : options;
|
188 |
+
};
|
189 |
+
|
190 |
+
$.fn.tipsy.autoNS = function() {
|
191 |
+
return $(this).offset().top > ($(document).scrollTop() + $(window).height() / 2) ? 's' : 'n';
|
192 |
+
};
|
193 |
+
|
194 |
+
$.fn.tipsy.autoWE = function() {
|
195 |
+
return $(this).offset().left > ($(document).scrollLeft() + $(window).width() / 2) ? 'e' : 'w';
|
196 |
+
};
|
197 |
+
|
198 |
+
})(jQuery);
|
static/js/jquery/ui.autocomplete.js
ADDED
@@ -0,0 +1,606 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
* jQuery UI Autocomplete 1.8.10
|
3 |
+
*
|
4 |
+
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
|
5 |
+
* Dual licensed under the MIT or GPL Version 2 licenses.
|
6 |
+
* http://jquery.org/license
|
7 |
+
*
|
8 |
+
* http://docs.jquery.com/UI/Autocomplete
|
9 |
+
*
|
10 |
+
* Depends:
|
11 |
+
* jquery.ui.core.js
|
12 |
+
* jquery.ui.widget.js
|
13 |
+
* jquery.ui.position.js
|
14 |
+
*/
|
15 |
+
(function( $, undefined ) {
|
16 |
+
|
17 |
+
// used to prevent race conditions with remote data sources
|
18 |
+
var requestIndex = 0;
|
19 |
+
|
20 |
+
$.widget( "ui.autocomplete", {
|
21 |
+
options: {
|
22 |
+
appendTo: "body",
|
23 |
+
delay: 300,
|
24 |
+
minLength: 1,
|
25 |
+
position: {
|
26 |
+
my: "left top",
|
27 |
+
at: "left bottom",
|
28 |
+
collision: "none"
|
29 |
+
},
|
30 |
+
source: null
|
31 |
+
},
|
32 |
+
|
33 |
+
pending: 0,
|
34 |
+
|
35 |
+
_create: function() {
|
36 |
+
var self = this,
|
37 |
+
doc = this.element[ 0 ].ownerDocument,
|
38 |
+
suppressKeyPress;
|
39 |
+
|
40 |
+
this.element
|
41 |
+
.addClass( "ui-autocomplete-input" )
|
42 |
+
.attr( "autocomplete", "off" )
|
43 |
+
// TODO verify these actually work as intended
|
44 |
+
.attr({
|
45 |
+
role: "textbox",
|
46 |
+
"aria-autocomplete": "list",
|
47 |
+
"aria-haspopup": "true"
|
48 |
+
})
|
49 |
+
.bind( "keydown.autocomplete", function( event ) {
|
50 |
+
if ( self.options.disabled || self.element.attr( "readonly" ) ) {
|
51 |
+
return;
|
52 |
+
}
|
53 |
+
|
54 |
+
suppressKeyPress = false;
|
55 |
+
var keyCode = $.ui.keyCode;
|
56 |
+
switch( event.keyCode ) {
|
57 |
+
case keyCode.PAGE_UP:
|
58 |
+
self._move( "previousPage", event );
|
59 |
+
break;
|
60 |
+
case keyCode.PAGE_DOWN:
|
61 |
+
self._move( "nextPage", event );
|
62 |
+
break;
|
63 |
+
case keyCode.UP:
|
64 |
+
self._move( "previous", event );
|
65 |
+
// prevent moving cursor to beginning of text field in some browsers
|
66 |
+
event.preventDefault();
|
67 |
+
break;
|
68 |
+
case keyCode.DOWN:
|
69 |
+
self._move( "next", event );
|
70 |
+
// prevent moving cursor to end of text field in some browsers
|
71 |
+
event.preventDefault();
|
72 |
+
break;
|
73 |
+
case keyCode.ENTER:
|
74 |
+
case keyCode.NUMPAD_ENTER:
|
75 |
+
// when menu is open and has focus
|
76 |
+
if ( self.menu.active ) {
|
77 |
+
// #6055 - Opera still allows the keypress to occur
|
78 |
+
// which causes forms to submit
|
79 |
+
suppressKeyPress = true;
|
80 |
+
event.preventDefault();
|
81 |
+
}
|
82 |
+
//passthrough - ENTER and TAB both select the current element
|
83 |
+
case keyCode.TAB:
|
84 |
+
if ( !self.menu.active ) {
|
85 |
+
return;
|
86 |
+
}
|
87 |
+
self.menu.select( event );
|
88 |
+
break;
|
89 |
+
case keyCode.ESCAPE:
|
90 |
+
self.element.val( self.term );
|
91 |
+
self.close( event );
|
92 |
+
break;
|
93 |
+
default:
|
94 |
+
// keypress is triggered before the input value is changed
|
95 |
+
clearTimeout( self.searching );
|
96 |
+
self.searching = setTimeout(function() {
|
97 |
+
// only search if the value has changed
|
98 |
+
if ( self.term != self.element.val() ) {
|
99 |
+
self.selectedItem = null;
|
100 |
+
self.search( null, event );
|
101 |
+
}
|
102 |
+
}, self.options.delay );
|
103 |
+
break;
|
104 |
+
}
|
105 |
+
})
|
106 |
+
.bind( "keypress.autocomplete", function( event ) {
|
107 |
+
if ( suppressKeyPress ) {
|
108 |
+
suppressKeyPress = false;
|
109 |
+
event.preventDefault();
|
110 |
+
}
|
111 |
+
})
|
112 |
+
.bind( "focus.autocomplete", function() {
|
113 |
+
if ( self.options.disabled ) {
|
114 |
+
return;
|
115 |
+
}
|
116 |
+
|
117 |
+
self.selectedItem = null;
|
118 |
+
self.previous = self.element.val();
|
119 |
+
})
|
120 |
+
.bind( "blur.autocomplete", function( event ) {
|
121 |
+
if ( self.options.disabled ) {
|
122 |
+
return;
|
123 |
+
}
|
124 |
+
|
125 |
+
clearTimeout( self.searching );
|
126 |
+
// clicks on the menu (or a button to trigger a search) will cause a blur event
|
127 |
+
self.closing = setTimeout(function() {
|
128 |
+
self.close( event );
|
129 |
+
self._change( event );
|
130 |
+
}, 150 );
|
131 |
+
});
|
132 |
+
this._initSource();
|
133 |
+
this.response = function() {
|
134 |
+
return self._response.apply( self, arguments );
|
135 |
+
};
|
136 |
+
this.menu = $( "<ul></ul>" )
|
137 |
+
.addClass( "ui-autocomplete" )
|
138 |
+
.appendTo( $( this.options.appendTo || "body", doc )[0] )
|
139 |
+
// prevent the close-on-blur in case of a "slow" click on the menu (long mousedown)
|
140 |
+
.mousedown(function( event ) {
|
141 |
+
// clicking on the scrollbar causes focus to shift to the body
|
142 |
+
// but we can't detect a mouseup or a click immediately afterward
|
143 |
+
// so we have to track the next mousedown and close the menu if
|
144 |
+
// the user clicks somewhere outside of the autocomplete
|
145 |
+
var menuElement = self.menu.element[ 0 ];
|
146 |
+
if ( !$( event.target ).closest( ".ui-menu-item" ).length ) {
|
147 |
+
setTimeout(function() {
|
148 |
+
$( document ).one( 'mousedown', function( event ) {
|
149 |
+
if ( event.target !== self.element[ 0 ] &&
|
150 |
+
event.target !== menuElement &&
|
151 |
+
!$.ui.contains( menuElement, event.target ) ) {
|
152 |
+
self.close();
|
153 |
+
}
|
154 |
+
});
|
155 |
+
}, 1 );
|
156 |
+
}
|
157 |
+
|
158 |
+
// use another timeout to make sure the blur-event-handler on the input was already triggered
|
159 |
+
setTimeout(function() {
|
160 |
+
clearTimeout( self.closing );
|
161 |
+
}, 13);
|
162 |
+
})
|
163 |
+
.menu({
|
164 |
+
focus: function( event, ui ) {
|
165 |
+
var item = ui.item.data( "item.autocomplete" );
|
166 |
+
if ( false !== self._trigger( "focus", event, { item: item } ) ) {
|
167 |
+
// use value to match what will end up in the input, if it was a key event
|
168 |
+
if ( /^key/.test(event.originalEvent.type) ) {
|
169 |
+
self.element.val( item.value );
|
170 |
+
}
|
171 |
+
}
|
172 |
+
},
|
173 |
+
selected: function( event, ui ) {
|
174 |
+
var item = ui.item.data( "item.autocomplete" ),
|
175 |
+
previous = self.previous;
|
176 |
+
|
177 |
+
// only trigger when focus was lost (click on menu)
|
178 |
+
if ( self.element[0] !== doc.activeElement ) {
|
179 |
+
self.element.focus();
|
180 |
+
self.previous = previous;
|
181 |
+
// #6109 - IE triggers two focus events and the second
|
182 |
+
// is asynchronous, so we need to reset the previous
|
183 |
+
// term synchronously and asynchronously :-(
|
184 |
+
setTimeout(function() {
|
185 |
+
self.previous = previous;
|
186 |
+
self.selectedItem = item;
|
187 |
+
}, 1);
|
188 |
+
}
|
189 |
+
|
190 |
+
if ( false !== self._trigger( "select", event, { item: item } ) ) {
|
191 |
+
self.element.val( item.value );
|
192 |
+
}
|
193 |
+
// reset the term after the select event
|
194 |
+
// this allows custom select handling to work properly
|
195 |
+
self.term = self.element.val();
|
196 |
+
|
197 |
+
self.close( event );
|
198 |
+
self.selectedItem = item;
|
199 |
+
},
|
200 |
+
blur: function( event, ui ) {
|
201 |
+
// don't set the value of the text field if it's already correct
|
202 |
+
// this prevents moving the cursor unnecessarily
|
203 |
+
if ( self.menu.element.is(":visible") &&
|
204 |
+
( self.element.val() !== self.term ) ) {
|
205 |
+
self.element.val( self.term );
|
206 |
+
}
|
207 |
+
}
|
208 |
+
})
|
209 |
+
.zIndex( this.element.zIndex() + 1 )
|
210 |
+
// workaround for jQuery bug #5781 http://dev.jquery.com/ticket/5781
|
211 |
+
.css({ top: 0, left: 0 })
|
212 |
+
.data( "menu" );
|
213 |
+
if ( $.fn.bgiframe ) {
|
214 |
+
this.menu.element.bgiframe();
|
215 |
+
}
|
216 |
+
},
|
217 |
+
|
218 |
+
destroy: function() {
|
219 |
+
this.element
|
220 |
+
.removeClass( "ui-autocomplete-input" )
|
221 |
+
.removeAttr( "autocomplete" )
|
222 |
+
.removeAttr( "role" )
|
223 |
+
.removeAttr( "aria-autocomplete" )
|
224 |
+
.removeAttr( "aria-haspopup" );
|
225 |
+
this.menu.element.remove();
|
226 |
+
$.Widget.prototype.destroy.call( this );
|
227 |
+
},
|
228 |
+
|
229 |
+
_setOption: function( key, value ) {
|
230 |
+
$.Widget.prototype._setOption.apply( this, arguments );
|
231 |
+
if ( key === "source" ) {
|
232 |
+
this._initSource();
|
233 |
+
}
|
234 |
+
if ( key === "appendTo" ) {
|
235 |
+
this.menu.element.appendTo( $( value || "body", this.element[0].ownerDocument )[0] )
|
236 |
+
}
|
237 |
+
if ( key === "disabled" && value && this.xhr ) {
|
238 |
+
this.xhr.abort();
|
239 |
+
}
|
240 |
+
},
|
241 |
+
|
242 |
+
_initSource: function() {
|
243 |
+
var self = this,
|
244 |
+
array,
|
245 |
+
url;
|
246 |
+
if ( $.isArray(this.options.source) ) {
|
247 |
+
array = this.options.source;
|
248 |
+
this.source = function( request, response ) {
|
249 |
+
response( $.ui.autocomplete.filter(array, request.term) );
|
250 |
+
};
|
251 |
+
} else if ( typeof this.options.source === "string" ) {
|
252 |
+
url = this.options.source;
|
253 |
+
this.source = function( request, response ) {
|
254 |
+
if ( self.xhr ) {
|
255 |
+
self.xhr.abort();
|
256 |
+
}
|
257 |
+
self.xhr = $.ajax({
|
258 |
+
url: url,
|
259 |
+
data: request,
|
260 |
+
dataType: "json",
|
261 |
+
autocompleteRequest: ++requestIndex,
|
262 |
+
success: function( data, status ) {
|
263 |
+
if ( this.autocompleteRequest === requestIndex ) {
|
264 |
+
response( data );
|
265 |
+
}
|
266 |
+
},
|
267 |
+
error: function() {
|
268 |
+
if ( this.autocompleteRequest === requestIndex ) {
|
269 |
+
response( [] );
|
270 |
+
}
|
271 |
+
}
|
272 |
+
});
|
273 |
+
};
|
274 |
+
} else {
|
275 |
+
this.source = this.options.source;
|
276 |
+
}
|
277 |
+
},
|
278 |
+
|
279 |
+
search: function( value, event ) {
|
280 |
+
value = value != null ? value : this.element.val();
|
281 |
+
|
282 |
+
// always save the actual value, not the one passed as an argument
|
283 |
+
this.term = this.element.val();
|
284 |
+
|
285 |
+
if ( value.length < this.options.minLength ) {
|
286 |
+
return this.close( event );
|
287 |
+
}
|
288 |
+
|
289 |
+
clearTimeout( this.closing );
|
290 |
+
if ( this._trigger( "search", event ) === false ) {
|
291 |
+
return;
|
292 |
+
}
|
293 |
+
|
294 |
+
return this._search( value );
|
295 |
+
},
|
296 |
+
|
297 |
+
_search: function( value ) {
|
298 |
+
this.pending++;
|
299 |
+
this.element.addClass( "ui-autocomplete-loading" );
|
300 |
+
|
301 |
+
this.source( { term: value }, this.response );
|
302 |
+
},
|
303 |
+
|
304 |
+
_response: function( content ) {
|
305 |
+
if ( !this.options.disabled && content && content.length ) {
|
306 |
+
content = this._normalize( content );
|
307 |
+
this._suggest( content );
|
308 |
+
this._trigger( "open" );
|
309 |
+
} else {
|
310 |
+
this.close();
|
311 |
+
}
|
312 |
+
this.pending--;
|
313 |
+
if ( !this.pending ) {
|
314 |
+
this.element.removeClass( "ui-autocomplete-loading" );
|
315 |
+
}
|
316 |
+
},
|
317 |
+
|
318 |
+
close: function( event ) {
|
319 |
+
clearTimeout( this.closing );
|
320 |
+
if ( this.menu.element.is(":visible") ) {
|
321 |
+
this.menu.element.hide();
|
322 |
+
this.menu.deactivate();
|
323 |
+
this._trigger( "close", event );
|
324 |
+
}
|
325 |
+
},
|
326 |
+
|
327 |
+
_change: function( event ) {
|
328 |
+
if ( this.previous !== this.element.val() ) {
|
329 |
+
this._trigger( "change", event, { item: this.selectedItem } );
|
330 |
+
}
|
331 |
+
},
|
332 |
+
|
333 |
+
_normalize: function( items ) {
|
334 |
+
// assume all items have the right format when the first item is complete
|
335 |
+
if ( items.length && items[0].label && items[0].value ) {
|
336 |
+
return items;
|
337 |
+
}
|
338 |
+
return $.map( items, function(item) {
|
339 |
+
if ( typeof item === "string" ) {
|
340 |
+
return {
|
341 |
+
label: item,
|
342 |
+
value: item
|
343 |
+
};
|
344 |
+
}
|
345 |
+
return $.extend({
|
346 |
+
label: item.label || item.value,
|
347 |
+
value: item.value || item.label
|
348 |
+
}, item );
|
349 |
+
});
|
350 |
+
},
|
351 |
+
|
352 |
+
_suggest: function( items ) {
|
353 |
+
var ul = this.menu.element
|
354 |
+
.empty()
|
355 |
+
.zIndex( this.element.zIndex() + 1 );
|
356 |
+
this._renderMenu( ul, items );
|
357 |
+
// TODO refresh should check if the active item is still in the dom, removing the need for a manual deactivate
|
358 |
+
this.menu.deactivate();
|
359 |
+
this.menu.refresh();
|
360 |
+
|
361 |
+
// size and position menu
|
362 |
+
ul.show();
|
363 |
+
this._resizeMenu();
|
364 |
+
ul.position( $.extend({
|
365 |
+
of: this.element
|
366 |
+
}, this.options.position ));
|
367 |
+
},
|
368 |
+
|
369 |
+
_resizeMenu: function() {
|
370 |
+
var ul = this.menu.element;
|
371 |
+
ul.outerWidth( Math.max(
|
372 |
+
ul.width( "" ).outerWidth(),
|
373 |
+
this.element.outerWidth()
|
374 |
+
) );
|
375 |
+
},
|
376 |
+
|
377 |
+
_renderMenu: function( ul, items ) {
|
378 |
+
var self = this;
|
379 |
+
$.each( items, function( index, item ) {
|
380 |
+
self._renderItem( ul, item );
|
381 |
+
});
|
382 |
+
},
|
383 |
+
|
384 |
+
_renderItem: function( ul, item) {
|
385 |
+
return $( "<li></li>" )
|
386 |
+
.data( "item.autocomplete", item )
|
387 |
+
.append( $( "<a></a>" ).text( item.label ) )
|
388 |
+
.appendTo( ul );
|
389 |
+
},
|
390 |
+
|
391 |
+
_move: function( direction, event ) {
|
392 |
+
if ( !this.menu.element.is(":visible") ) {
|
393 |
+
this.search( null, event );
|
394 |
+
return;
|
395 |
+
}
|
396 |
+
if ( this.menu.first() && /^previous/.test(direction) ||
|
397 |
+
this.menu.last() && /^next/.test(direction) ) {
|
398 |
+
this.element.val( this.term );
|
399 |
+
this.menu.deactivate();
|
400 |
+
return;
|
401 |
+
}
|
402 |
+
this.menu[ direction ]( event );
|
403 |
+
},
|
404 |
+
|
405 |
+
widget: function() {
|
406 |
+
return this.menu.element;
|
407 |
+
}
|
408 |
+
});
|
409 |
+
|
410 |
+
$.extend( $.ui.autocomplete, {
|
411 |
+
escapeRegex: function( value ) {
|
412 |
+
return value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
413 |
+
},
|
414 |
+
filter: function(array, term) {
|
415 |
+
var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
|
416 |
+
return $.grep( array, function(value) {
|
417 |
+
return matcher.test( value.label || value.value || value );
|
418 |
+
});
|
419 |
+
}
|
420 |
+
});
|
421 |
+
|
422 |
+
}( jQuery ));
|
423 |
+
|
424 |
+
/*
|
425 |
+
* jQuery UI Menu (not officially released)
|
426 |
+
*
|
427 |
+
* This widget isn't yet finished and the API is subject to change. We plan to finish
|
428 |
+
* it for the next release. You're welcome to give it a try anyway and give us feedback,
|
429 |
+
* as long as you're okay with migrating your code later on. We can help with that, too.
|
430 |
+
*
|
431 |
+
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
|
432 |
+
* Dual licensed under the MIT or GPL Version 2 licenses.
|
433 |
+
* http://jquery.org/license
|
434 |
+
*
|
435 |
+
* http://docs.jquery.com/UI/Menu
|
436 |
+
*
|
437 |
+
* Depends:
|
438 |
+
* jquery.ui.core.js
|
439 |
+
* jquery.ui.widget.js
|
440 |
+
*/
|
441 |
+
(function($) {
|
442 |
+
|
443 |
+
$.widget("ui.menu", {
|
444 |
+
_create: function() {
|
445 |
+
var self = this;
|
446 |
+
this.element
|
447 |
+
.addClass("ui-menu ui-widget ui-widget-content ui-corner-all")
|
448 |
+
.attr({
|
449 |
+
role: "listbox",
|
450 |
+
"aria-activedescendant": "ui-active-menuitem"
|
451 |
+
})
|
452 |
+
.click(function( event ) {
|
453 |
+
if ( !$( event.target ).closest( ".ui-menu-item a" ).length ) {
|
454 |
+
return;
|
455 |
+
}
|
456 |
+
// temporary
|
457 |
+
event.preventDefault();
|
458 |
+
self.select( event );
|
459 |
+
});
|
460 |
+
this.refresh();
|
461 |
+
},
|
462 |
+
|
463 |
+
refresh: function() {
|
464 |
+
var self = this;
|
465 |
+
|
466 |
+
// don't refresh list items that are already adapted
|
467 |
+
var items = this.element.children("li:not(.ui-menu-item):has(a)")
|
468 |
+
.addClass("ui-menu-item")
|
469 |
+
.attr("role", "menuitem");
|
470 |
+
|
471 |
+
items.children("a")
|
472 |
+
.addClass("ui-corner-all")
|
473 |
+
.attr("tabindex", -1)
|
474 |
+
// mouseenter doesn't work with event delegation
|
475 |
+
.mouseenter(function( event ) {
|
476 |
+
self.activate( event, $(this).parent() );
|
477 |
+
})
|
478 |
+
.mouseleave(function() {
|
479 |
+
self.deactivate();
|
480 |
+
});
|
481 |
+
},
|
482 |
+
|
483 |
+
activate: function( event, item ) {
|
484 |
+
this.deactivate();
|
485 |
+
if (this.hasScroll()) {
|
486 |
+
var offset = item.offset().top - this.element.offset().top,
|
487 |
+
scroll = this.element.attr("scrollTop"),
|
488 |
+
elementHeight = this.element.height();
|
489 |
+
if (offset < 0) {
|
490 |
+
this.element.attr("scrollTop", scroll + offset);
|
491 |
+
} else if (offset >= elementHeight) {
|
492 |
+
this.element.attr("scrollTop", scroll + offset - elementHeight + item.height());
|
493 |
+
}
|
494 |
+
}
|
495 |
+
this.active = item.eq(0)
|
496 |
+
.children("a")
|
497 |
+
.addClass("ui-state-hover")
|
498 |
+
.attr("id", "ui-active-menuitem")
|
499 |
+
.end();
|
500 |
+
this._trigger("focus", event, { item: item });
|
501 |
+
},
|
502 |
+
|
503 |
+
deactivate: function() {
|
504 |
+
if (!this.active) { return; }
|
505 |
+
|
506 |
+
this.active.children("a")
|
507 |
+
.removeClass("ui-state-hover")
|
508 |
+
.removeAttr("id");
|
509 |
+
this._trigger("blur");
|
510 |
+
this.active = null;
|
511 |
+
},
|
512 |
+
|
513 |
+
next: function(event) {
|
514 |
+
this.move("next", ".ui-menu-item:first", event);
|
515 |
+
},
|
516 |
+
|
517 |
+
previous: function(event) {
|
518 |
+
this.move("prev", ".ui-menu-item:last", event);
|
519 |
+
},
|
520 |
+
|
521 |
+
first: function() {
|
522 |
+
return this.active && !this.active.prevAll(".ui-menu-item").length;
|
523 |
+
},
|
524 |
+
|
525 |
+
last: function() {
|
526 |
+
return this.active && !this.active.nextAll(".ui-menu-item").length;
|
527 |
+
},
|
528 |
+
|
529 |
+
move: function(direction, edge, event) {
|
530 |
+
if (!this.active) {
|
531 |
+
this.activate(event, this.element.children(edge));
|
532 |
+
return;
|
533 |
+
}
|
534 |
+
var next = this.active[direction + "All"](".ui-menu-item").eq(0);
|
535 |
+
if (next.length) {
|
536 |
+
this.activate(event, next);
|
537 |
+
} else {
|
538 |
+
this.activate(event, this.element.children(edge));
|
539 |
+
}
|
540 |
+
},
|
541 |
+
|
542 |
+
// TODO merge with previousPage
|
543 |
+
nextPage: function(event) {
|
544 |
+
if (this.hasScroll()) {
|
545 |
+
// TODO merge with no-scroll-else
|
546 |
+
if (!this.active || this.last()) {
|
547 |
+
this.activate(event, this.element.children(".ui-menu-item:first"));
|
548 |
+
return;
|
549 |
+
}
|
550 |
+
var base = this.active.offset().top,
|
551 |
+
height = this.element.height(),
|
552 |
+
result = this.element.children(".ui-menu-item").filter(function() {
|
553 |
+
var close = $(this).offset().top - base - height + $(this).height();
|
554 |
+
// TODO improve approximation
|
555 |
+
return close < 10 && close > -10;
|
556 |
+
});
|
557 |
+
|
558 |
+
// TODO try to catch this earlier when scrollTop indicates the last page anyway
|
559 |
+
if (!result.length) {
|
560 |
+
result = this.element.children(".ui-menu-item:last");
|
561 |
+
}
|
562 |
+
this.activate(event, result);
|
563 |
+
} else {
|
564 |
+
this.activate(event, this.element.children(".ui-menu-item")
|
565 |
+
.filter(!this.active || this.last() ? ":first" : ":last"));
|
566 |
+
}
|
567 |
+
},
|
568 |
+
|
569 |
+
// TODO merge with nextPage
|
570 |
+
previousPage: function(event) {
|
571 |
+
if (this.hasScroll()) {
|
572 |
+
// TODO merge with no-scroll-else
|
573 |
+
if (!this.active || this.first()) {
|
574 |
+
this.activate(event, this.element.children(".ui-menu-item:last"));
|
575 |
+
return;
|
576 |
+
}
|
577 |
+
|
578 |
+
var base = this.active.offset().top,
|
579 |
+
height = this.element.height();
|
580 |
+
result = this.element.children(".ui-menu-item").filter(function() {
|
581 |
+
var close = $(this).offset().top - base + height - $(this).height();
|
582 |
+
// TODO improve approximation
|
583 |
+
return close < 10 && close > -10;
|
584 |
+
});
|
585 |
+
|
586 |
+
// TODO try to catch this earlier when scrollTop indicates the last page anyway
|
587 |
+
if (!result.length) {
|
588 |
+
result = this.element.children(".ui-menu-item:first");
|
589 |
+
}
|
590 |
+
this.activate(event, result);
|
591 |
+
} else {
|
592 |
+
this.activate(event, this.element.children(".ui-menu-item")
|
593 |
+
.filter(!this.active || this.first() ? ":last" : ":first"));
|
594 |
+
}
|
595 |
+
},
|
596 |
+
|
597 |
+
hasScroll: function() {
|
598 |
+
return this.element.height() < this.element.attr("scrollHeight");
|
599 |
+
},
|
600 |
+
|
601 |
+
select: function( event ) {
|
602 |
+
this._trigger("selected", event, { item: this.active });
|
603 |
+
}
|
604 |
+
});
|
605 |
+
|
606 |
+
}(jQuery));
|
static/js/jquery/ui.datepicker.js
ADDED
@@ -0,0 +1,1636 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
* jQuery UI Datepicker 1.7.3
|
3 |
+
*
|
4 |
+
* Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
|
5 |
+
* Dual licensed under the MIT (MIT-LICENSE.txt)
|
6 |
+
* and GPL (GPL-LICENSE.txt) licenses.
|
7 |
+
*
|
8 |
+
* http://docs.jquery.com/UI/Datepicker
|
9 |
+
*
|
10 |
+
* Depends:
|
11 |
+
* ui.core.js
|
12 |
+
*/
|
13 |
+
|
14 |
+
(function($) { // hide the namespace
|
15 |
+
|
16 |
+
$.extend($.ui, { datepicker: { version: "1.7.3" } });
|
17 |
+
|
18 |
+
var PROP_NAME = 'datepicker';
|
19 |
+
|
20 |
+
/* Date picker manager.
|
21 |
+
Use the singleton instance of this class, $.datepicker, to interact with the date picker.
|
22 |
+
Settings for (groups of) date pickers are maintained in an instance object,
|
23 |
+
allowing multiple different settings on the same page. */
|
24 |
+
|
25 |
+
function Datepicker() {
|
26 |
+
this.debug = false; // Change this to true to start debugging
|
27 |
+
this._curInst = null; // The current instance in use
|
28 |
+
this._keyEvent = false; // If the last event was a key event
|
29 |
+
this._disabledInputs = []; // List of date picker inputs that have been disabled
|
30 |
+
this._datepickerShowing = false; // True if the popup picker is showing , false if not
|
31 |
+
this._inDialog = false; // True if showing within a "dialog", false if not
|
32 |
+
this._mainDivId = 'ui-datepicker-div'; // The ID of the main datepicker division
|
33 |
+
this._inlineClass = 'ui-datepicker-inline'; // The name of the inline marker class
|
34 |
+
this._appendClass = 'ui-datepicker-append'; // The name of the append marker class
|
35 |
+
this._triggerClass = 'ui-datepicker-trigger'; // The name of the trigger marker class
|
36 |
+
this._dialogClass = 'ui-datepicker-dialog'; // The name of the dialog marker class
|
37 |
+
this._disableClass = 'ui-datepicker-disabled'; // The name of the disabled covering marker class
|
38 |
+
this._unselectableClass = 'ui-datepicker-unselectable'; // The name of the unselectable cell marker class
|
39 |
+
this._currentClass = 'ui-datepicker-current-day'; // The name of the current day marker class
|
40 |
+
this._dayOverClass = 'ui-datepicker-days-cell-over'; // The name of the day hover marker class
|
41 |
+
this.regional = []; // Available regional settings, indexed by language code
|
42 |
+
this.regional[''] = { // Default regional settings
|
43 |
+
closeText: 'Done', // Display text for close link
|
44 |
+
prevText: 'Prev', // Display text for previous month link
|
45 |
+
nextText: 'Next', // Display text for next month link
|
46 |
+
currentText: 'Today', // Display text for current month link
|
47 |
+
monthNames: ['January','February','March','April','May','June',
|
48 |
+
'July','August','September','October','November','December'], // Names of months for drop-down and formatting
|
49 |
+
monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], // For formatting
|
50 |
+
dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], // For formatting
|
51 |
+
dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], // For formatting
|
52 |
+
dayNamesMin: ['Su','Mo','Tu','We','Th','Fr','Sa'], // Column headings for days starting at Sunday
|
53 |
+
dateFormat: 'mm/dd/yy', // See format options on parseDate
|
54 |
+
firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ...
|
55 |
+
isRTL: false // True if right-to-left language, false if left-to-right
|
56 |
+
};
|
57 |
+
this._defaults = { // Global defaults for all the date picker instances
|
58 |
+
showOn: 'focus', // 'focus' for popup on focus,
|
59 |
+
// 'button' for trigger button, or 'both' for either
|
60 |
+
showAnim: 'show', // Name of jQuery animation for popup
|
61 |
+
showOptions: {}, // Options for enhanced animations
|
62 |
+
defaultDate: null, // Used when field is blank: actual date,
|
63 |
+
// +/-number for offset from today, null for today
|
64 |
+
appendText: '', // Display text following the input box, e.g. showing the format
|
65 |
+
buttonText: '...', // Text for trigger button
|
66 |
+
buttonImage: '', // URL for trigger button image
|
67 |
+
buttonImageOnly: false, // True if the image appears alone, false if it appears on a button
|
68 |
+
hideIfNoPrevNext: false, // True to hide next/previous month links
|
69 |
+
// if not applicable, false to just disable them
|
70 |
+
navigationAsDateFormat: false, // True if date formatting applied to prev/today/next links
|
71 |
+
gotoCurrent: false, // True if today link goes back to current selection instead
|
72 |
+
changeMonth: false, // True if month can be selected directly, false if only prev/next
|
73 |
+
changeYear: false, // True if year can be selected directly, false if only prev/next
|
74 |
+
showMonthAfterYear: false, // True if the year select precedes month, false for month then year
|
75 |
+
yearRange: '-10:+10', // Range of years to display in drop-down,
|
76 |
+
// either relative to current year (-nn:+nn) or absolute (nnnn:nnnn)
|
77 |
+
showOtherMonths: false, // True to show dates in other months, false to leave blank
|
78 |
+
calculateWeek: this.iso8601Week, // How to calculate the week of the year,
|
79 |
+
// takes a Date and returns the number of the week for it
|
80 |
+
shortYearCutoff: '+10', // Short year values < this are in the current century,
|
81 |
+
// > this are in the previous century,
|
82 |
+
// string value starting with '+' for current year + value
|
83 |
+
minDate: null, // The earliest selectable date, or null for no limit
|
84 |
+
maxDate: null, // The latest selectable date, or null for no limit
|
85 |
+
duration: 'normal', // Duration of display/closure
|
86 |
+
beforeShowDay: null, // Function that takes a date and returns an array with
|
87 |
+
// [0] = true if selectable, false if not, [1] = custom CSS class name(s) or '',
|
88 |
+
// [2] = cell title (optional), e.g. $.datepicker.noWeekends
|
89 |
+
beforeShow: null, // Function that takes an input field and
|
90 |
+
// returns a set of custom settings for the date picker
|
91 |
+
onSelect: null, // Define a callback function when a date is selected
|
92 |
+
onChangeMonthYear: null, // Define a callback function when the month or year is changed
|
93 |
+
onClose: null, // Define a callback function when the datepicker is closed
|
94 |
+
numberOfMonths: 1, // Number of months to show at a time
|
95 |
+
showCurrentAtPos: 0, // The position in multipe months at which to show the current month (starting at 0)
|
96 |
+
stepMonths: 1, // Number of months to step back/forward
|
97 |
+
stepBigMonths: 12, // Number of months to step back/forward for the big links
|
98 |
+
altField: '', // Selector for an alternate field to store selected dates into
|
99 |
+
altFormat: '', // The date format to use for the alternate field
|
100 |
+
constrainInput: true, // The input is constrained by the current date format
|
101 |
+
showButtonPanel: false // True to show button panel, false to not show it
|
102 |
+
};
|
103 |
+
$.extend(this._defaults, this.regional['']);
|
104 |
+
this.dpDiv = $('<div id="' + this._mainDivId + '" class="ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all ui-helper-hidden-accessible"></div>');
|
105 |
+
}
|
106 |
+
|
107 |
+
$.extend(Datepicker.prototype, {
|
108 |
+
/* Class name added to elements to indicate already configured with a date picker. */
|
109 |
+
markerClassName: 'hasDatepicker',
|
110 |
+
|
111 |
+
/* Debug logging (if enabled). */
|
112 |
+
log: function () {
|
113 |
+
if (this.debug)
|
114 |
+
console.log.apply('', arguments);
|
115 |
+
},
|
116 |
+
|
117 |
+
/* Override the default settings for all instances of the date picker.
|
118 |
+
@param settings object - the new settings to use as defaults (anonymous object)
|
119 |
+
@return the manager object */
|
120 |
+
setDefaults: function(settings) {
|
121 |
+
extendRemove(this._defaults, settings || {});
|
122 |
+
return this;
|
123 |
+
},
|
124 |
+
|
125 |
+
/* Attach the date picker to a jQuery selection.
|
126 |
+
@param target element - the target input field or division or span
|
127 |
+
@param settings object - the new settings to use for this date picker instance (anonymous) */
|
128 |
+
_attachDatepicker: function(target, settings) {
|
129 |
+
// check for settings on the control itself - in namespace 'date:'
|
130 |
+
var inlineSettings = null;
|
131 |
+
for (var attrName in this._defaults) {
|
132 |
+
var attrValue = target.getAttribute('date:' + attrName);
|
133 |
+
if (attrValue) {
|
134 |
+
inlineSettings = inlineSettings || {};
|
135 |
+
try {
|
136 |
+
inlineSettings[attrName] = eval(attrValue);
|
137 |
+
} catch (err) {
|
138 |
+
inlineSettings[attrName] = attrValue;
|
139 |
+
}
|
140 |
+
}
|
141 |
+
}
|
142 |
+
var nodeName = target.nodeName.toLowerCase();
|
143 |
+
var inline = (nodeName == 'div' || nodeName == 'span');
|
144 |
+
if (!target.id)
|
145 |
+
target.id = 'dp' + (++this.uuid);
|
146 |
+
var inst = this._newInst($(target), inline);
|
147 |
+
inst.settings = $.extend({}, settings || {}, inlineSettings || {});
|
148 |
+
if (nodeName == 'input') {
|
149 |
+
this._connectDatepicker(target, inst);
|
150 |
+
} else if (inline) {
|
151 |
+
this._inlineDatepicker(target, inst);
|
152 |
+
}
|
153 |
+
},
|
154 |
+
|
155 |
+
/* Create a new instance object. */
|
156 |
+
_newInst: function(target, inline) {
|
157 |
+
var id = target[0].id.replace(/([:\[\]\.])/g, '\\\\$1'); // escape jQuery meta chars
|
158 |
+
return {id: id, input: target, // associated target
|
159 |
+
selectedDay: 0, selectedMonth: 0, selectedYear: 0, // current selection
|
160 |
+
drawMonth: 0, drawYear: 0, // month being drawn
|
161 |
+
inline: inline, // is datepicker inline or not
|
162 |
+
dpDiv: (!inline ? this.dpDiv : // presentation div
|
163 |
+
$('<div class="' + this._inlineClass + ' ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>'))};
|
164 |
+
},
|
165 |
+
|
166 |
+
/* Attach the date picker to an input field. */
|
167 |
+
_connectDatepicker: function(target, inst) {
|
168 |
+
var input = $(target);
|
169 |
+
inst.append = $([]);
|
170 |
+
inst.trigger = $([]);
|
171 |
+
if (input.hasClass(this.markerClassName))
|
172 |
+
return;
|
173 |
+
var appendText = this._get(inst, 'appendText');
|
174 |
+
var isRTL = this._get(inst, 'isRTL');
|
175 |
+
if (appendText) {
|
176 |
+
inst.append = $('<span class="' + this._appendClass + '">' + appendText + '</span>');
|
177 |
+
input[isRTL ? 'before' : 'after'](inst.append);
|
178 |
+
}
|
179 |
+
var showOn = this._get(inst, 'showOn');
|
180 |
+
if (showOn == 'focus' || showOn == 'both') // pop-up date picker when in the marked field
|
181 |
+
input.focus(this._showDatepicker);
|
182 |
+
if (showOn == 'button' || showOn == 'both') { // pop-up date picker when button clicked
|
183 |
+
var buttonText = this._get(inst, 'buttonText');
|
184 |
+
var buttonImage = this._get(inst, 'buttonImage');
|
185 |
+
inst.trigger = $(this._get(inst, 'buttonImageOnly') ?
|
186 |
+
$('<img/>').addClass(this._triggerClass).
|
187 |
+
attr({ src: buttonImage, alt: buttonText, title: buttonText }) :
|
188 |
+
$('<button type="button"></button>').addClass(this._triggerClass).
|
189 |
+
html(buttonImage == '' ? buttonText : $('<img/>').attr(
|
190 |
+
{ src:buttonImage, alt:buttonText, title:buttonText })));
|
191 |
+
input[isRTL ? 'before' : 'after'](inst.trigger);
|
192 |
+
inst.trigger.click(function() {
|
193 |
+
if ($.datepicker._datepickerShowing && $.datepicker._lastInput == target)
|
194 |
+
$.datepicker._hideDatepicker();
|
195 |
+
else
|
196 |
+
$.datepicker._showDatepicker(target);
|
197 |
+
return false;
|
198 |
+
});
|
199 |
+
}
|
200 |
+
input.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).
|
201 |
+
bind("setData.datepicker", function(event, key, value) {
|
202 |
+
inst.settings[key] = value;
|
203 |
+
}).bind("getData.datepicker", function(event, key) {
|
204 |
+
return this._get(inst, key);
|
205 |
+
});
|
206 |
+
$.data(target, PROP_NAME, inst);
|
207 |
+
},
|
208 |
+
|
209 |
+
/* Attach an inline date picker to a div. */
|
210 |
+
_inlineDatepicker: function(target, inst) {
|
211 |
+
var divSpan = $(target);
|
212 |
+
if (divSpan.hasClass(this.markerClassName))
|
213 |
+
return;
|
214 |
+
divSpan.addClass(this.markerClassName).append(inst.dpDiv).
|
215 |
+
bind("setData.datepicker", function(event, key, value){
|
216 |
+
inst.settings[key] = value;
|
217 |
+
}).bind("getData.datepicker", function(event, key){
|
218 |
+
return this._get(inst, key);
|
219 |
+
});
|
220 |
+
$.data(target, PROP_NAME, inst);
|
221 |
+
this._setDate(inst, this._getDefaultDate(inst));
|
222 |
+
this._updateDatepicker(inst);
|
223 |
+
this._updateAlternate(inst);
|
224 |
+
},
|
225 |
+
|
226 |
+
/* Pop-up the date picker in a "dialog" box.
|
227 |
+
@param input element - ignored
|
228 |
+
@param dateText string - the initial date to display (in the current format)
|
229 |
+
@param onSelect function - the function(dateText) to call when a date is selected
|
230 |
+
@param settings object - update the dialog date picker instance's settings (anonymous object)
|
231 |
+
@param pos int[2] - coordinates for the dialog's position within the screen or
|
232 |
+
event - with x/y coordinates or
|
233 |
+
leave empty for default (screen centre)
|
234 |
+
@return the manager object */
|
235 |
+
_dialogDatepicker: function(input, dateText, onSelect, settings, pos) {
|
236 |
+
var inst = this._dialogInst; // internal instance
|
237 |
+
if (!inst) {
|
238 |
+
var id = 'dp' + (++this.uuid);
|
239 |
+
this._dialogInput = $('<input type="text" id="' + id +
|
240 |
+
'" size="1" style="position: absolute; top: -100px;"/>');
|
241 |
+
this._dialogInput.keydown(this._doKeyDown);
|
242 |
+
$('body').append(this._dialogInput);
|
243 |
+
inst = this._dialogInst = this._newInst(this._dialogInput, false);
|
244 |
+
inst.settings = {};
|
245 |
+
$.data(this._dialogInput[0], PROP_NAME, inst);
|
246 |
+
}
|
247 |
+
extendRemove(inst.settings, settings || {});
|
248 |
+
this._dialogInput.val(dateText);
|
249 |
+
|
250 |
+
this._pos = (pos ? (pos.length ? pos : [pos.pageX, pos.pageY]) : null);
|
251 |
+
if (!this._pos) {
|
252 |
+
var browserWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
|
253 |
+
var browserHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
|
254 |
+
var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft;
|
255 |
+
var scrollY = document.documentElement.scrollTop || document.body.scrollTop;
|
256 |
+
this._pos = // should use actual width/height below
|
257 |
+
[(browserWidth / 2) - 100 + scrollX, (browserHeight / 2) - 150 + scrollY];
|
258 |
+
}
|
259 |
+
|
260 |
+
// move input on screen for focus, but hidden behind dialog
|
261 |
+
this._dialogInput.css('left', this._pos[0] + 'px').css('top', this._pos[1] + 'px');
|
262 |
+
inst.settings.onSelect = onSelect;
|
263 |
+
this._inDialog = true;
|
264 |
+
this.dpDiv.addClass(this._dialogClass);
|
265 |
+
this._showDatepicker(this._dialogInput[0]);
|
266 |
+
if ($.blockUI)
|
267 |
+
$.blockUI(this.dpDiv);
|
268 |
+
$.data(this._dialogInput[0], PROP_NAME, inst);
|
269 |
+
return this;
|
270 |
+
},
|
271 |
+
|
272 |
+
/* Detach a datepicker from its control.
|
273 |
+
@param target element - the target input field or division or span */
|
274 |
+
_destroyDatepicker: function(target) {
|
275 |
+
var $target = $(target);
|
276 |
+
var inst = $.data(target, PROP_NAME);
|
277 |
+
if (!$target.hasClass(this.markerClassName)) {
|
278 |
+
return;
|
279 |
+
}
|
280 |
+
var nodeName = target.nodeName.toLowerCase();
|
281 |
+
$.removeData(target, PROP_NAME);
|
282 |
+
if (nodeName == 'input') {
|
283 |
+
inst.append.remove();
|
284 |
+
inst.trigger.remove();
|
285 |
+
$target.removeClass(this.markerClassName).
|
286 |
+
unbind('focus', this._showDatepicker).
|
287 |
+
unbind('keydown', this._doKeyDown).
|
288 |
+
unbind('keypress', this._doKeyPress);
|
289 |
+
} else if (nodeName == 'div' || nodeName == 'span')
|
290 |
+
$target.removeClass(this.markerClassName).empty();
|
291 |
+
},
|
292 |
+
|
293 |
+
/* Enable the date picker to a jQuery selection.
|
294 |
+
@param target element - the target input field or division or span */
|
295 |
+
_enableDatepicker: function(target) {
|
296 |
+
var $target = $(target);
|
297 |
+
var inst = $.data(target, PROP_NAME);
|
298 |
+
if (!$target.hasClass(this.markerClassName)) {
|
299 |
+
return;
|
300 |
+
}
|
301 |
+
var nodeName = target.nodeName.toLowerCase();
|
302 |
+
if (nodeName == 'input') {
|
303 |
+
target.disabled = false;
|
304 |
+
inst.trigger.filter('button').
|
305 |
+
each(function() { this.disabled = false; }).end().
|
306 |
+
filter('img').css({opacity: '1.0', cursor: ''});
|
307 |
+
}
|
308 |
+
else if (nodeName == 'div' || nodeName == 'span') {
|
309 |
+
var inline = $target.children('.' + this._inlineClass);
|
310 |
+
inline.children().removeClass('ui-state-disabled');
|
311 |
+
}
|
312 |
+
this._disabledInputs = $.map(this._disabledInputs,
|
313 |
+
function(value) { return (value == target ? null : value); }); // delete entry
|
314 |
+
},
|
315 |
+
|
316 |
+
/* Disable the date picker to a jQuery selection.
|
317 |
+
@param target element - the target input field or division or span */
|
318 |
+
_disableDatepicker: function(target) {
|
319 |
+
var $target = $(target);
|
320 |
+
var inst = $.data(target, PROP_NAME);
|
321 |
+
if (!$target.hasClass(this.markerClassName)) {
|
322 |
+
return;
|
323 |
+
}
|
324 |
+
var nodeName = target.nodeName.toLowerCase();
|
325 |
+
if (nodeName == 'input') {
|
326 |
+
target.disabled = true;
|
327 |
+
inst.trigger.filter('button').
|
328 |
+
each(function() { this.disabled = true; }).end().
|
329 |
+
filter('img').css({opacity: '0.5', cursor: 'default'});
|
330 |
+
}
|
331 |
+
else if (nodeName == 'div' || nodeName == 'span') {
|
332 |
+
var inline = $target.children('.' + this._inlineClass);
|
333 |
+
inline.children().addClass('ui-state-disabled');
|
334 |
+
}
|
335 |
+
this._disabledInputs = $.map(this._disabledInputs,
|
336 |
+
function(value) { return (value == target ? null : value); }); // delete entry
|
337 |
+
this._disabledInputs[this._disabledInputs.length] = target;
|
338 |
+
},
|
339 |
+
|
340 |
+
/* Is the first field in a jQuery collection disabled as a datepicker?
|
341 |
+
@param target element - the target input field or division or span
|
342 |
+
@return boolean - true if disabled, false if enabled */
|
343 |
+
_isDisabledDatepicker: function(target) {
|
344 |
+
if (!target) {
|
345 |
+
return false;
|
346 |
+
}
|
347 |
+
for (var i = 0; i < this._disabledInputs.length; i++) {
|
348 |
+
if (this._disabledInputs[i] == target)
|
349 |
+
return true;
|
350 |
+
}
|
351 |
+
return false;
|
352 |
+
},
|
353 |
+
|
354 |
+
/* Retrieve the instance data for the target control.
|
355 |
+
@param target element - the target input field or division or span
|
356 |
+
@return object - the associated instance data
|
357 |
+
@throws error if a jQuery problem getting data */
|
358 |
+
_getInst: function(target) {
|
359 |
+
try {
|
360 |
+
return $.data(target, PROP_NAME);
|
361 |
+
}
|
362 |
+
catch (err) {
|
363 |
+
throw 'Missing instance data for this datepicker';
|
364 |
+
}
|
365 |
+
},
|
366 |
+
|
367 |
+
/* Update or retrieve the settings for a date picker attached to an input field or division.
|
368 |
+
@param target element - the target input field or division or span
|
369 |
+
@param name object - the new settings to update or
|
370 |
+
string - the name of the setting to change or retrieve,
|
371 |
+
when retrieving also 'all' for all instance settings or
|
372 |
+
'defaults' for all global defaults
|
373 |
+
@param value any - the new value for the setting
|
374 |
+
(omit if above is an object or to retrieve a value) */
|
375 |
+
_optionDatepicker: function(target, name, value) {
|
376 |
+
var inst = this._getInst(target);
|
377 |
+
if (arguments.length == 2 && typeof name == 'string') {
|
378 |
+
return (name == 'defaults' ? $.extend({}, $.datepicker._defaults) :
|
379 |
+
(inst ? (name == 'all' ? $.extend({}, inst.settings) :
|
380 |
+
this._get(inst, name)) : null));
|
381 |
+
}
|
382 |
+
var settings = name || {};
|
383 |
+
if (typeof name == 'string') {
|
384 |
+
settings = {};
|
385 |
+
settings[name] = value;
|
386 |
+
}
|
387 |
+
if (inst) {
|
388 |
+
if (this._curInst == inst) {
|
389 |
+
this._hideDatepicker(null);
|
390 |
+
}
|
391 |
+
var date = this._getDateDatepicker(target);
|
392 |
+
extendRemove(inst.settings, settings);
|
393 |
+
this._setDateDatepicker(target, date);
|
394 |
+
this._updateDatepicker(inst);
|
395 |
+
}
|
396 |
+
},
|
397 |
+
|
398 |
+
// change method deprecated
|
399 |
+
_changeDatepicker: function(target, name, value) {
|
400 |
+
this._optionDatepicker(target, name, value);
|
401 |
+
},
|
402 |
+
|
403 |
+
/* Redraw the date picker attached to an input field or division.
|
404 |
+
@param target element - the target input field or division or span */
|
405 |
+
_refreshDatepicker: function(target) {
|
406 |
+
var inst = this._getInst(target);
|
407 |
+
if (inst) {
|
408 |
+
this._updateDatepicker(inst);
|
409 |
+
}
|
410 |
+
},
|
411 |
+
|
412 |
+
/* Set the dates for a jQuery selection.
|
413 |
+
@param target element - the target input field or division or span
|
414 |
+
@param date Date - the new date
|
415 |
+
@param endDate Date - the new end date for a range (optional) */
|
416 |
+
_setDateDatepicker: function(target, date, endDate) {
|
417 |
+
var inst = this._getInst(target);
|
418 |
+
if (inst) {
|
419 |
+
this._setDate(inst, date, endDate);
|
420 |
+
this._updateDatepicker(inst);
|
421 |
+
this._updateAlternate(inst);
|
422 |
+
}
|
423 |
+
},
|
424 |
+
|
425 |
+
/* Get the date(s) for the first entry in a jQuery selection.
|
426 |
+
@param target element - the target input field or division or span
|
427 |
+
@return Date - the current date or
|
428 |
+
Date[2] - the current dates for a range */
|
429 |
+
_getDateDatepicker: function(target) {
|
430 |
+
var inst = this._getInst(target);
|
431 |
+
if (inst && !inst.inline)
|
432 |
+
this._setDateFromField(inst);
|
433 |
+
return (inst ? this._getDate(inst) : null);
|
434 |
+
},
|
435 |
+
|
436 |
+
/* Handle keystrokes. */
|
437 |
+
_doKeyDown: function(event) {
|
438 |
+
var inst = $.datepicker._getInst(event.target);
|
439 |
+
var handled = true;
|
440 |
+
var isRTL = inst.dpDiv.is('.ui-datepicker-rtl');
|
441 |
+
inst._keyEvent = true;
|
442 |
+
if ($.datepicker._datepickerShowing)
|
443 |
+
switch (event.keyCode) {
|
444 |
+
case 9: $.datepicker._hideDatepicker(null, '');
|
445 |
+
break; // hide on tab out
|
446 |
+
case 13: var sel = $('td.' + $.datepicker._dayOverClass +
|
447 |
+
', td.' + $.datepicker._currentClass, inst.dpDiv);
|
448 |
+
if (sel[0])
|
449 |
+
$.datepicker._selectDay(event.target, inst.selectedMonth, inst.selectedYear, sel[0]);
|
450 |
+
else
|
451 |
+
$.datepicker._hideDatepicker(null, $.datepicker._get(inst, 'duration'));
|
452 |
+
return false; // don't submit the form
|
453 |
+
break; // select the value on enter
|
454 |
+
case 27: $.datepicker._hideDatepicker(null, $.datepicker._get(inst, 'duration'));
|
455 |
+
break; // hide on escape
|
456 |
+
case 33: $.datepicker._adjustDate(event.target, (event.ctrlKey ?
|
457 |
+
-$.datepicker._get(inst, 'stepBigMonths') :
|
458 |
+
-$.datepicker._get(inst, 'stepMonths')), 'M');
|
459 |
+
break; // previous month/year on page up/+ ctrl
|
460 |
+
case 34: $.datepicker._adjustDate(event.target, (event.ctrlKey ?
|
461 |
+
+$.datepicker._get(inst, 'stepBigMonths') :
|
462 |
+
+$.datepicker._get(inst, 'stepMonths')), 'M');
|
463 |
+
break; // next month/year on page down/+ ctrl
|
464 |
+
case 35: if (event.ctrlKey || event.metaKey) $.datepicker._clearDate(event.target);
|
465 |
+
handled = event.ctrlKey || event.metaKey;
|
466 |
+
break; // clear on ctrl or command +end
|
467 |
+
case 36: if (event.ctrlKey || event.metaKey) $.datepicker._gotoToday(event.target);
|
468 |
+
handled = event.ctrlKey || event.metaKey;
|
469 |
+
break; // current on ctrl or command +home
|
470 |
+
case 37: if (event.ctrlKey || event.metaKey) $.datepicker._adjustDate(event.target, (isRTL ? +1 : -1), 'D');
|
471 |
+
handled = event.ctrlKey || event.metaKey;
|
472 |
+
// -1 day on ctrl or command +left
|
473 |
+
if (event.originalEvent.altKey) $.datepicker._adjustDate(event.target, (event.ctrlKey ?
|
474 |
+
-$.datepicker._get(inst, 'stepBigMonths') :
|
475 |
+
-$.datepicker._get(inst, 'stepMonths')), 'M');
|
476 |
+
// next month/year on alt +left on Mac
|
477 |
+
break;
|
478 |
+
case 38: if (event.ctrlKey || event.metaKey) $.datepicker._adjustDate(event.target, -7, 'D');
|
479 |
+
handled = event.ctrlKey || event.metaKey;
|
480 |
+
break; // -1 week on ctrl or command +up
|
481 |
+
case 39: if (event.ctrlKey || event.metaKey) $.datepicker._adjustDate(event.target, (isRTL ? -1 : +1), 'D');
|
482 |
+
handled = event.ctrlKey || event.metaKey;
|
483 |
+
// +1 day on ctrl or command +right
|
484 |
+
if (event.originalEvent.altKey) $.datepicker._adjustDate(event.target, (event.ctrlKey ?
|
485 |
+
+$.datepicker._get(inst, 'stepBigMonths') :
|
486 |
+
+$.datepicker._get(inst, 'stepMonths')), 'M');
|
487 |
+
// next month/year on alt +right
|
488 |
+
break;
|
489 |
+
case 40: if (event.ctrlKey || event.metaKey) $.datepicker._adjustDate(event.target, +7, 'D');
|
490 |
+
handled = event.ctrlKey || event.metaKey;
|
491 |
+
break; // +1 week on ctrl or command +down
|
492 |
+
default: handled = false;
|
493 |
+
}
|
494 |
+
else if (event.keyCode == 36 && event.ctrlKey) // display the date picker on ctrl+home
|
495 |
+
$.datepicker._showDatepicker(this);
|
496 |
+
else {
|
497 |
+
handled = false;
|
498 |
+
}
|
499 |
+
if (handled) {
|
500 |
+
event.preventDefault();
|
501 |
+
event.stopPropagation();
|
502 |
+
}
|
503 |
+
},
|
504 |
+
|
505 |
+
/* Filter entered characters - based on date format. */
|
506 |
+
_doKeyPress: function(event) {
|
507 |
+
var inst = $.datepicker._getInst(event.target);
|
508 |
+
if ($.datepicker._get(inst, 'constrainInput')) {
|
509 |
+
var chars = $.datepicker._possibleChars($.datepicker._get(inst, 'dateFormat'));
|
510 |
+
var chr = String.fromCharCode(event.charCode == undefined ? event.keyCode : event.charCode);
|
511 |
+
return event.ctrlKey || (chr < ' ' || !chars || chars.indexOf(chr) > -1);
|
512 |
+
}
|
513 |
+
},
|
514 |
+
|
515 |
+
/* Pop-up the date picker for a given input field.
|
516 |
+
@param input element - the input field attached to the date picker or
|
517 |
+
event - if triggered by focus */
|
518 |
+
_showDatepicker: function(input) {
|
519 |
+
input = input.target || input;
|
520 |
+
if (input.nodeName.toLowerCase() != 'input') // find from button/image trigger
|
521 |
+
input = $('input', input.parentNode)[0];
|
522 |
+
if ($.datepicker._isDisabledDatepicker(input) || $.datepicker._lastInput == input) // already here
|
523 |
+
return;
|
524 |
+
var inst = $.datepicker._getInst(input);
|
525 |
+
var beforeShow = $.datepicker._get(inst, 'beforeShow');
|
526 |
+
extendRemove(inst.settings, (beforeShow ? beforeShow.apply(input, [input, inst]) : {}));
|
527 |
+
$.datepicker._hideDatepicker(null, '');
|
528 |
+
$.datepicker._lastInput = input;
|
529 |
+
$.datepicker._setDateFromField(inst);
|
530 |
+
if ($.datepicker._inDialog) // hide cursor
|
531 |
+
input.value = '';
|
532 |
+
if (!$.datepicker._pos) { // position below input
|
533 |
+
$.datepicker._pos = $.datepicker._findPos(input);
|
534 |
+
$.datepicker._pos[1] += input.offsetHeight; // add the height
|
535 |
+
}
|
536 |
+
var isFixed = false;
|
537 |
+
$(input).parents().each(function() {
|
538 |
+
isFixed |= $(this).css('position') == 'fixed';
|
539 |
+
return !isFixed;
|
540 |
+
});
|
541 |
+
if (isFixed && $.browser.opera) { // correction for Opera when fixed and scrolled
|
542 |
+
$.datepicker._pos[0] -= document.documentElement.scrollLeft;
|
543 |
+
$.datepicker._pos[1] -= document.documentElement.scrollTop;
|
544 |
+
}
|
545 |
+
var offset = {left: $.datepicker._pos[0], top: $.datepicker._pos[1]};
|
546 |
+
$.datepicker._pos = null;
|
547 |
+
inst.rangeStart = null;
|
548 |
+
// determine sizing offscreen
|
549 |
+
inst.dpDiv.css({position: 'absolute', display: 'block', top: '-1000px'});
|
550 |
+
$.datepicker._updateDatepicker(inst);
|
551 |
+
// fix width for dynamic number of date pickers
|
552 |
+
// and adjust position before showing
|
553 |
+
offset = $.datepicker._checkOffset(inst, offset, isFixed);
|
554 |
+
inst.dpDiv.css({position: ($.datepicker._inDialog && $.blockUI ?
|
555 |
+
'static' : (isFixed ? 'fixed' : 'absolute')), display: 'none',
|
556 |
+
left: offset.left + 'px', top: offset.top + 'px'});
|
557 |
+
if (!inst.inline) {
|
558 |
+
var showAnim = $.datepicker._get(inst, 'showAnim') || 'show';
|
559 |
+
var duration = $.datepicker._get(inst, 'duration');
|
560 |
+
var postProcess = function() {
|
561 |
+
$.datepicker._datepickerShowing = true;
|
562 |
+
if ($.browser.msie && parseInt($.browser.version,10) < 7) // fix IE < 7 select problems
|
563 |
+
$('iframe.ui-datepicker-cover').css({width: inst.dpDiv.width() + 4,
|
564 |
+
height: inst.dpDiv.height() + 4});
|
565 |
+
};
|
566 |
+
if ($.effects && $.effects[showAnim])
|
567 |
+
inst.dpDiv.show(showAnim, $.datepicker._get(inst, 'showOptions'), duration, postProcess);
|
568 |
+
else
|
569 |
+
inst.dpDiv[showAnim](duration, postProcess);
|
570 |
+
if (duration == '')
|
571 |
+
postProcess();
|
572 |
+
if (inst.input[0].type != 'hidden')
|
573 |
+
inst.input[0].focus();
|
574 |
+
$.datepicker._curInst = inst;
|
575 |
+
}
|
576 |
+
},
|
577 |
+
|
578 |
+
/* Generate the date picker content. */
|
579 |
+
_updateDatepicker: function(inst) {
|
580 |
+
var dims = {width: inst.dpDiv.width() + 4,
|
581 |
+
height: inst.dpDiv.height() + 4};
|
582 |
+
var self = this;
|
583 |
+
inst.dpDiv.empty().append(this._generateHTML(inst))
|
584 |
+
.find('iframe.ui-datepicker-cover').
|
585 |
+
css({width: dims.width, height: dims.height})
|
586 |
+
.end()
|
587 |
+
.find('button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a')
|
588 |
+
.bind('mouseout', function(){
|
589 |
+
$(this).removeClass('ui-state-hover');
|
590 |
+
if(this.className.indexOf('ui-datepicker-prev') != -1) $(this).removeClass('ui-datepicker-prev-hover');
|
591 |
+
if(this.className.indexOf('ui-datepicker-next') != -1) $(this).removeClass('ui-datepicker-next-hover');
|
592 |
+
})
|
593 |
+
.bind('mouseover', function(){
|
594 |
+
if (!self._isDisabledDatepicker( inst.inline ? inst.dpDiv.parent()[0] : inst.input[0])) {
|
595 |
+
$(this).parents('.ui-datepicker-calendar').find('a').removeClass('ui-state-hover');
|
596 |
+
$(this).addClass('ui-state-hover');
|
597 |
+
if(this.className.indexOf('ui-datepicker-prev') != -1) $(this).addClass('ui-datepicker-prev-hover');
|
598 |
+
if(this.className.indexOf('ui-datepicker-next') != -1) $(this).addClass('ui-datepicker-next-hover');
|
599 |
+
}
|
600 |
+
})
|
601 |
+
.end()
|
602 |
+
.find('.' + this._dayOverClass + ' a')
|
603 |
+
.trigger('mouseover')
|
604 |
+
.end();
|
605 |
+
var numMonths = this._getNumberOfMonths(inst);
|
606 |
+
var cols = numMonths[1];
|
607 |
+
var width = 17;
|
608 |
+
if (cols > 1) {
|
609 |
+
inst.dpDiv.addClass('ui-datepicker-multi-' + cols).css('width', (width * cols) + 'em');
|
610 |
+
} else {
|
611 |
+
inst.dpDiv.removeClass('ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4').width('');
|
612 |
+
}
|
613 |
+
inst.dpDiv[(numMonths[0] != 1 || numMonths[1] != 1 ? 'add' : 'remove') +
|
614 |
+
'Class']('ui-datepicker-multi');
|
615 |
+
inst.dpDiv[(this._get(inst, 'isRTL') ? 'add' : 'remove') +
|
616 |
+
'Class']('ui-datepicker-rtl');
|
617 |
+
if (inst.input && inst.input[0].type != 'hidden' && inst == $.datepicker._curInst)
|
618 |
+
$(inst.input[0]).focus();
|
619 |
+
},
|
620 |
+
|
621 |
+
/* Check positioning to remain on screen. */
|
622 |
+
_checkOffset: function(inst, offset, isFixed) {
|
623 |
+
var dpWidth = inst.dpDiv.outerWidth();
|
624 |
+
var dpHeight = inst.dpDiv.outerHeight();
|
625 |
+
var inputWidth = inst.input ? inst.input.outerWidth() : 0;
|
626 |
+
var inputHeight = inst.input ? inst.input.outerHeight() : 0;
|
627 |
+
var viewWidth = (window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth) + $(document).scrollLeft();
|
628 |
+
var viewHeight = (window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight) + $(document).scrollTop();
|
629 |
+
|
630 |
+
offset.left -= (this._get(inst, 'isRTL') ? (dpWidth - inputWidth) : 0);
|
631 |
+
offset.left -= (isFixed && offset.left == inst.input.offset().left) ? $(document).scrollLeft() : 0;
|
632 |
+
offset.top -= (isFixed && offset.top == (inst.input.offset().top + inputHeight)) ? $(document).scrollTop() : 0;
|
633 |
+
|
634 |
+
// now check if datepicker is showing outside window viewport - move to a better place if so.
|
635 |
+
offset.left -= (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ? Math.abs(offset.left + dpWidth - viewWidth) : 0;
|
636 |
+
offset.top -= (offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ? Math.abs(offset.top + dpHeight + inputHeight*2 - viewHeight) : 0;
|
637 |
+
|
638 |
+
return offset;
|
639 |
+
},
|
640 |
+
|
641 |
+
/* Find an object's position on the screen. */
|
642 |
+
_findPos: function(obj) {
|
643 |
+
while (obj && (obj.type == 'hidden' || obj.nodeType != 1)) {
|
644 |
+
obj = obj.nextSibling;
|
645 |
+
}
|
646 |
+
var position = $(obj).offset();
|
647 |
+
return [position.left, position.top];
|
648 |
+
},
|
649 |
+
|
650 |
+
/* Hide the date picker from view.
|
651 |
+
@param input element - the input field attached to the date picker
|
652 |
+
@param duration string - the duration over which to close the date picker */
|
653 |
+
_hideDatepicker: function(input, duration) {
|
654 |
+
var inst = this._curInst;
|
655 |
+
if (!inst || (input && inst != $.data(input, PROP_NAME)))
|
656 |
+
return;
|
657 |
+
if (inst.stayOpen)
|
658 |
+
this._selectDate('#' + inst.id, this._formatDate(inst,
|
659 |
+
inst.currentDay, inst.currentMonth, inst.currentYear));
|
660 |
+
inst.stayOpen = false;
|
661 |
+
if (this._datepickerShowing) {
|
662 |
+
duration = (duration != null ? duration : this._get(inst, 'duration'));
|
663 |
+
var showAnim = this._get(inst, 'showAnim');
|
664 |
+
var postProcess = function() {
|
665 |
+
$.datepicker._tidyDialog(inst);
|
666 |
+
};
|
667 |
+
if (duration != '' && $.effects && $.effects[showAnim])
|
668 |
+
inst.dpDiv.hide(showAnim, $.datepicker._get(inst, 'showOptions'),
|
669 |
+
duration, postProcess);
|
670 |
+
else
|
671 |
+
inst.dpDiv[(duration == '' ? 'hide' : (showAnim == 'slideDown' ? 'slideUp' :
|
672 |
+
(showAnim == 'fadeIn' ? 'fadeOut' : 'hide')))](duration, postProcess);
|
673 |
+
if (duration == '')
|
674 |
+
this._tidyDialog(inst);
|
675 |
+
var onClose = this._get(inst, 'onClose');
|
676 |
+
if (onClose)
|
677 |
+
onClose.apply((inst.input ? inst.input[0] : null),
|
678 |
+
[(inst.input ? inst.input.val() : ''), inst]); // trigger custom callback
|
679 |
+
this._datepickerShowing = false;
|
680 |
+
this._lastInput = null;
|
681 |
+
if (this._inDialog) {
|
682 |
+
this._dialogInput.css({ position: 'absolute', left: '0', top: '-100px' });
|
683 |
+
if ($.blockUI) {
|
684 |
+
$.unblockUI();
|
685 |
+
$('body').append(this.dpDiv);
|
686 |
+
}
|
687 |
+
}
|
688 |
+
this._inDialog = false;
|
689 |
+
}
|
690 |
+
this._curInst = null;
|
691 |
+
},
|
692 |
+
|
693 |
+
/* Tidy up after a dialog display. */
|
694 |
+
_tidyDialog: function(inst) {
|
695 |
+
inst.dpDiv.removeClass(this._dialogClass).unbind('.ui-datepicker-calendar');
|
696 |
+
},
|
697 |
+
|
698 |
+
/* Close date picker if clicked elsewhere. */
|
699 |
+
_checkExternalClick: function(event) {
|
700 |
+
if (!$.datepicker._curInst)
|
701 |
+
return;
|
702 |
+
var $target = $(event.target);
|
703 |
+
if (($target.parents('#' + $.datepicker._mainDivId).length == 0) &&
|
704 |
+
!$target.hasClass($.datepicker.markerClassName) &&
|
705 |
+
!$target.hasClass($.datepicker._triggerClass) &&
|
706 |
+
$.datepicker._datepickerShowing && !($.datepicker._inDialog && $.blockUI))
|
707 |
+
$.datepicker._hideDatepicker(null, '');
|
708 |
+
},
|
709 |
+
|
710 |
+
/* Adjust one of the date sub-fields. */
|
711 |
+
_adjustDate: function(id, offset, period) {
|
712 |
+
var target = $(id);
|
713 |
+
var inst = this._getInst(target[0]);
|
714 |
+
if (this._isDisabledDatepicker(target[0])) {
|
715 |
+
return;
|
716 |
+
}
|
717 |
+
this._adjustInstDate(inst, offset +
|
718 |
+
(period == 'M' ? this._get(inst, 'showCurrentAtPos') : 0), // undo positioning
|
719 |
+
period);
|
720 |
+
this._updateDatepicker(inst);
|
721 |
+
},
|
722 |
+
|
723 |
+
/* Action for current link. */
|
724 |
+
_gotoToday: function(id) {
|
725 |
+
var target = $(id);
|
726 |
+
var inst = this._getInst(target[0]);
|
727 |
+
if (this._get(inst, 'gotoCurrent') && inst.currentDay) {
|
728 |
+
inst.selectedDay = inst.currentDay;
|
729 |
+
inst.drawMonth = inst.selectedMonth = inst.currentMonth;
|
730 |
+
inst.drawYear = inst.selectedYear = inst.currentYear;
|
731 |
+
}
|
732 |
+
else {
|
733 |
+
var date = new Date();
|
734 |
+
inst.selectedDay = date.getDate();
|
735 |
+
inst.drawMonth = inst.selectedMonth = date.getMonth();
|
736 |
+
inst.drawYear = inst.selectedYear = date.getFullYear();
|
737 |
+
}
|
738 |
+
this._notifyChange(inst);
|
739 |
+
this._adjustDate(target);
|
740 |
+
},
|
741 |
+
|
742 |
+
/* Action for selecting a new month/year. */
|
743 |
+
_selectMonthYear: function(id, select, period) {
|
744 |
+
var target = $(id);
|
745 |
+
var inst = this._getInst(target[0]);
|
746 |
+
inst._selectingMonthYear = false;
|
747 |
+
inst['selected' + (period == 'M' ? 'Month' : 'Year')] =
|
748 |
+
inst['draw' + (period == 'M' ? 'Month' : 'Year')] =
|
749 |
+
parseInt(select.options[select.selectedIndex].value,10);
|
750 |
+
this._notifyChange(inst);
|
751 |
+
this._adjustDate(target);
|
752 |
+
},
|
753 |
+
|
754 |
+
/* Restore input focus after not changing month/year. */
|
755 |
+
_clickMonthYear: function(id) {
|
756 |
+
var target = $(id);
|
757 |
+
var inst = this._getInst(target[0]);
|
758 |
+
if (inst.input && inst._selectingMonthYear && !$.browser.msie)
|
759 |
+
inst.input[0].focus();
|
760 |
+
inst._selectingMonthYear = !inst._selectingMonthYear;
|
761 |
+
},
|
762 |
+
|
763 |
+
/* Action for selecting a day. */
|
764 |
+
_selectDay: function(id, month, year, td) {
|
765 |
+
var target = $(id);
|
766 |
+
if ($(td).hasClass(this._unselectableClass) || this._isDisabledDatepicker(target[0])) {
|
767 |
+
return;
|
768 |
+
}
|
769 |
+
var inst = this._getInst(target[0]);
|
770 |
+
inst.selectedDay = inst.currentDay = $('a', td).html();
|
771 |
+
inst.selectedMonth = inst.currentMonth = month;
|
772 |
+
inst.selectedYear = inst.currentYear = year;
|
773 |
+
if (inst.stayOpen) {
|
774 |
+
inst.endDay = inst.endMonth = inst.endYear = null;
|
775 |
+
}
|
776 |
+
this._selectDate(id, this._formatDate(inst,
|
777 |
+
inst.currentDay, inst.currentMonth, inst.currentYear));
|
778 |
+
if (inst.stayOpen) {
|
779 |
+
inst.rangeStart = this._daylightSavingAdjust(
|
780 |
+
new Date(inst.currentYear, inst.currentMonth, inst.currentDay));
|
781 |
+
this._updateDatepicker(inst);
|
782 |
+
}
|
783 |
+
},
|
784 |
+
|
785 |
+
/* Erase the input field and hide the date picker. */
|
786 |
+
_clearDate: function(id) {
|
787 |
+
var target = $(id);
|
788 |
+
var inst = this._getInst(target[0]);
|
789 |
+
inst.stayOpen = false;
|
790 |
+
inst.endDay = inst.endMonth = inst.endYear = inst.rangeStart = null;
|
791 |
+
this._selectDate(target, '');
|
792 |
+
},
|
793 |
+
|
794 |
+
/* Update the input field with the selected date. */
|
795 |
+
_selectDate: function(id, dateStr) {
|
796 |
+
var target = $(id);
|
797 |
+
var inst = this._getInst(target[0]);
|
798 |
+
dateStr = (dateStr != null ? dateStr : this._formatDate(inst));
|
799 |
+
if (inst.input)
|
800 |
+
inst.input.val(dateStr);
|
801 |
+
this._updateAlternate(inst);
|
802 |
+
var onSelect = this._get(inst, 'onSelect');
|
803 |
+
if (onSelect)
|
804 |
+
onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]); // trigger custom callback
|
805 |
+
else if (inst.input)
|
806 |
+
inst.input.trigger('change'); // fire the change event
|
807 |
+
if (inst.inline)
|
808 |
+
this._updateDatepicker(inst);
|
809 |
+
else if (!inst.stayOpen) {
|
810 |
+
this._hideDatepicker(null, this._get(inst, 'duration'));
|
811 |
+
this._lastInput = inst.input[0];
|
812 |
+
if (typeof(inst.input[0]) != 'object')
|
813 |
+
inst.input[0].focus(); // restore focus
|
814 |
+
this._lastInput = null;
|
815 |
+
}
|
816 |
+
},
|
817 |
+
|
818 |
+
/* Update any alternate field to synchronise with the main field. */
|
819 |
+
_updateAlternate: function(inst) {
|
820 |
+
var altField = this._get(inst, 'altField');
|
821 |
+
if (altField) { // update alternate field too
|
822 |
+
var altFormat = this._get(inst, 'altFormat') || this._get(inst, 'dateFormat');
|
823 |
+
var date = this._getDate(inst);
|
824 |
+
dateStr = this.formatDate(altFormat, date, this._getFormatConfig(inst));
|
825 |
+
$(altField).each(function() { $(this).val(dateStr); });
|
826 |
+
}
|
827 |
+
},
|
828 |
+
|
829 |
+
/* Set as beforeShowDay function to prevent selection of weekends.
|
830 |
+
@param date Date - the date to customise
|
831 |
+
@return [boolean, string] - is this date selectable?, what is its CSS class? */
|
832 |
+
noWeekends: function(date) {
|
833 |
+
var day = date.getDay();
|
834 |
+
return [(day > 0 && day < 6), ''];
|
835 |
+
},
|
836 |
+
|
837 |
+
/* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition.
|
838 |
+
@param date Date - the date to get the week for
|
839 |
+
@return number - the number of the week within the year that contains this date */
|
840 |
+
iso8601Week: function(date) {
|
841 |
+
var checkDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
842 |
+
var firstMon = new Date(checkDate.getFullYear(), 1 - 1, 4); // First week always contains 4 Jan
|
843 |
+
var firstDay = firstMon.getDay() || 7; // Day of week: Mon = 1, ..., Sun = 7
|
844 |
+
firstMon.setDate(firstMon.getDate() + 1 - firstDay); // Preceding Monday
|
845 |
+
if (firstDay < 4 && checkDate < firstMon) { // Adjust first three days in year if necessary
|
846 |
+
checkDate.setDate(checkDate.getDate() - 3); // Generate for previous year
|
847 |
+
return $.datepicker.iso8601Week(checkDate);
|
848 |
+
} else if (checkDate > new Date(checkDate.getFullYear(), 12 - 1, 28)) { // Check last three days in year
|
849 |
+
firstDay = new Date(checkDate.getFullYear() + 1, 1 - 1, 4).getDay() || 7;
|
850 |
+
if (firstDay > 4 && (checkDate.getDay() || 7) < firstDay - 3) { // Adjust if necessary
|
851 |
+
return 1;
|
852 |
+
}
|
853 |
+
}
|
854 |
+
return Math.floor(((checkDate - firstMon) / 86400000) / 7) + 1; // Weeks to given date
|
855 |
+
},
|
856 |
+
|
857 |
+
/* Parse a string value into a date object.
|
858 |
+
See formatDate below for the possible formats.
|
859 |
+
|
860 |
+
@param format string - the expected format of the date
|
861 |
+
@param value string - the date in the above format
|
862 |
+
@param settings Object - attributes include:
|
863 |
+
shortYearCutoff number - the cutoff year for determining the century (optional)
|
864 |
+
dayNamesShort string[7] - abbreviated names of the days from Sunday (optional)
|
865 |
+
dayNames string[7] - names of the days from Sunday (optional)
|
866 |
+
monthNamesShort string[12] - abbreviated names of the months (optional)
|
867 |
+
monthNames string[12] - names of the months (optional)
|
868 |
+
@return Date - the extracted date value or null if value is blank */
|
869 |
+
parseDate: function (format, value, settings) {
|
870 |
+
if (format == null || value == null)
|
871 |
+
throw 'Invalid arguments';
|
872 |
+
value = (typeof value == 'object' ? value.toString() : value + '');
|
873 |
+
if (value == '')
|
874 |
+
return null;
|
875 |
+
var shortYearCutoff = (settings ? settings.shortYearCutoff : null) || this._defaults.shortYearCutoff;
|
876 |
+
var dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort;
|
877 |
+
var dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames;
|
878 |
+
var monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort;
|
879 |
+
var monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames;
|
880 |
+
var year = -1;
|
881 |
+
var month = -1;
|
882 |
+
var day = -1;
|
883 |
+
var doy = -1;
|
884 |
+
var literal = false;
|
885 |
+
// Check whether a format character is doubled
|
886 |
+
var lookAhead = function(match) {
|
887 |
+
var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) == match);
|
888 |
+
if (matches)
|
889 |
+
iFormat++;
|
890 |
+
return matches;
|
891 |
+
};
|
892 |
+
// Extract a number from the string value
|
893 |
+
var getNumber = function(match) {
|
894 |
+
lookAhead(match);
|
895 |
+
var origSize = (match == '@' ? 14 : (match == 'y' ? 4 : (match == 'o' ? 3 : 2)));
|
896 |
+
var size = origSize;
|
897 |
+
var num = 0;
|
898 |
+
while (size > 0 && iValue < value.length &&
|
899 |
+
value.charAt(iValue) >= '0' && value.charAt(iValue) <= '9') {
|
900 |
+
num = num * 10 + parseInt(value.charAt(iValue++),10);
|
901 |
+
size--;
|
902 |
+
}
|
903 |
+
if (size == origSize)
|
904 |
+
throw 'Missing number at position ' + iValue;
|
905 |
+
return num;
|
906 |
+
};
|
907 |
+
// Extract a name from the string value and convert to an index
|
908 |
+
var getName = function(match, shortNames, longNames) {
|
909 |
+
var names = (lookAhead(match) ? longNames : shortNames);
|
910 |
+
var size = 0;
|
911 |
+
for (var j = 0; j < names.length; j++)
|
912 |
+
size = Math.max(size, names[j].length);
|
913 |
+
var name = '';
|
914 |
+
var iInit = iValue;
|
915 |
+
while (size > 0 && iValue < value.length) {
|
916 |
+
name += value.charAt(iValue++);
|
917 |
+
for (var i = 0; i < names.length; i++)
|
918 |
+
if (name == names[i])
|
919 |
+
return i + 1;
|
920 |
+
size--;
|
921 |
+
}
|
922 |
+
throw 'Unknown name at position ' + iInit;
|
923 |
+
};
|
924 |
+
// Confirm that a literal character matches the string value
|
925 |
+
var checkLiteral = function() {
|
926 |
+
if (value.charAt(iValue) != format.charAt(iFormat))
|
927 |
+
throw 'Unexpected literal at position ' + iValue;
|
928 |
+
iValue++;
|
929 |
+
};
|
930 |
+
var iValue = 0;
|
931 |
+
for (var iFormat = 0; iFormat < format.length; iFormat++) {
|
932 |
+
if (literal)
|
933 |
+
if (format.charAt(iFormat) == "'" && !lookAhead("'"))
|
934 |
+
literal = false;
|
935 |
+
else
|
936 |
+
checkLiteral();
|
937 |
+
else
|
938 |
+
switch (format.charAt(iFormat)) {
|
939 |
+
case 'd':
|
940 |
+
day = getNumber('d');
|
941 |
+
break;
|
942 |
+
case 'D':
|
943 |
+
getName('D', dayNamesShort, dayNames);
|
944 |
+
break;
|
945 |
+
case 'o':
|
946 |
+
doy = getNumber('o');
|
947 |
+
break;
|
948 |
+
case 'm':
|
949 |
+
month = getNumber('m');
|
950 |
+
break;
|
951 |
+
case 'M':
|
952 |
+
month = getName('M', monthNamesShort, monthNames);
|
953 |
+
break;
|
954 |
+
case 'y':
|
955 |
+
year = getNumber('y');
|
956 |
+
break;
|
957 |
+
case '@':
|
958 |
+
var date = new Date(getNumber('@'));
|
959 |
+
year = date.getFullYear();
|
960 |
+
month = date.getMonth() + 1;
|
961 |
+
day = date.getDate();
|
962 |
+
break;
|
963 |
+
case "'":
|
964 |
+
if (lookAhead("'"))
|
965 |
+
checkLiteral();
|
966 |
+
else
|
967 |
+
literal = true;
|
968 |
+
break;
|
969 |
+
default:
|
970 |
+
checkLiteral();
|
971 |
+
}
|
972 |
+
}
|
973 |
+
if (year == -1)
|
974 |
+
year = new Date().getFullYear();
|
975 |
+
else if (year < 100)
|
976 |
+
year += new Date().getFullYear() - new Date().getFullYear() % 100 +
|
977 |
+
(year <= shortYearCutoff ? 0 : -100);
|
978 |
+
if (doy > -1) {
|
979 |
+
month = 1;
|
980 |
+
day = doy;
|
981 |
+
do {
|
982 |
+
var dim = this._getDaysInMonth(year, month - 1);
|
983 |
+
if (day <= dim)
|
984 |
+
break;
|
985 |
+
month++;
|
986 |
+
day -= dim;
|
987 |
+
} while (true);
|
988 |
+
}
|
989 |
+
var date = this._daylightSavingAdjust(new Date(year, month - 1, day));
|
990 |
+
if (date.getFullYear() != year || date.getMonth() + 1 != month || date.getDate() != day)
|
991 |
+
throw 'Invalid date'; // E.g. 31/02/*
|
992 |
+
return date;
|
993 |
+
},
|
994 |
+
|
995 |
+
/* Standard date formats. */
|
996 |
+
ATOM: 'yy-mm-dd', // RFC 3339 (ISO 8601)
|
997 |
+
COOKIE: 'D, dd M yy',
|
998 |
+
ISO_8601: 'yy-mm-dd',
|
999 |
+
RFC_822: 'D, d M y',
|
1000 |
+
RFC_850: 'DD, dd-M-y',
|
1001 |
+
RFC_1036: 'D, d M y',
|
1002 |
+
RFC_1123: 'D, d M yy',
|
1003 |
+
RFC_2822: 'D, d M yy',
|
1004 |
+
RSS: 'D, d M y', // RFC 822
|
1005 |
+
TIMESTAMP: '@',
|
1006 |
+
W3C: 'yy-mm-dd', // ISO 8601
|
1007 |
+
|
1008 |
+
/* Format a date object into a string value.
|
1009 |
+
The format can be combinations of the following:
|
1010 |
+
d - day of month (no leading zero)
|
1011 |
+
dd - day of month (two digit)
|
1012 |
+
o - day of year (no leading zeros)
|
1013 |
+
oo - day of year (three digit)
|
1014 |
+
D - day name short
|
1015 |
+
DD - day name long
|
1016 |
+
m - month of year (no leading zero)
|
1017 |
+
mm - month of year (two digit)
|
1018 |
+
M - month name short
|
1019 |
+
MM - month name long
|
1020 |
+
y - year (two digit)
|
1021 |
+
yy - year (four digit)
|
1022 |
+
@ - Unix timestamp (ms since 01/01/1970)
|
1023 |
+
'...' - literal text
|
1024 |
+
'' - single quote
|
1025 |
+
|
1026 |
+
@param format string - the desired format of the date
|
1027 |
+
@param date Date - the date value to format
|
1028 |
+
@param settings Object - attributes include:
|
1029 |
+
dayNamesShort string[7] - abbreviated names of the days from Sunday (optional)
|
1030 |
+
dayNames string[7] - names of the days from Sunday (optional)
|
1031 |
+
monthNamesShort string[12] - abbreviated names of the months (optional)
|
1032 |
+
monthNames string[12] - names of the months (optional)
|
1033 |
+
@return string - the date in the above format */
|
1034 |
+
formatDate: function (format, date, settings) {
|
1035 |
+
if (!date)
|
1036 |
+
return '';
|
1037 |
+
var dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort;
|
1038 |
+
var dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames;
|
1039 |
+
var monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort;
|
1040 |
+
var monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames;
|
1041 |
+
// Check whether a format character is doubled
|
1042 |
+
var lookAhead = function(match) {
|
1043 |
+
var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) == match);
|
1044 |
+
if (matches)
|
1045 |
+
iFormat++;
|
1046 |
+
return matches;
|
1047 |
+
};
|
1048 |
+
// Format a number, with leading zero if necessary
|
1049 |
+
var formatNumber = function(match, value, len) {
|
1050 |
+
var num = '' + value;
|
1051 |
+
if (lookAhead(match))
|
1052 |
+
while (num.length < len)
|
1053 |
+
num = '0' + num;
|
1054 |
+
return num;
|
1055 |
+
};
|
1056 |
+
// Format a name, short or long as requested
|
1057 |
+
var formatName = function(match, value, shortNames, longNames) {
|
1058 |
+
return (lookAhead(match) ? longNames[value] : shortNames[value]);
|
1059 |
+
};
|
1060 |
+
var output = '';
|
1061 |
+
var literal = false;
|
1062 |
+
if (date)
|
1063 |
+
for (var iFormat = 0; iFormat < format.length; iFormat++) {
|
1064 |
+
if (literal)
|
1065 |
+
if (format.charAt(iFormat) == "'" && !lookAhead("'"))
|
1066 |
+
literal = false;
|
1067 |
+
else
|
1068 |
+
output += format.charAt(iFormat);
|
1069 |
+
else
|
1070 |
+
switch (format.charAt(iFormat)) {
|
1071 |
+
case 'd':
|
1072 |
+
output += formatNumber('d', date.getDate(), 2);
|
1073 |
+
break;
|
1074 |
+
case 'D':
|
1075 |
+
output += formatName('D', date.getDay(), dayNamesShort, dayNames);
|
1076 |
+
break;
|
1077 |
+
case 'o':
|
1078 |
+
var doy = date.getDate();
|
1079 |
+
for (var m = date.getMonth() - 1; m >= 0; m--)
|
1080 |
+
doy += this._getDaysInMonth(date.getFullYear(), m);
|
1081 |
+
output += formatNumber('o', doy, 3);
|
1082 |
+
break;
|
1083 |
+
case 'm':
|
1084 |
+
output += formatNumber('m', date.getMonth() + 1, 2);
|
1085 |
+
break;
|
1086 |
+
case 'M':
|
1087 |
+
output += formatName('M', date.getMonth(), monthNamesShort, monthNames);
|
1088 |
+
break;
|
1089 |
+
case 'y':
|
1090 |
+
output += (lookAhead('y') ? date.getFullYear() :
|
1091 |
+
(date.getYear() % 100 < 10 ? '0' : '') + date.getYear() % 100);
|
1092 |
+
break;
|
1093 |
+
case '@':
|
1094 |
+
output += date.getTime();
|
1095 |
+
break;
|
1096 |
+
case "'":
|
1097 |
+
if (lookAhead("'"))
|
1098 |
+
output += "'";
|
1099 |
+
else
|
1100 |
+
literal = true;
|
1101 |
+
break;
|
1102 |
+
default:
|
1103 |
+
output += format.charAt(iFormat);
|
1104 |
+
}
|
1105 |
+
}
|
1106 |
+
return output;
|
1107 |
+
},
|
1108 |
+
|
1109 |
+
/* Extract all possible characters from the date format. */
|
1110 |
+
_possibleChars: function (format) {
|
1111 |
+
var chars = '';
|
1112 |
+
var literal = false;
|
1113 |
+
for (var iFormat = 0; iFormat < format.length; iFormat++)
|
1114 |
+
if (literal)
|
1115 |
+
if (format.charAt(iFormat) == "'" && !lookAhead("'"))
|
1116 |
+
literal = false;
|
1117 |
+
else
|
1118 |
+
chars += format.charAt(iFormat);
|
1119 |
+
else
|
1120 |
+
switch (format.charAt(iFormat)) {
|
1121 |
+
case 'd': case 'm': case 'y': case '@':
|
1122 |
+
chars += '0123456789';
|
1123 |
+
break;
|
1124 |
+
case 'D': case 'M':
|
1125 |
+
return null; // Accept anything
|
1126 |
+
case "'":
|
1127 |
+
if (lookAhead("'"))
|
1128 |
+
chars += "'";
|
1129 |
+
else
|
1130 |
+
literal = true;
|
1131 |
+
break;
|
1132 |
+
default:
|
1133 |
+
chars += format.charAt(iFormat);
|
1134 |
+
}
|
1135 |
+
return chars;
|
1136 |
+
},
|
1137 |
+
|
1138 |
+
/* Get a setting value, defaulting if necessary. */
|
1139 |
+
_get: function(inst, name) {
|
1140 |
+
return inst.settings[name] !== undefined ?
|
1141 |
+
inst.settings[name] : this._defaults[name];
|
1142 |
+
},
|
1143 |
+
|
1144 |
+
/* Parse existing date and initialise date picker. */
|
1145 |
+
_setDateFromField: function(inst) {
|
1146 |
+
var dateFormat = this._get(inst, 'dateFormat');
|
1147 |
+
var dates = inst.input ? inst.input.val() : null;
|
1148 |
+
inst.endDay = inst.endMonth = inst.endYear = null;
|
1149 |
+
var date = defaultDate = this._getDefaultDate(inst);
|
1150 |
+
var settings = this._getFormatConfig(inst);
|
1151 |
+
try {
|
1152 |
+
date = this.parseDate(dateFormat, dates, settings) || defaultDate;
|
1153 |
+
} catch (event) {
|
1154 |
+
this.log(event);
|
1155 |
+
date = defaultDate;
|
1156 |
+
}
|
1157 |
+
inst.selectedDay = date.getDate();
|
1158 |
+
inst.drawMonth = inst.selectedMonth = date.getMonth();
|
1159 |
+
inst.drawYear = inst.selectedYear = date.getFullYear();
|
1160 |
+
inst.currentDay = (dates ? date.getDate() : 0);
|
1161 |
+
inst.currentMonth = (dates ? date.getMonth() : 0);
|
1162 |
+
inst.currentYear = (dates ? date.getFullYear() : 0);
|
1163 |
+
this._adjustInstDate(inst);
|
1164 |
+
},
|
1165 |
+
|
1166 |
+
/* Retrieve the default date shown on opening. */
|
1167 |
+
_getDefaultDate: function(inst) {
|
1168 |
+
var date = this._determineDate(this._get(inst, 'defaultDate'), new Date());
|
1169 |
+
var minDate = this._getMinMaxDate(inst, 'min', true);
|
1170 |
+
var maxDate = this._getMinMaxDate(inst, 'max');
|
1171 |
+
date = (minDate && date < minDate ? minDate : date);
|
1172 |
+
date = (maxDate && date > maxDate ? maxDate : date);
|
1173 |
+
return date;
|
1174 |
+
},
|
1175 |
+
|
1176 |
+
/* A date may be specified as an exact value or a relative one. */
|
1177 |
+
_determineDate: function(date, defaultDate) {
|
1178 |
+
var offsetNumeric = function(offset) {
|
1179 |
+
var date = new Date();
|
1180 |
+
date.setDate(date.getDate() + offset);
|
1181 |
+
return date;
|
1182 |
+
};
|
1183 |
+
var offsetString = function(offset, getDaysInMonth) {
|
1184 |
+
var date = new Date();
|
1185 |
+
var year = date.getFullYear();
|
1186 |
+
var month = date.getMonth();
|
1187 |
+
var day = date.getDate();
|
1188 |
+
var pattern = /([+-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g;
|
1189 |
+
var matches = pattern.exec(offset);
|
1190 |
+
while (matches) {
|
1191 |
+
switch (matches[2] || 'd') {
|
1192 |
+
case 'd' : case 'D' :
|
1193 |
+
day += parseInt(matches[1],10); break;
|
1194 |
+
case 'w' : case 'W' :
|
1195 |
+
day += parseInt(matches[1],10) * 7; break;
|
1196 |
+
case 'm' : case 'M' :
|
1197 |
+
month += parseInt(matches[1],10);
|
1198 |
+
day = Math.min(day, getDaysInMonth(year, month));
|
1199 |
+
break;
|
1200 |
+
case 'y': case 'Y' :
|
1201 |
+
year += parseInt(matches[1],10);
|
1202 |
+
day = Math.min(day, getDaysInMonth(year, month));
|
1203 |
+
break;
|
1204 |
+
}
|
1205 |
+
matches = pattern.exec(offset);
|
1206 |
+
}
|
1207 |
+
return new Date(year, month, day);
|
1208 |
+
};
|
1209 |
+
date = (date == null ? defaultDate :
|
1210 |
+
(typeof date == 'string' ? offsetString(date, this._getDaysInMonth) :
|
1211 |
+
(typeof date == 'number' ? (isNaN(date) ? defaultDate : offsetNumeric(date)) : date)));
|
1212 |
+
date = (date && date.toString() == 'Invalid Date' ? defaultDate : date);
|
1213 |
+
if (date) {
|
1214 |
+
date.setHours(0);
|
1215 |
+
date.setMinutes(0);
|
1216 |
+
date.setSeconds(0);
|
1217 |
+
date.setMilliseconds(0);
|
1218 |
+
}
|
1219 |
+
return this._daylightSavingAdjust(date);
|
1220 |
+
},
|
1221 |
+
|
1222 |
+
/* Handle switch to/from daylight saving.
|
1223 |
+
Hours may be non-zero on daylight saving cut-over:
|
1224 |
+
> 12 when midnight changeover, but then cannot generate
|
1225 |
+
midnight datetime, so jump to 1AM, otherwise reset.
|
1226 |
+
@param date (Date) the date to check
|
1227 |
+
@return (Date) the corrected date */
|
1228 |
+
_daylightSavingAdjust: function(date) {
|
1229 |
+
if (!date) return null;
|
1230 |
+
date.setHours(date.getHours() > 12 ? date.getHours() + 2 : 0);
|
1231 |
+
return date;
|
1232 |
+
},
|
1233 |
+
|
1234 |
+
/* Set the date(s) directly. */
|
1235 |
+
_setDate: function(inst, date, endDate) {
|
1236 |
+
var clear = !(date);
|
1237 |
+
var origMonth = inst.selectedMonth;
|
1238 |
+
var origYear = inst.selectedYear;
|
1239 |
+
date = this._determineDate(date, new Date());
|
1240 |
+
inst.selectedDay = inst.currentDay = date.getDate();
|
1241 |
+
inst.drawMonth = inst.selectedMonth = inst.currentMonth = date.getMonth();
|
1242 |
+
inst.drawYear = inst.selectedYear = inst.currentYear = date.getFullYear();
|
1243 |
+
if (origMonth != inst.selectedMonth || origYear != inst.selectedYear)
|
1244 |
+
this._notifyChange(inst);
|
1245 |
+
this._adjustInstDate(inst);
|
1246 |
+
if (inst.input) {
|
1247 |
+
inst.input.val(clear ? '' : this._formatDate(inst));
|
1248 |
+
}
|
1249 |
+
},
|
1250 |
+
|
1251 |
+
/* Retrieve the date(s) directly. */
|
1252 |
+
_getDate: function(inst) {
|
1253 |
+
var startDate = (!inst.currentYear || (inst.input && inst.input.val() == '') ? null :
|
1254 |
+
this._daylightSavingAdjust(new Date(
|
1255 |
+
inst.currentYear, inst.currentMonth, inst.currentDay)));
|
1256 |
+
return startDate;
|
1257 |
+
},
|
1258 |
+
|
1259 |
+
/* Generate the HTML for the current state of the date picker. */
|
1260 |
+
_generateHTML: function(inst) {
|
1261 |
+
var today = new Date();
|
1262 |
+
today = this._daylightSavingAdjust(
|
1263 |
+
new Date(today.getFullYear(), today.getMonth(), today.getDate())); // clear time
|
1264 |
+
var isRTL = this._get(inst, 'isRTL');
|
1265 |
+
var showButtonPanel = this._get(inst, 'showButtonPanel');
|
1266 |
+
var hideIfNoPrevNext = this._get(inst, 'hideIfNoPrevNext');
|
1267 |
+
var navigationAsDateFormat = this._get(inst, 'navigationAsDateFormat');
|
1268 |
+
var numMonths = this._getNumberOfMonths(inst);
|
1269 |
+
var showCurrentAtPos = this._get(inst, 'showCurrentAtPos');
|
1270 |
+
var stepMonths = this._get(inst, 'stepMonths');
|
1271 |
+
var stepBigMonths = this._get(inst, 'stepBigMonths');
|
1272 |
+
var isMultiMonth = (numMonths[0] != 1 || numMonths[1] != 1);
|
1273 |
+
var currentDate = this._daylightSavingAdjust((!inst.currentDay ? new Date(9999, 9, 9) :
|
1274 |
+
new Date(inst.currentYear, inst.currentMonth, inst.currentDay)));
|
1275 |
+
var minDate = this._getMinMaxDate(inst, 'min', true);
|
1276 |
+
var maxDate = this._getMinMaxDate(inst, 'max');
|
1277 |
+
var drawMonth = inst.drawMonth - showCurrentAtPos;
|
1278 |
+
var drawYear = inst.drawYear;
|
1279 |
+
if (drawMonth < 0) {
|
1280 |
+
drawMonth += 12;
|
1281 |
+
drawYear--;
|
1282 |
+
}
|
1283 |
+
if (maxDate) {
|
1284 |
+
var maxDraw = this._daylightSavingAdjust(new Date(maxDate.getFullYear(),
|
1285 |
+
maxDate.getMonth() - numMonths[1] + 1, maxDate.getDate()));
|
1286 |
+
maxDraw = (minDate && maxDraw < minDate ? minDate : maxDraw);
|
1287 |
+
while (this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1)) > maxDraw) {
|
1288 |
+
drawMonth--;
|
1289 |
+
if (drawMonth < 0) {
|
1290 |
+
drawMonth = 11;
|
1291 |
+
drawYear--;
|
1292 |
+
}
|
1293 |
+
}
|
1294 |
+
}
|
1295 |
+
inst.drawMonth = drawMonth;
|
1296 |
+
inst.drawYear = drawYear;
|
1297 |
+
var prevText = this._get(inst, 'prevText');
|
1298 |
+
prevText = (!navigationAsDateFormat ? prevText : this.formatDate(prevText,
|
1299 |
+
this._daylightSavingAdjust(new Date(drawYear, drawMonth - stepMonths, 1)),
|
1300 |
+
this._getFormatConfig(inst)));
|
1301 |
+
var prev = (this._canAdjustMonth(inst, -1, drawYear, drawMonth) ?
|
1302 |
+
'<a class="ui-datepicker-prev ui-corner-all" onclick="DP_jQuery.datepicker._adjustDate(\'#' + inst.id + '\', -' + stepMonths + ', \'M\');"' +
|
1303 |
+
' title="' + prevText + '"><span class="ui-icon ui-icon-circle-triangle-' + ( isRTL ? 'e' : 'w') + '">' + prevText + '</span></a>' :
|
1304 |
+
(hideIfNoPrevNext ? '' : '<a class="ui-datepicker-prev ui-corner-all ui-state-disabled" title="'+ prevText +'"><span class="ui-icon ui-icon-circle-triangle-' + ( isRTL ? 'e' : 'w') + '">' + prevText + '</span></a>'));
|
1305 |
+
var nextText = this._get(inst, 'nextText');
|
1306 |
+
nextText = (!navigationAsDateFormat ? nextText : this.formatDate(nextText,
|
1307 |
+
this._daylightSavingAdjust(new Date(drawYear, drawMonth + stepMonths, 1)),
|
1308 |
+
this._getFormatConfig(inst)));
|
1309 |
+
var next = (this._canAdjustMonth(inst, +1, drawYear, drawMonth) ?
|
1310 |
+
'<a class="ui-datepicker-next ui-corner-all" onclick="DP_jQuery.datepicker._adjustDate(\'#' + inst.id + '\', +' + stepMonths + ', \'M\');"' +
|
1311 |
+
' title="' + nextText + '"><span class="ui-icon ui-icon-circle-triangle-' + ( isRTL ? 'w' : 'e') + '">' + nextText + '</span></a>' :
|
1312 |
+
(hideIfNoPrevNext ? '' : '<a class="ui-datepicker-next ui-corner-all ui-state-disabled" title="'+ nextText + '"><span class="ui-icon ui-icon-circle-triangle-' + ( isRTL ? 'w' : 'e') + '">' + nextText + '</span></a>'));
|
1313 |
+
var currentText = this._get(inst, 'currentText');
|
1314 |
+
var gotoDate = (this._get(inst, 'gotoCurrent') && inst.currentDay ? currentDate : today);
|
1315 |
+
currentText = (!navigationAsDateFormat ? currentText :
|
1316 |
+
this.formatDate(currentText, gotoDate, this._getFormatConfig(inst)));
|
1317 |
+
var controls = (!inst.inline ? '<button type="button" class="ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all" onclick="DP_jQuery.datepicker._hideDatepicker();">' + this._get(inst, 'closeText') + '</button>' : '');
|
1318 |
+
var buttonPanel = (showButtonPanel) ? '<div class="ui-datepicker-buttonpane ui-widget-content">' + (isRTL ? controls : '') +
|
1319 |
+
(this._isInRange(inst, gotoDate) ? '<button type="button" class="ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all" onclick="DP_jQuery.datepicker._gotoToday(\'#' + inst.id + '\');"' +
|
1320 |
+
'>' + currentText + '</button>' : '') + (isRTL ? '' : controls) + '</div>' : '';
|
1321 |
+
var firstDay = parseInt(this._get(inst, 'firstDay'),10);
|
1322 |
+
firstDay = (isNaN(firstDay) ? 0 : firstDay);
|
1323 |
+
var dayNames = this._get(inst, 'dayNames');
|
1324 |
+
var dayNamesShort = this._get(inst, 'dayNamesShort');
|
1325 |
+
var dayNamesMin = this._get(inst, 'dayNamesMin');
|
1326 |
+
var monthNames = this._get(inst, 'monthNames');
|
1327 |
+
var monthNamesShort = this._get(inst, 'monthNamesShort');
|
1328 |
+
var beforeShowDay = this._get(inst, 'beforeShowDay');
|
1329 |
+
var showOtherMonths = this._get(inst, 'showOtherMonths');
|
1330 |
+
var calculateWeek = this._get(inst, 'calculateWeek') || this.iso8601Week;
|
1331 |
+
var endDate = inst.endDay ? this._daylightSavingAdjust(
|
1332 |
+
new Date(inst.endYear, inst.endMonth, inst.endDay)) : currentDate;
|
1333 |
+
var defaultDate = this._getDefaultDate(inst);
|
1334 |
+
var html = '';
|
1335 |
+
for (var row = 0; row < numMonths[0]; row++) {
|
1336 |
+
var group = '';
|
1337 |
+
for (var col = 0; col < numMonths[1]; col++) {
|
1338 |
+
var selectedDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, inst.selectedDay));
|
1339 |
+
var cornerClass = ' ui-corner-all';
|
1340 |
+
var calender = '';
|
1341 |
+
if (isMultiMonth) {
|
1342 |
+
calender += '<div class="ui-datepicker-group ui-datepicker-group-';
|
1343 |
+
switch (col) {
|
1344 |
+
case 0: calender += 'first'; cornerClass = ' ui-corner-' + (isRTL ? 'right' : 'left'); break;
|
1345 |
+
case numMonths[1]-1: calender += 'last'; cornerClass = ' ui-corner-' + (isRTL ? 'left' : 'right'); break;
|
1346 |
+
default: calender += 'middle'; cornerClass = ''; break;
|
1347 |
+
}
|
1348 |
+
calender += '">';
|
1349 |
+
}
|
1350 |
+
calender += '<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix' + cornerClass + '">' +
|
1351 |
+
(/all|left/.test(cornerClass) && row == 0 ? (isRTL ? next : prev) : '') +
|
1352 |
+
(/all|right/.test(cornerClass) && row == 0 ? (isRTL ? prev : next) : '') +
|
1353 |
+
this._generateMonthYearHeader(inst, drawMonth, drawYear, minDate, maxDate,
|
1354 |
+
selectedDate, row > 0 || col > 0, monthNames, monthNamesShort) + // draw month headers
|
1355 |
+
'</div><table class="ui-datepicker-calendar"><thead>' +
|
1356 |
+
'<tr>';
|
1357 |
+
var thead = '';
|
1358 |
+
for (var dow = 0; dow < 7; dow++) { // days of the week
|
1359 |
+
var day = (dow + firstDay) % 7;
|
1360 |
+
thead += '<th' + ((dow + firstDay + 6) % 7 >= 5 ? ' class="ui-datepicker-week-end"' : '') + '>' +
|
1361 |
+
'<span title="' + dayNames[day] + '">' + dayNamesMin[day] + '</span></th>';
|
1362 |
+
}
|
1363 |
+
calender += thead + '</tr></thead><tbody>';
|
1364 |
+
var daysInMonth = this._getDaysInMonth(drawYear, drawMonth);
|
1365 |
+
if (drawYear == inst.selectedYear && drawMonth == inst.selectedMonth)
|
1366 |
+
inst.selectedDay = Math.min(inst.selectedDay, daysInMonth);
|
1367 |
+
var leadDays = (this._getFirstDayOfMonth(drawYear, drawMonth) - firstDay + 7) % 7;
|
1368 |
+
var numRows = (isMultiMonth ? 6 : Math.ceil((leadDays + daysInMonth) / 7)); // calculate the number of rows to generate
|
1369 |
+
var printDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1 - leadDays));
|
1370 |
+
for (var dRow = 0; dRow < numRows; dRow++) { // create date picker rows
|
1371 |
+
calender += '<tr>';
|
1372 |
+
var tbody = '';
|
1373 |
+
for (var dow = 0; dow < 7; dow++) { // create date picker days
|
1374 |
+
var daySettings = (beforeShowDay ?
|
1375 |
+
beforeShowDay.apply((inst.input ? inst.input[0] : null), [printDate]) : [true, '']);
|
1376 |
+
var otherMonth = (printDate.getMonth() != drawMonth);
|
1377 |
+
var unselectable = otherMonth || !daySettings[0] ||
|
1378 |
+
(minDate && printDate < minDate) || (maxDate && printDate > maxDate);
|
1379 |
+
tbody += '<td class="' +
|
1380 |
+
((dow + firstDay + 6) % 7 >= 5 ? ' ui-datepicker-week-end' : '') + // highlight weekends
|
1381 |
+
(otherMonth ? ' ui-datepicker-other-month' : '') + // highlight days from other months
|
1382 |
+
((printDate.getTime() == selectedDate.getTime() && drawMonth == inst.selectedMonth && inst._keyEvent) || // user pressed key
|
1383 |
+
(defaultDate.getTime() == printDate.getTime() && defaultDate.getTime() == selectedDate.getTime()) ?
|
1384 |
+
// or defaultDate is current printedDate and defaultDate is selectedDate
|
1385 |
+
' ' + this._dayOverClass : '') + // highlight selected day
|
1386 |
+
(unselectable ? ' ' + this._unselectableClass + ' ui-state-disabled': '') + // highlight unselectable days
|
1387 |
+
(otherMonth && !showOtherMonths ? '' : ' ' + daySettings[1] + // highlight custom dates
|
1388 |
+
(printDate.getTime() >= currentDate.getTime() && printDate.getTime() <= endDate.getTime() ? // in current range
|
1389 |
+
' ' + this._currentClass : '') + // highlight selected day
|
1390 |
+
(printDate.getTime() == today.getTime() ? ' ui-datepicker-today' : '')) + '"' + // highlight today (if different)
|
1391 |
+
((!otherMonth || showOtherMonths) && daySettings[2] ? ' title="' + daySettings[2] + '"' : '') + // cell title
|
1392 |
+
(unselectable ? '' : ' onclick="DP_jQuery.datepicker._selectDay(\'#' +
|
1393 |
+
inst.id + '\',' + drawMonth + ',' + drawYear + ', this);return false;"') + '>' + // actions
|
1394 |
+
(otherMonth ? (showOtherMonths ? printDate.getDate() : ' ') : // display for other months
|
1395 |
+
(unselectable ? '<span class="ui-state-default">' + printDate.getDate() + '</span>' : '<a class="ui-state-default' +
|
1396 |
+
(printDate.getTime() == today.getTime() ? ' ui-state-highlight' : '') +
|
1397 |
+
(printDate.getTime() >= currentDate.getTime() && printDate.getTime() <= endDate.getTime() ? // in current range
|
1398 |
+
' ui-state-active' : '') + // highlight selected day
|
1399 |
+
'" href="#">' + printDate.getDate() + '</a>')) + '</td>'; // display for this month
|
1400 |
+
printDate.setDate(printDate.getDate() + 1);
|
1401 |
+
printDate = this._daylightSavingAdjust(printDate);
|
1402 |
+
}
|
1403 |
+
calender += tbody + '</tr>';
|
1404 |
+
}
|
1405 |
+
drawMonth++;
|
1406 |
+
if (drawMonth > 11) {
|
1407 |
+
drawMonth = 0;
|
1408 |
+
drawYear++;
|
1409 |
+
}
|
1410 |
+
calender += '</tbody></table>' + (isMultiMonth ? '</div>' +
|
1411 |
+
((numMonths[0] > 0 && col == numMonths[1]-1) ? '<div class="ui-datepicker-row-break"></div>' : '') : '');
|
1412 |
+
group += calender;
|
1413 |
+
}
|
1414 |
+
html += group;
|
1415 |
+
}
|
1416 |
+
html += buttonPanel + ($.browser.msie && parseInt($.browser.version,10) < 7 && !inst.inline ?
|
1417 |
+
'<iframe src="javascript:false;" class="ui-datepicker-cover" frameborder="0"></iframe>' : '');
|
1418 |
+
inst._keyEvent = false;
|
1419 |
+
return html;
|
1420 |
+
},
|
1421 |
+
|
1422 |
+
/* Generate the month and year header. */
|
1423 |
+
_generateMonthYearHeader: function(inst, drawMonth, drawYear, minDate, maxDate,
|
1424 |
+
selectedDate, secondary, monthNames, monthNamesShort) {
|
1425 |
+
minDate = (inst.rangeStart && minDate && selectedDate < minDate ? selectedDate : minDate);
|
1426 |
+
var changeMonth = this._get(inst, 'changeMonth');
|
1427 |
+
var changeYear = this._get(inst, 'changeYear');
|
1428 |
+
var showMonthAfterYear = this._get(inst, 'showMonthAfterYear');
|
1429 |
+
var html = '<div class="ui-datepicker-title">';
|
1430 |
+
var monthHtml = '';
|
1431 |
+
// month selection
|
1432 |
+
if (secondary || !changeMonth)
|
1433 |
+
monthHtml += '<span class="ui-datepicker-month">' + monthNames[drawMonth] + '</span> ';
|
1434 |
+
else {
|
1435 |
+
var inMinYear = (minDate && minDate.getFullYear() == drawYear);
|
1436 |
+
var inMaxYear = (maxDate && maxDate.getFullYear() == drawYear);
|
1437 |
+
monthHtml += '<select class="ui-datepicker-month" ' +
|
1438 |
+
'onchange="DP_jQuery.datepicker._selectMonthYear(\'#' + inst.id + '\', this, \'M\');" ' +
|
1439 |
+
'onclick="DP_jQuery.datepicker._clickMonthYear(\'#' + inst.id + '\');"' +
|
1440 |
+
'>';
|
1441 |
+
for (var month = 0; month < 12; month++) {
|
1442 |
+
if ((!inMinYear || month >= minDate.getMonth()) &&
|
1443 |
+
(!inMaxYear || month <= maxDate.getMonth()))
|
1444 |
+
monthHtml += '<option value="' + month + '"' +
|
1445 |
+
(month == drawMonth ? ' selected="selected"' : '') +
|
1446 |
+
'>' + monthNamesShort[month] + '</option>';
|
1447 |
+
}
|
1448 |
+
monthHtml += '</select>';
|
1449 |
+
}
|
1450 |
+
if (!showMonthAfterYear)
|
1451 |
+
html += monthHtml + ((secondary || changeMonth || changeYear) && (!(changeMonth && changeYear)) ? ' ' : '');
|
1452 |
+
// year selection
|
1453 |
+
if (secondary || !changeYear)
|
1454 |
+
html += '<span class="ui-datepicker-year">' + drawYear + '</span>';
|
1455 |
+
else {
|
1456 |
+
// determine range of years to display
|
1457 |
+
var years = this._get(inst, 'yearRange').split(':');
|
1458 |
+
var year = 0;
|
1459 |
+
var endYear = 0;
|
1460 |
+
if (years.length != 2) {
|
1461 |
+
year = drawYear - 10;
|
1462 |
+
endYear = drawYear + 10;
|
1463 |
+
} else if (years[0].charAt(0) == '+' || years[0].charAt(0) == '-') {
|
1464 |
+
year = drawYear + parseInt(years[0], 10);
|
1465 |
+
endYear = drawYear + parseInt(years[1], 10);
|
1466 |
+
} else {
|
1467 |
+
year = parseInt(years[0], 10);
|
1468 |
+
endYear = parseInt(years[1], 10);
|
1469 |
+
}
|
1470 |
+
year = (minDate ? Math.max(year, minDate.getFullYear()) : year);
|
1471 |
+
endYear = (maxDate ? Math.min(endYear, maxDate.getFullYear()) : endYear);
|
1472 |
+
html += '<select class="ui-datepicker-year" ' +
|
1473 |
+
'onchange="DP_jQuery.datepicker._selectMonthYear(\'#' + inst.id + '\', this, \'Y\');" ' +
|
1474 |
+
'onclick="DP_jQuery.datepicker._clickMonthYear(\'#' + inst.id + '\');"' +
|
1475 |
+
'>';
|
1476 |
+
for (; year <= endYear; year++) {
|
1477 |
+
html += '<option value="' + year + '"' +
|
1478 |
+
(year == drawYear ? ' selected="selected"' : '') +
|
1479 |
+
'>' + year + '</option>';
|
1480 |
+
}
|
1481 |
+
html += '</select>';
|
1482 |
+
}
|
1483 |
+
if (showMonthAfterYear)
|
1484 |
+
html += (secondary || changeMonth || changeYear ? ' ' : '') + monthHtml;
|
1485 |
+
html += '</div>'; // Close datepicker_header
|
1486 |
+
return html;
|
1487 |
+
},
|
1488 |
+
|
1489 |
+
/* Adjust one of the date sub-fields. */
|
1490 |
+
_adjustInstDate: function(inst, offset, period) {
|
1491 |
+
var year = inst.drawYear + (period == 'Y' ? offset : 0);
|
1492 |
+
var month = inst.drawMonth + (period == 'M' ? offset : 0);
|
1493 |
+
var day = Math.min(inst.selectedDay, this._getDaysInMonth(year, month)) +
|
1494 |
+
(period == 'D' ? offset : 0);
|
1495 |
+
var date = this._daylightSavingAdjust(new Date(year, month, day));
|
1496 |
+
// ensure it is within the bounds set
|
1497 |
+
var minDate = this._getMinMaxDate(inst, 'min', true);
|
1498 |
+
var maxDate = this._getMinMaxDate(inst, 'max');
|
1499 |
+
date = (minDate && date < minDate ? minDate : date);
|
1500 |
+
date = (maxDate && date > maxDate ? maxDate : date);
|
1501 |
+
inst.selectedDay = date.getDate();
|
1502 |
+
inst.drawMonth = inst.selectedMonth = date.getMonth();
|
1503 |
+
inst.drawYear = inst.selectedYear = date.getFullYear();
|
1504 |
+
if (period == 'M' || period == 'Y')
|
1505 |
+
this._notifyChange(inst);
|
1506 |
+
},
|
1507 |
+
|
1508 |
+
/* Notify change of month/year. */
|
1509 |
+
_notifyChange: function(inst) {
|
1510 |
+
var onChange = this._get(inst, 'onChangeMonthYear');
|
1511 |
+
if (onChange)
|
1512 |
+
onChange.apply((inst.input ? inst.input[0] : null),
|
1513 |
+
[inst.selectedYear, inst.selectedMonth + 1, inst]);
|
1514 |
+
},
|
1515 |
+
|
1516 |
+
/* Determine the number of months to show. */
|
1517 |
+
_getNumberOfMonths: function(inst) {
|
1518 |
+
var numMonths = this._get(inst, 'numberOfMonths');
|
1519 |
+
return (numMonths == null ? [1, 1] : (typeof numMonths == 'number' ? [1, numMonths] : numMonths));
|
1520 |
+
},
|
1521 |
+
|
1522 |
+
/* Determine the current maximum date - ensure no time components are set - may be overridden for a range. */
|
1523 |
+
_getMinMaxDate: function(inst, minMax, checkRange) {
|
1524 |
+
var date = this._determineDate(this._get(inst, minMax + 'Date'), null);
|
1525 |
+
return (!checkRange || !inst.rangeStart ? date :
|
1526 |
+
(!date || inst.rangeStart > date ? inst.rangeStart : date));
|
1527 |
+
},
|
1528 |
+
|
1529 |
+
/* Find the number of days in a given month. */
|
1530 |
+
_getDaysInMonth: function(year, month) {
|
1531 |
+
return 32 - new Date(year, month, 32).getDate();
|
1532 |
+
},
|
1533 |
+
|
1534 |
+
/* Find the day of the week of the first of a month. */
|
1535 |
+
_getFirstDayOfMonth: function(year, month) {
|
1536 |
+
return new Date(year, month, 1).getDay();
|
1537 |
+
},
|
1538 |
+
|
1539 |
+
/* Determines if we should allow a "next/prev" month display change. */
|
1540 |
+
_canAdjustMonth: function(inst, offset, curYear, curMonth) {
|
1541 |
+
var numMonths = this._getNumberOfMonths(inst);
|
1542 |
+
var date = this._daylightSavingAdjust(new Date(
|
1543 |
+
curYear, curMonth + (offset < 0 ? offset : numMonths[1]), 1));
|
1544 |
+
if (offset < 0)
|
1545 |
+
date.setDate(this._getDaysInMonth(date.getFullYear(), date.getMonth()));
|
1546 |
+
return this._isInRange(inst, date);
|
1547 |
+
},
|
1548 |
+
|
1549 |
+
/* Is the given date in the accepted range? */
|
1550 |
+
_isInRange: function(inst, date) {
|
1551 |
+
// during range selection, use minimum of selected date and range start
|
1552 |
+
var newMinDate = (!inst.rangeStart ? null : this._daylightSavingAdjust(
|
1553 |
+
new Date(inst.selectedYear, inst.selectedMonth, inst.selectedDay)));
|
1554 |
+
newMinDate = (newMinDate && inst.rangeStart < newMinDate ? inst.rangeStart : newMinDate);
|
1555 |
+
var minDate = newMinDate || this._getMinMaxDate(inst, 'min');
|
1556 |
+
var maxDate = this._getMinMaxDate(inst, 'max');
|
1557 |
+
return ((!minDate || date >= minDate) && (!maxDate || date <= maxDate));
|
1558 |
+
},
|
1559 |
+
|
1560 |
+
/* Provide the configuration settings for formatting/parsing. */
|
1561 |
+
_getFormatConfig: function(inst) {
|
1562 |
+
var shortYearCutoff = this._get(inst, 'shortYearCutoff');
|
1563 |
+
shortYearCutoff = (typeof shortYearCutoff != 'string' ? shortYearCutoff :
|
1564 |
+
new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10));
|
1565 |
+
return {shortYearCutoff: shortYearCutoff,
|
1566 |
+
dayNamesShort: this._get(inst, 'dayNamesShort'), dayNames: this._get(inst, 'dayNames'),
|
1567 |
+
monthNamesShort: this._get(inst, 'monthNamesShort'), monthNames: this._get(inst, 'monthNames')};
|
1568 |
+
},
|
1569 |
+
|
1570 |
+
/* Format the given date for display. */
|
1571 |
+
_formatDate: function(inst, day, month, year) {
|
1572 |
+
if (!day) {
|
1573 |
+
inst.currentDay = inst.selectedDay;
|
1574 |
+
inst.currentMonth = inst.selectedMonth;
|
1575 |
+
inst.currentYear = inst.selectedYear;
|
1576 |
+
}
|
1577 |
+
var date = (day ? (typeof day == 'object' ? day :
|
1578 |
+
this._daylightSavingAdjust(new Date(year, month, day))) :
|
1579 |
+
this._daylightSavingAdjust(new Date(inst.currentYear, inst.currentMonth, inst.currentDay)));
|
1580 |
+
return this.formatDate(this._get(inst, 'dateFormat'), date, this._getFormatConfig(inst));
|
1581 |
+
}
|
1582 |
+
});
|
1583 |
+
|
1584 |
+
/* jQuery extend now ignores nulls! */
|
1585 |
+
function extendRemove(target, props) {
|
1586 |
+
$.extend(target, props);
|
1587 |
+
for (var name in props)
|
1588 |
+
if (props[name] == null || props[name] == undefined)
|
1589 |
+
target[name] = props[name];
|
1590 |
+
return target;
|
1591 |
+
};
|
1592 |
+
|
1593 |
+
/* Determine whether an object is an array. */
|
1594 |
+
function isArray(a) {
|
1595 |
+
return (a && (($.browser.safari && typeof a == 'object' && a.length) ||
|
1596 |
+
(a.constructor && a.constructor.toString().match(/\Array\(\)/))));
|
1597 |
+
};
|
1598 |
+
|
1599 |
+
/* Invoke the datepicker functionality.
|
1600 |
+
@param options string - a command, optionally followed by additional parameters or
|
1601 |
+
Object - settings for attaching new datepicker functionality
|
1602 |
+
@return jQuery object */
|
1603 |
+
$.fn.datepicker = function(options){
|
1604 |
+
|
1605 |
+
/* Initialise the date picker. */
|
1606 |
+
if (!$.datepicker.initialized) {
|
1607 |
+
$(document).mousedown($.datepicker._checkExternalClick).
|
1608 |
+
find('body').append($.datepicker.dpDiv);
|
1609 |
+
$.datepicker.initialized = true;
|
1610 |
+
}
|
1611 |
+
|
1612 |
+
var otherArgs = Array.prototype.slice.call(arguments, 1);
|
1613 |
+
if (typeof options == 'string' && (options == 'isDisabled' || options == 'getDate'))
|
1614 |
+
return $.datepicker['_' + options + 'Datepicker'].
|
1615 |
+
apply($.datepicker, [this[0]].concat(otherArgs));
|
1616 |
+
if (options == 'option' && arguments.length == 2 && typeof arguments[1] == 'string')
|
1617 |
+
return $.datepicker['_' + options + 'Datepicker'].
|
1618 |
+
apply($.datepicker, [this[0]].concat(otherArgs));
|
1619 |
+
return this.each(function() {
|
1620 |
+
typeof options == 'string' ?
|
1621 |
+
$.datepicker['_' + options + 'Datepicker'].
|
1622 |
+
apply($.datepicker, [this].concat(otherArgs)) :
|
1623 |
+
$.datepicker._attachDatepicker(this, options);
|
1624 |
+
});
|
1625 |
+
};
|
1626 |
+
|
1627 |
+
$.datepicker = new Datepicker(); // singleton instance
|
1628 |
+
$.datepicker.initialized = false;
|
1629 |
+
$.datepicker.uuid = new Date().getTime();
|
1630 |
+
$.datepicker.version = "1.7.3";
|
1631 |
+
|
1632 |
+
// Workaround for #4055
|
1633 |
+
// Add another global to avoid noConflict issues with inline event handlers
|
1634 |
+
window.DP_jQuery = $;
|
1635 |
+
|
1636 |
+
})(jQuery);
|
static/js/pmxi.js
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/**
|
2 |
+
* plugin javascript
|
3 |
+
*/
|
4 |
+
(function($){$(function () {
|
5 |
+
|
6 |
+
$('#dismiss').click(function(){
|
7 |
+
|
8 |
+
$(this).parents('div.error:first').slideUp();
|
9 |
+
$.post('admin.php?page=pmxi-admin-settings&action=dismiss', {dismiss: true}, function (data) {
|
10 |
+
|
11 |
+
}, 'html');
|
12 |
+
})
|
13 |
+
|
14 |
+
});})(jQuery);
|
views/admin/help/index.php
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
1 |
+
<h2><?php _e('WP All Import Help', 'pmxi_plugin') ?></h2>
|
2 |
+
|
views/admin/home/index.php
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<div class="wrap">
|
2 |
+
<?php
|
3 |
+
$homeurl = "http://www.wpallimport.com/adminpanel/index.php?v=".urlencode(PMXI_Plugin::getInstance()->getVersion());
|
4 |
+
$contents = @file_get_contents($homeurl);
|
5 |
+
if ( ! $contents) {
|
6 |
+
?>
|
7 |
+
<iframe src='<?php echo $homeurl; ?>' width='600'></iframe><br />
|
8 |
+
<?php
|
9 |
+
} else {
|
10 |
+
echo $contents;
|
11 |
+
}
|
12 |
+
?>
|
13 |
+
</div>
|
14 |
+
|
15 |
+
|
16 |
+
|
views/admin/import/element.php
ADDED
@@ -0,0 +1 @@
|
|
|
1 |
+
<?php //stub ?>
|
views/admin/import/element_after.php
ADDED
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<form class="choose-elements no-enter-submit" method="post">
|
2 |
+
<table class="layout">
|
3 |
+
<tr>
|
4 |
+
<td class="left">
|
5 |
+
<h2><?php _e('Import XML/CSV - Step 2: Element Selector', 'pmxi_plugin') ?><br/><span class="taglines"><?php _e('select which elements you wish to import', 'pmxi_plugin') ?></span></h2>
|
6 |
+
<hr />
|
7 |
+
|
8 |
+
|
9 |
+
<p>
|
10 |
+
<?php _e('<b>Choose elements for import by double clicking on corresponding opening tag. Recurring sibling elements, if present, are selected automatically.</b>', 'pmxi_plugin') ?>
|
11 |
+
</p>
|
12 |
+
|
13 |
+
<div class="ajax-console">
|
14 |
+
<?php if ($this->errors->get_error_codes()): ?>
|
15 |
+
<?php $this->error() ?>
|
16 |
+
<?php endif ?>
|
17 |
+
</div>
|
18 |
+
|
19 |
+
<div class="xml">
|
20 |
+
<?php $this->render_xml_element($dom->documentElement) ?>
|
21 |
+
</div>
|
22 |
+
</td>
|
23 |
+
<td class="right">
|
24 |
+
<p>
|
25 |
+
<?php _e('Advaned users: use <a href="http://www.w3schools.com/xpath/default.asp" target="_blank">XPath syntax.</a> If you are unable to figure out what XPath you need to match certain elements you are trying to import, contact <a href="http://www.wpallimport.com/support" target="_blank">support</a> and include your XML file. We will get back to you with the XPath.', 'pmxi_plugin') ?>
|
26 |
+
</p>
|
27 |
+
<p>
|
28 |
+
<?php _e('Tip: change [1], [2], [3], etc. to [*] to match more elements.', 'pmxi_plugin') ?>
|
29 |
+
</p>
|
30 |
+
<div><input type="text" name="xpath" value="<?php echo esc_attr($post['xpath']) ?>" /></div>
|
31 |
+
<p class="submit-buttons">
|
32 |
+
<a href="<?php echo $this->baseUrl ?>" class="button back">Back</a>
|
33 |
+
|
34 |
+
<input type="hidden" name="is_submitted" value="1" />
|
35 |
+
<?php wp_nonce_field('choose-elements', '_wpnonce_choose-elements') ?>
|
36 |
+
<input type="submit" class="button-primary" value="<?php _e('Continue', 'pmxi_plugin') ?> >>" />
|
37 |
+
</p>
|
38 |
+
</td>
|
39 |
+
</tr>
|
40 |
+
</table>
|
41 |
+
</form>
|
views/admin/import/error.php
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
1 |
+
<?php foreach ($errors as $msg): ?>
|
2 |
+
<div class="error inline"><p><?php echo $msg ?></p></div>
|
3 |
+
<?php endforeach ?>
|
views/admin/import/evaluate.php
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<div class="updated">
|
2 |
+
<p><?php printf(__('Specified XPath matches <strong>%s</strong> %s.', 'pmxi_plugin'), $node_list_count, _n('element', 'elements', $node_list_count, 'pmxi_plugin')) ?></p>
|
3 |
+
<?php if (PMXI_Plugin::getInstance()->getOption('highlight_limit') and $node_list->length > PMXI_Plugin::getInstance()->getOption('highlight_limit')): ?>
|
4 |
+
<p><?php _e('<strong>Note</strong>: Highlighting is turned off since can be very slow on large sets of elements.', 'pmxi_plugin') ?></p>
|
5 |
+
<?php endif ?>
|
6 |
+
</div>
|
7 |
+
<script type="text/javascript">
|
8 |
+
(function($){
|
9 |
+
var paths = <?php echo json_encode($paths) ?>;
|
10 |
+
var $xml = $('.xml');
|
11 |
+
for (var i = 0; i < paths.length; i++) {
|
12 |
+
$xml.find('.xml-element[title="' + paths[i] + '"]').addClass('selected').parents('.xml-element').find('> .xml-content.collapsed').removeClass('collapsed').parent().find('> .xml-expander').html('-');
|
13 |
+
}
|
14 |
+
})(jQuery);
|
15 |
+
</script>
|
views/admin/import/index.php
ADDED
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<table class="layout">
|
2 |
+
<tr>
|
3 |
+
<td class="left">
|
4 |
+
<h2><?php _e('Import XML/CSV - Step 1: Choose Your File', 'pmxi_plugin') ?><br/><span class="taglines"><?php _e('choose which CSV or XML file you want to import', 'pmxi_plugin') ?></span></h2>
|
5 |
+
<hr />
|
6 |
+
|
7 |
+
<?php if ($this->errors->get_error_codes()): ?>
|
8 |
+
<?php $this->error() ?>
|
9 |
+
<?php endif ?>
|
10 |
+
|
11 |
+
<form method="post" class="choose-file no-enter-submit" enctype="multipart/form-data" autocomplete="off">
|
12 |
+
<input type="hidden" name="is_submitted" value="1" />
|
13 |
+
<?php wp_nonce_field('upload-xml', '_wpnonce_upload-xml') ?>
|
14 |
+
|
15 |
+
<div class="file-type-container">
|
16 |
+
<h3>
|
17 |
+
<input type="radio" id="type_upload" name="type" value="upload" <?php echo 'upload' == $post['type'] ? 'checked="checked"' : '' ?> />
|
18 |
+
<label for="type_upload"><?php _e('Upload XML/CSV File From Your Computer', 'pmxi_plugin') ?></label>
|
19 |
+
</h3>
|
20 |
+
<div class="file-type-options">
|
21 |
+
<input type="file" class="regular-text" name="upload" />
|
22 |
+
<div class="note"><strong><?php _e('Warning', 'pmxi_plugin') ?></strong>: <?php printf(__('Your host allows a maximum filesize of <strong>%sB</strong> for uploads', 'pmxi_plugin'), ini_get('upload_max_filesize')) ?></div>
|
23 |
+
</div>
|
24 |
+
</div>
|
25 |
+
<div class="file-type-container">
|
26 |
+
<h3>
|
27 |
+
<input type="radio" id="type_url" name="type" value="url" <?php echo 'url' == $post['type'] ? 'checked="checked"' : '' ?> />
|
28 |
+
<label for="type_url"><?php _e('Get XML/CSV File From URL', 'pmxi_plugin') ?></label>
|
29 |
+
</h3>
|
30 |
+
<div class="file-type-options">
|
31 |
+
<input type="text" class="regular-text" name="url" value="<?php echo esc_attr($post['url']) ?>" />
|
32 |
+
</div>
|
33 |
+
</div>
|
34 |
+
<div class="file-type-container">
|
35 |
+
<h3>
|
36 |
+
<input type="radio" id="type_ftp" name="type" value="ftp" <?php echo 'ftp' == $post['type'] ? 'checked="checked"' : '' ?> />
|
37 |
+
<label for="type_ftp"><?php _e('Get XML/CSV File Via FTP', 'pmxi_plugin') ?>*</label>
|
38 |
+
</h3>
|
39 |
+
<div class="file-type-options">
|
40 |
+
<input type="text" class="regular-text" name="ftp[url]" value="<?php echo esc_attr($post['ftp']['url']) ?>" /><br />
|
41 |
+
<input type="text" name="ftp[user]" title="username" /><strong>:</strong><input type="password" name="ftp[pass]" title="passowrd" />
|
42 |
+
</div>
|
43 |
+
</div>
|
44 |
+
<div class="file-type-container">
|
45 |
+
<h3>
|
46 |
+
<input type="radio" id="type_file" name="type" value="file" <?php echo 'file' == $post['type'] ? 'checked="checked"' : '' ?> />
|
47 |
+
<label for="type_file"><?php _e('Get XML/CSV File On This Server', 'pmxi_plugin') ?>*</label>
|
48 |
+
</h3>
|
49 |
+
<div class="file-type-options">
|
50 |
+
<input type="text" id="__FILE_SOURCE" class="regular-text autocomplete" name="file" value="<?php echo esc_attr($post['file']) ?>" />
|
51 |
+
<?php
|
52 |
+
$local_files = array_merge(
|
53 |
+
PMXI_Helper::safe_glob(PMXI_Plugin::ROOT_DIR . '/upload/*.xml', PMXI_Helper::GLOB_RECURSE),
|
54 |
+
PMXI_Helper::safe_glob(PMXI_Plugin::ROOT_DIR . '/upload/*.gz', PMXI_Helper::GLOB_RECURSE),
|
55 |
+
PMXI_Helper::safe_glob(PMXI_Plugin::ROOT_DIR . '/upload/*.csv', PMXI_Helper::GLOB_RECURSE)
|
56 |
+
);
|
57 |
+
sort($local_files);
|
58 |
+
?>
|
59 |
+
<script type="text/javascript">
|
60 |
+
__FILE_SOURCE = <?php echo json_encode($local_files) ?>;
|
61 |
+
</script>
|
62 |
+
<div class="note"><?php printf(__('Upload files to <strong>%s</strong> and they will appear in this list', 'pmxi_plugin'), PMXI_Plugin::ROOT_DIR . '/upload/') ?></div>
|
63 |
+
</div>
|
64 |
+
</div>
|
65 |
+
<?php if ($history->count()): ?>
|
66 |
+
<div class="file-type-container">
|
67 |
+
<h3>
|
68 |
+
<input type="radio" id="type_reimport" name="type" value="reimport" <?php echo 'reimport' == $post['type'] ? 'checked="checked"' : '' ?> />
|
69 |
+
<label for="type_reimport"><?php _e('Get Previously Imported XML or CSV', 'pmxi_plugin') ?></label>
|
70 |
+
</h3>
|
71 |
+
<div class="file-type-options">
|
72 |
+
<input type="text" id="__REIMPORT_SOURCE" class="regular-text autocomplete" name="reimport" value="<?php echo esc_attr($post['reimport']) ?>" readonly="readonly" />
|
73 |
+
<?php
|
74 |
+
$reimports = array();
|
75 |
+
foreach ($history as $file) $reimports[] = '#' . $file['id'] . ': ' . $file['name'] . __(' on ', 'pmxi_plugin') . mysql2date('Y/m/d', $file['registered_on']) . ' - ' . preg_replace('%^(\w+://[^:]+:)[^@]+@%', '$1*****@', $file['path']);
|
76 |
+
?>
|
77 |
+
<script type="text/javascript">
|
78 |
+
__REIMPORT_SOURCE = <?php echo json_encode($reimports) ?>;
|
79 |
+
</script>
|
80 |
+
</div>
|
81 |
+
</div>
|
82 |
+
<?php endif ?>
|
83 |
+
|
84 |
+
<div class="file-type-container">
|
85 |
+
<?php if ($imports->count()): ?>
|
86 |
+
<div class="input">
|
87 |
+
<input type="checkbox" id="is_update_previous" name="is_update_previous" class="switcher" disabled="disabled"/>
|
88 |
+
<label for="is_update_previous"><?php _e('Update Previous Import', 'pmxi_plugin') ?></label>
|
89 |
+
</div>
|
90 |
+
<a href="http://www.wpallimport.com/upgrade-to-pro?from=upi" target="_blank">Upgrade to pro to update previous imports.</a>
|
91 |
+
<?php endif ?>
|
92 |
+
<p class="submit-buttons">
|
93 |
+
<input type="hidden" name="is_submitted" value="1" />
|
94 |
+
<?php wp_nonce_field('choose-file', '_wpnonce_choose-file') ?>
|
95 |
+
<input type="submit" class="button-primary" value="<?php _e('Continue', 'pmxi_plugin') ?> >>" />
|
96 |
+
</p>
|
97 |
+
</div>
|
98 |
+
<br />
|
99 |
+
<table><tr><td class="note"><b>*</b> </td><td class="note"><?php _e('These options support shell wildcard patterns<a href="#help" class="help" title="A shell wildcard pattern is a string used by *nix systems for referencing several files at once. The most common case is using asterisk symbol in the place of any set of characters, e.g. `*.xml` would correspond to any file with `xml` extension.">?</a> which enables linking several XML files to the same import. The option is useful when the exact source path is not known upfront or is going to change, e.g. some content providers submit XML files each time with a new name.<p> CSV files are converted to XML using YQL. YQL is a service hosted by Yahoo. For this to work, WP All Import must be installed on a public-facing server (not localhost) and the /wp-content/uploads/wpallimport/ folder must be accessible to Yahoo over HTTP.</p>', 'pmxi_plugin') ?></td></tr></table>
|
100 |
+
</form>
|
101 |
+
</td>
|
102 |
+
<td class="right">
|
103 |
+
|
104 |
+
</td>
|
105 |
+
</tr>
|
106 |
+
</table>
|
views/admin/import/options.php
ADDED
@@ -0,0 +1,309 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<form class="options <?php echo ! $this->isWizard ? 'edit' : '' ?>" method="post">
|
2 |
+
<table class="layout">
|
3 |
+
<tr>
|
4 |
+
<td class="left">
|
5 |
+
<h2>
|
6 |
+
<?php if ($this->isWizard): ?>
|
7 |
+
<?php if ($is_loaded_template && !$load_options): ?>
|
8 |
+
<span class="load-options">
|
9 |
+
Load Options... <input type="checkbox" name="load_options" /><a class="help" href="#help" original-title="Load options from selected template.">?</a>
|
10 |
+
</span>
|
11 |
+
<?php elseif ($is_loaded_template): ?>
|
12 |
+
<span class="load-options">
|
13 |
+
Reset Options... <input type="checkbox" name="reset_options" /><a class="help" href="#help" original-title="Reset options.">?</a>
|
14 |
+
</span>
|
15 |
+
<?php endif; ?>
|
16 |
+
<?php _e('Import XML/CSV - Step 4: Post Options', 'pmxi_plugin') ?><br/><span class="taglines"><?php _e('options for the created posts', 'pmxi_plugin') ?></span>
|
17 |
+
<?php else: ?>
|
18 |
+
<?php _e('Edit Import Options', 'pmxi_plugin') ?>
|
19 |
+
<?php endif ?>
|
20 |
+
</h2>
|
21 |
+
<hr />
|
22 |
+
|
23 |
+
<?php if ($this->errors->get_error_codes()): ?>
|
24 |
+
<?php $this->error() ?>
|
25 |
+
<?php endif ?>
|
26 |
+
|
27 |
+
<div class="post-type-container">
|
28 |
+
<h3>
|
29 |
+
<input type="radio" id="type_post" name="type" value="post" <?php echo 'post' == $post['type'] ? 'checked="checked"' : '' ?> />
|
30 |
+
<label for="type_post"><?php _e('Create Posts', 'pmxi_plugin') ?></label>
|
31 |
+
</h3>
|
32 |
+
<div class="post-type-options">
|
33 |
+
<table class="form-table">
|
34 |
+
<tr>
|
35 |
+
<th><?php _e('Categories', 'pmxi_plugin') ?> <a href="#help" class="help" title="<?php _e('Enter Category IDs or Names separated by commas. Enclose a name with double quotes if it contains comma.', 'pmxi_plugin') ?>">?</a></th>
|
36 |
+
<td><input type="text" class="widefat" name="categories" value="<?php echo esc_attr($post['categories']) ?>" /></td>
|
37 |
+
<td class="delim">
|
38 |
+
<input type="text" class="small" name="categories_delim" maxlength="1" value="<?php echo esc_attr($post['categories_delim']) ?>" />
|
39 |
+
<a href="#help" class="help" title="<?php _e('Delimiter used for category list', 'pmxi_plugin') ?>">?</a>
|
40 |
+
</td>
|
41 |
+
</tr>
|
42 |
+
<tr>
|
43 |
+
<th><?php _e('Tags', 'pmxi_plugin') ?> <a href="#help" class="help" title="<?php _e('Enter tags separated by commas.', 'pmxi_plugin') ?>">?</a></th>
|
44 |
+
<td><input type="text" name="tags" class="widefat" value="<?php echo esc_attr($post['tags']) ?>" /></td>
|
45 |
+
<td class="delim">
|
46 |
+
<input type="text" class="small" name="tags_delim" maxlength="1" value="<?php echo esc_attr($post['tags_delim']) ?>" />
|
47 |
+
<a href="#help" class="help" title="<?php _e('Delimiter used for tag list', 'pmxi_plugin') ?>">?</a>
|
48 |
+
</td>
|
49 |
+
</tr>
|
50 |
+
<tr>
|
51 |
+
<td colspan="3"> <a href="http://www.wpallimport.com/upgrade-to-pro?from=cpt" target="_blank">To import to Custom Post Types, upgrade to pro.</a> </td>
|
52 |
+
</tr>
|
53 |
+
<?php $post_taxonomies = array_diff_key(get_taxonomies_by_object_type(array('post'), 'object'), array_flip(array('category', 'post_tag', 'post_format'))) ?>
|
54 |
+
<?php foreach ($post_taxonomies as $ctx): ?>
|
55 |
+
<tr class="post_taxonomy" data-type="<?php echo implode(' ', $ctx->object_type) ?>">
|
56 |
+
<th><nobr><?php echo $ctx->labels->name ?> <a href="#help" class="help" title="<?php _e('Enter taxonomies separated by commas.', 'pmxi_plugin') ?>">?</a></nobr></th>
|
57 |
+
<td><input type="text" name="post_taxonomies[<?php echo $ctx->name ?>]" class="widefat" value="<?php echo esc_attr(isset($post['post_taxonomies'][$ctx->name]) ? $post['post_taxonomies'][$ctx->name] : '') ?>" /></td>
|
58 |
+
<td class="delim">
|
59 |
+
<input type="text" class="small" name="post_taxonomies_delim[<?php echo $ctx->name ?>]" maxlength="1" value="<?php echo esc_attr(isset($post['post_taxonomies_delim'][$ctx->name]) ? $post['post_taxonomies_delim'][$ctx->name] : ',') ?>" />
|
60 |
+
<a href="#help" class="help" title="<?php _e('Delimiter used for taxonomy list', 'pmxi_plugin') ?>">?</a>
|
61 |
+
</td>
|
62 |
+
</tr>
|
63 |
+
<?php endforeach ?>
|
64 |
+
</table>
|
65 |
+
</div>
|
66 |
+
</div>
|
67 |
+
<div class="post-type-container">
|
68 |
+
<h3>
|
69 |
+
<input type="radio" id="type_page" name="type" value="page" <?php echo 'page' == $post['type'] ? 'checked="checked"' : '' ?> />
|
70 |
+
<label for="type_page"><?php _e('Create Pages', 'pmxi_plugin') ?></label>
|
71 |
+
</h3>
|
72 |
+
<div class="post-type-options">
|
73 |
+
<table class="form-table">
|
74 |
+
<tr>
|
75 |
+
<th><?php _e('Page Template', 'pmxi_plugin') ?></th>
|
76 |
+
<td>
|
77 |
+
<select name="page_template" id="page_template">
|
78 |
+
<option value='default'><?php _e('Default', 'pmxi_plugin') ?></option>
|
79 |
+
<?php page_template_dropdown($template); ?>
|
80 |
+
</select>
|
81 |
+
</td>
|
82 |
+
</tr>
|
83 |
+
<tr>
|
84 |
+
<th><?php _e('Parent Page', 'pmxi_plugin') ?></th>
|
85 |
+
<td>
|
86 |
+
<?php wp_dropdown_pages(array('post_type' => 'page', 'selected' => $post['parent'], 'name' => 'parent', 'show_option_none' => __('(no parent)', 'pmxi_plugin'), 'sort_column'=> 'menu_order, post_title',)) ?>
|
87 |
+
</td>
|
88 |
+
</tr>
|
89 |
+
<tr>
|
90 |
+
<th><?php _e('Order', 'pmxi_plugin') ?></th>
|
91 |
+
<td><input type="text" class="small-text" name="order" value="<?php echo esc_attr($post['order']) ?>" /></td>
|
92 |
+
</tr>
|
93 |
+
<?php $page_taxonomies = get_taxonomies_by_object_type('page', 'object') ?>
|
94 |
+
<?php foreach ($page_taxonomies as $ctx): ?>
|
95 |
+
<tr class="page_taxonomy" data-type="<?php echo implode(' ', $ctx->object_type) ?>">
|
96 |
+
<th><nobr><?php echo $ctx->labels->name ?> <a href="#help" class="help" title="<?php _e('Enter taxonomies separated by commas.', 'pmxi_plugin') ?>">?</a></nobr></th>
|
97 |
+
<td><input type="text" name="page_taxonomies[<?php echo $ctx->name ?>]" class="widefat" value="<?php echo esc_attr(isset($post['page_taxonomies'][$ctx->name]) ? $post['page_taxonomies'][$ctx->name] : '') ?>" /></td>
|
98 |
+
<td class="delim">
|
99 |
+
<input type="text" class="small" name="page_taxonomies_delim[<?php echo $ctx->name ?>]" maxlength="1" value="<?php echo esc_attr(isset($post['page_taxonomies_delim'][$ctx->name]) ? $post['page_taxonomies_delim'][$ctx->name] : ',') ?>" />
|
100 |
+
<a href="#help" class="help" title="<?php _e('Delimiter used for taxonomy list', 'pmxi_plugin') ?>">?</a>
|
101 |
+
</td>
|
102 |
+
</tr>
|
103 |
+
<?php endforeach ?>
|
104 |
+
</table>
|
105 |
+
</div>
|
106 |
+
</div>
|
107 |
+
|
108 |
+
<h2><?php _e('Generic Options', 'pmxi_plugin') ?></h2>
|
109 |
+
<hr />
|
110 |
+
|
111 |
+
<div class="input">
|
112 |
+
<input type="hidden" name="is_import_specified" value="0" />
|
113 |
+
<input type="checkbox" id="is_import_specified" class="switcher" name="is_import_specified" value="1" <?php echo $post['is_import_specified'] ? 'checked="checked"': '' ?>/>
|
114 |
+
<label for="is_import_specified"><?php _e('Import only specified records', 'pmxi_plugin') ?> <a href="#help" class="help" title="<?php _e('Enter records or record ranges separated by commas, e.g. <b>1,5,7-10</b> would import the first, the fifth, and the seventh to tenth.', 'pmxi_plugin') ?>">?</a></label>
|
115 |
+
<span class="switcher-target-is_import_specified" style="vertical-align:middle">
|
116 |
+
<input type="text" name="import_specified" value="<?php echo esc_attr($post['import_specified']) ?>" />
|
117 |
+
</span>
|
118 |
+
</div>
|
119 |
+
<div>
|
120 |
+
<input type="hidden" name="is_duplicates" value="0" />
|
121 |
+
<input type="checkbox" id="is_duplicates" class="switcher" name="is_duplicates" value="1" disabled="disabled"/>
|
122 |
+
<label for="is_duplicates"><?php _e('Check for duplicates', 'pmxi_plugin') ?> <a href="#help" class="help" title="<?php _e('This option allows you to specify action for articles being imported which have duplicates in WordPress database.<br /><br /><b>Important</b>: This option applies only to pages or posts not associated with current import. To manage overwrite rules for records previously created by import operation currently being updated please see `Reimport / Update Options` section below.', 'pmxi_plugin') ?>">?</a></label>
|
123 |
+
<a href="http://www.wpallimport.com/upgrade-to-pro?from=cfd" target="_blank">Upgrade to pro for automatic duplicate detection.</a>
|
124 |
+
</div>
|
125 |
+
<?php if (in_array($source_type, array('ftp', 'file'))): ?>
|
126 |
+
<div class="input">
|
127 |
+
<input type="hidden" name="is_delete_source" value="0" />
|
128 |
+
<input type="checkbox" id="is_delete_source" name="is_delete_source" value="1" <?php echo $post['is_delete_source'] ? 'checked="checked"': '' ?>/>
|
129 |
+
<label for="is_delete_source"><?php _e('Delete source XML file after importing', 'pmxi_plugin') ?> <a href="#help" class="help" title="<?php _e('This setting takes effect only when script has access rights to perform the action, e.g. file is not deleted when pulled via HTTP or delete permission is not granted to the user that script is executed under.', 'pmxi_plugin') ?>">?</a></label>
|
130 |
+
</div>
|
131 |
+
<?php endif ?>
|
132 |
+
<?php if (class_exists('PMLC_Plugin')): // option is only valid when `WP Wizard Cloak` pluign is enabled ?>
|
133 |
+
<div class="input">
|
134 |
+
<input type="hidden" name="is_cloak" value="0" />
|
135 |
+
<input type="checkbox" id="is_cloak" name="is_cloak" value="1" <?php echo $post['is_cloak'] ? 'checked="checked"': '' ?>/>
|
136 |
+
<label for="is_cloak"><?php _e('Auto-Cloak Links', 'pmxi_plugin') ?> <a href="#help" class="help" title="<?php printf(__('Automatically process all links present in body of created post or page with <b>%s</b> plugin', 'pmxi_plugin'), PMLC_Plugin::getInstance()->getName()) ?>">?</a></label>
|
137 |
+
</div>
|
138 |
+
<?php endif ?>
|
139 |
+
<?php if (in_array($source_type, array('url', 'ftp', 'file'))): ?>
|
140 |
+
<div class="input">
|
141 |
+
<input type="hidden" name="is_scheduled" value="0" />
|
142 |
+
<input type="checkbox" id="is_scheduled" class="switcher" name="is_scheduled" value="1" disabled="disabled"/>
|
143 |
+
<label for="is_scheduled"><?php _e('Recurring import', 'pmxi_plugin') ?> <a href="#help" class="help" title="<?php _e('Consider this option if you want this import task to be run automatically on regular basis.', 'pmxi_plugin') ?>">?</a></label>
|
144 |
+
<a href="http://www.wpallimport.com/upgrade-to-pro?from=ri" target="_blank">Upgrade to pro for recurring/scheduled imports.</a>
|
145 |
+
</div>
|
146 |
+
<?php endif ?>
|
147 |
+
<h3><?php _e('Post Status', 'pmxi_plugin') ?></h3>
|
148 |
+
<div>
|
149 |
+
<input type="radio" id="status_publish" name="status" value="publish" <?php echo 'publish' == $post['status'] ? 'checked="checked"' : '' ?> />
|
150 |
+
<label for="status_publish"><?php _e('Published', 'pmxi_plugin') ?></label>
|
151 |
+
|
152 |
+
<input type="radio" id="status_draft" name="status" value="draft" <?php echo 'draft' == $post['status'] ? 'checked="checked"' : '' ?> />
|
153 |
+
<label for="status_draft"><?php _e('Draft', 'pmxi_plugin') ?></label>
|
154 |
+
</div>
|
155 |
+
|
156 |
+
<h3><?php _e('Post Dates', 'pmxi_plugin') ?> <a href="#help" class="help" title="<?php _e('Use any format supported by <b>strtotime</b>', 'pmxi_plugin') ?>">?</a></h3>
|
157 |
+
<div class="input">
|
158 |
+
<input type="radio" id="date_type_specific" class="switcher" name="date_type" value="specific" checked="checked"/>
|
159 |
+
<label for="date_type_specific">
|
160 |
+
<?php _e('As specified', 'pmxi_plugin') ?>
|
161 |
+
</label>
|
162 |
+
<span class="switcher-target-date_type_specific" style="vertical-align:middle">
|
163 |
+
<input type="text" class="datepicker" name="date" value="<?php echo esc_attr($post['date']) ?>" />
|
164 |
+
</span>
|
165 |
+
</div>
|
166 |
+
<div class="input">
|
167 |
+
<input type="radio" id="date_type_random" class="switcher" value="random" disabled="disabled" />
|
168 |
+
<label for="date_type_random">
|
169 |
+
<?php _e('Random dates', 'pmxi_plugin') ?>
|
170 |
+
</label>
|
171 |
+
<span class="" style="vertical-align:middle">
|
172 |
+
<?php _e('between', 'pmxi_plugin') ?>
|
173 |
+
<input type="text" class="datepicker" name="date_start" value="<?php echo esc_attr($post['date_start']) ?>" />
|
174 |
+
<?php _e('and', 'pmxi_plugin') ?>
|
175 |
+
<input type="text" class="datepicker" name="date_end" value="<?php echo esc_attr($post['date_end']) ?>" />
|
176 |
+
</span>
|
177 |
+
<a href="http://www.wpallimport.com/upgrade-to-pro?from=rd" target="_blank">To create posts with random dates, upgrade to pro.</a>
|
178 |
+
</div>
|
179 |
+
|
180 |
+
<h3><?php _e('Custom Fields', 'pmxi_plugin') ?></h3>
|
181 |
+
<table class="form-table custom-params">
|
182 |
+
<thead>
|
183 |
+
<tr>
|
184 |
+
<td><?php _e('Name', 'pmxi_plugin') ?></td>
|
185 |
+
<td><?php _e('Value', 'pmxi_plugin') ?></td>
|
186 |
+
<td></td>
|
187 |
+
</tr>
|
188 |
+
</thead>
|
189 |
+
<tbody>
|
190 |
+
<tr class="form-field">
|
191 |
+
<td><input type="text" name="custom_name[]" value="" disabled="disabled"/></td>
|
192 |
+
<td><textarea name="custom_value[]" disabled="disabled"></textarea></td>
|
193 |
+
<td class="action remove"></td>
|
194 |
+
</tr>
|
195 |
+
<tr>
|
196 |
+
<td colspan="3"> <a href="http://www.wpallimport.com/upgrade-to-pro?from=cf" target="_blank">To import data to Custom Fields (including fields in Custom Post Types), upgrade to pro.</a> </td>
|
197 |
+
</tr>
|
198 |
+
</tbody>
|
199 |
+
</table>
|
200 |
+
<br />
|
201 |
+
<div>
|
202 |
+
<input type="hidden" name="comment_status" value="closed" />
|
203 |
+
<input type="checkbox" id="comment_status" name="comment_status" value="open" <?php echo 'open' == $post['comment_status'] ? 'checked="checked"' : '' ?> />
|
204 |
+
<label for="comment_status"><?php _e('Allow Comments', 'pmxi_plugin') ?></label>
|
205 |
+
</div>
|
206 |
+
<div>
|
207 |
+
<input type="hidden" name="ping_status" value="closed" />
|
208 |
+
<input type="checkbox" id="ping_status" name="ping_status" value="open" <?php echo 'open' == $post['ping_status'] ? 'checked="checked"' : '' ?> />
|
209 |
+
<label for="ping_status"><?php _e('Allow Trackbacks and Pingbacks', 'pmxi_plugin') ?></label>
|
210 |
+
</div>
|
211 |
+
|
212 |
+
<h3><?php _e('Post Author', 'pmxi_plugin') ?></h3>
|
213 |
+
<div>
|
214 |
+
<?php wp_dropdown_users(array('name' => 'author', 'selected' => $post['author'])); ?>
|
215 |
+
</div>
|
216 |
+
|
217 |
+
<h3><?php _e('Featured Image', 'pmxi_plugin') ?></h3>
|
218 |
+
<div>
|
219 |
+
<input type="text" name="featured_image" disabled="disabled" style="width:300px;" value="<?php echo esc_attr($post['featured_image']) ?>" /> <span>Separate multiple image URLs with commas</span> <br/>
|
220 |
+
<a href="http://www.wpallimport.com/upgade-to-pro?from=fi" target="_blank">To import images to the post image gallery and set the Featured Image, upgrade to pro.</a>
|
221 |
+
</div>
|
222 |
+
|
223 |
+
<h2><?php _e('Reimport / Update Options', 'pmxi_plugin') ?></h2>
|
224 |
+
<hr />
|
225 |
+
|
226 |
+
<h3>
|
227 |
+
<?php _e('Post Unique Key', 'pmxi_plugin') ?>
|
228 |
+
<a href="#help" class="help" title="<?php _e('XPath expression which is used to detect correspondence between previously imported records and new ones. An expression used for the title is suitable in the most cases, but using recurring tag unique attribute, e.g. ID, if present, is good alternative as well.', 'pmxi_plugin') ?>">?</a>
|
229 |
+
</h3>
|
230 |
+
<div class="input">
|
231 |
+
<input type="text" class="smaller-text" name="unique_key" value="<?php echo esc_attr($post['unique_key']) ?>" <?php echo ! ($this->isWizard && $update_previous->isEmpty()) ? 'disabled="disabled"' : '' ?>/>
|
232 |
+
</div>
|
233 |
+
<br />
|
234 |
+
<div>
|
235 |
+
<input type="hidden" name="is_delete_missing" value="0" />
|
236 |
+
<input type="checkbox" id="is_delete_missing" name="is_delete_missing" value="1" <?php echo $post['is_delete_missing'] ? 'checked="checked"': '' ?> />
|
237 |
+
<label for="is_delete_missing"><?php _e('Delete missing records', 'pmxi_plugin') ?></label>
|
238 |
+
<a href="#help" class="help" title="<?php _e('Check this option if you want to delete posts from previous import operation which are not found among newly impoprted set.', 'pmxi_plugin') ?>">?</a>
|
239 |
+
</div>
|
240 |
+
<div>
|
241 |
+
<input type="hidden" name="is_keep_former_posts" value="0" />
|
242 |
+
<input type="checkbox" id="is_keep_former_posts" name="is_keep_former_posts" value="1" <?php echo $post['is_keep_former_posts'] ? 'checked="checked"': '' ?> class="switcher switcher-reversed" />
|
243 |
+
<label for="is_keep_former_posts"><?php _e('Do not update already existing records', 'pmxi_plugin') ?></label>
|
244 |
+
<a href="#help" class="help" title="<?php _e('Check this option if you do not want to update already esisting records. The option is useful if you plan to manually edit imported records and do not want them to be overwritten.<br /><br /><b>Important</b>: The option applies to the posts or pages associated with the import being updated. To handle potential conflicts with post or pages which are not associated with this import please use `Check for duplicates` option in `General Options` secion above.', 'pmxi_plugin') ?>">?</a>
|
245 |
+
</div>
|
246 |
+
<div class="switcher-target-is_keep_former_posts">
|
247 |
+
<div>
|
248 |
+
<input type="hidden" name="is_keep_status" value="0" />
|
249 |
+
<input type="checkbox" id="is_keep_status" name="is_keep_status" value="1" <?php echo $post['is_keep_status'] ? 'checked="checked"': '' ?> />
|
250 |
+
<label for="is_keep_status"><?php _e('Keep status', 'pmxi_plugin') ?></label>
|
251 |
+
<a href="#help" class="help" title="<?php _e('Check this option if you do not want previously imported posts to change their publish status or being restored from Trash.', 'pmxi_plugin') ?>">?</a>
|
252 |
+
</div>
|
253 |
+
<div>
|
254 |
+
<input type="hidden" name="is_keep_categories" value="0" />
|
255 |
+
<input type="checkbox" id="is_keep_categories" name="is_keep_categories" value="1" <?php echo $post['is_keep_categories'] ? 'checked="checked"': '' ?> />
|
256 |
+
<label for="is_keep_categories"><?php _e('Keep categories, tags and taxonomies', 'pmxi_plugin') ?></label>
|
257 |
+
<a href="#help" class="help" title="<?php _e('Check this option if you do not want previously imported posts to change their category, tag and custom taxonomies associations upon reimport.', 'pmxi_plugin') ?>">?</a>
|
258 |
+
</div>
|
259 |
+
</div>
|
260 |
+
<div>
|
261 |
+
<input type="hidden" name="is_keep_attachments" value="0" />
|
262 |
+
<input type="checkbox" id="is_keep_attachments" name="is_keep_attachments" value="1" <?php echo $post['is_keep_attachments'] ? 'checked="checked"': '' ?> />
|
263 |
+
<label for="is_keep_attachments"><?php _e('Keep attachments when records removed', 'pmxi_plugin') ?></label>
|
264 |
+
<a href="#help" class="help" title="<?php _e('Check this option if you want attachments like featured image to be kept in media library after parent post or page is removed or replaced during reimport operation.', 'pmxi_plugin') ?>">?</a>
|
265 |
+
</div>
|
266 |
+
|
267 |
+
<br />
|
268 |
+
<p>
|
269 |
+
<?php wp_nonce_field('options', '_wpnonce_options') ?>
|
270 |
+
<input type="hidden" name="is_submitted" value="1" />
|
271 |
+
|
272 |
+
<?php if ($this->isWizard): ?>
|
273 |
+
<a href="<?php echo add_query_arg('action', 'template', $this->baseUrl) ?>" class="button back"><?php _e('Back', 'pmxi_plugin') ?></a>
|
274 |
+
|
275 |
+
<input type="submit" class="button-primary ajax-import" value="<?php _e('Save & Create Posts', 'pmxi_plugin') ?>" />
|
276 |
+
|
277 |
+
<?php if (in_array($source_type, array('url', 'ftp', 'file'))): ?>
|
278 |
+
or
|
279 |
+
<input type="submit" name="save_only" class="button" value="<?php _e('Save Only', 'pmxi_plugin') ?>" />
|
280 |
+
<?php endif ?>
|
281 |
+
|
282 |
+
<?php else: ?>
|
283 |
+
<input type="submit" class="button-primary" value="<?php _e('Edit', 'pmxi_plugin') ?>" />
|
284 |
+
<?php endif ?>
|
285 |
+
</p>
|
286 |
+
|
287 |
+
</td>
|
288 |
+
<?php if ($this->isWizard or $this->isTemplateEdit): ?>
|
289 |
+
<td class="right">
|
290 |
+
<p><?php _e('Drag & Drop opening tag of an element for inserting corresponding XPath into a form element.', 'pmxi_plugin') ?></p>
|
291 |
+
<?php $this->tag() ?>
|
292 |
+
</td>
|
293 |
+
<?php endif ?>
|
294 |
+
</tr>
|
295 |
+
</table>
|
296 |
+
</form>
|
297 |
+
<div id="process" style="display:none;">
|
298 |
+
<?php include PMXI_Plugin::ROOT_DIR . '/views/admin/import/process.php'; ?>
|
299 |
+
</div>
|
300 |
+
<script type="text/javascript">
|
301 |
+
(function($){$(function(){
|
302 |
+
$('select[name="custom_type"]').change(function () {
|
303 |
+
var type = $(this).val(); if ('' == type) type = 'post';
|
304 |
+
$('tr.post_taxonomy').each(function () {
|
305 |
+
$(this)[$.inArray(type, $(this).data('type').split(' ')) < 0 ? 'hide' : 'fadeIn']();
|
306 |
+
});
|
307 |
+
}).change();
|
308 |
+
});})(jQuery);
|
309 |
+
</script>
|
views/admin/import/preview.php
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<div id="post-preview">
|
2 |
+
<?php if ($this->errors->get_error_codes()): ?>
|
3 |
+
<?php $this->error() ?>
|
4 |
+
<?php endif ?>
|
5 |
+
|
6 |
+
<?php if (isset($title)): ?>
|
7 |
+
<h2 class="title"><?php echo $title ?></h2>
|
8 |
+
<?php endif ?>
|
9 |
+
<?php if (isset($content)): ?>
|
10 |
+
<div class="content"><?php echo apply_filters('the_content', $content) ?></div>
|
11 |
+
<?php endif ?>
|
12 |
+
</div>
|
views/admin/import/process-complete.php
ADDED
File without changes
|
views/admin/import/process.php
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<div class="inner-content">
|
2 |
+
<h2><?php _e('Import XML - <span id="status">Processing...</span>', 'pmxi_plugin') ?></h2>
|
3 |
+
<hr />
|
4 |
+
|
5 |
+
<p><?php _e('Importing may take some time. Please do not close browser or refresh the page untill process is complete.', 'pmxi_plugin') ?></p>
|
6 |
+
</div>
|
views/admin/import/tag.php
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<div class="tag">
|
2 |
+
<input type="hidden" name="tagno" value="<?php echo $tagno ?>" />
|
3 |
+
<div class="title">
|
4 |
+
<?php printf(__('Record #<strong>%s</strong> out of <strong>%s</strong>', 'pmxi_plugin'), $tagno, $elements->length) ?>
|
5 |
+
<div class="navigation">
|
6 |
+
<?php if ($tagno > 1): ?><a href="#prev">⟨⟨</a><?php else: ?><span>⟨⟨</span><?php endif ?>
|
7 |
+
<?php if ($tagno < $elements->length): ?><a href="#next">⟩⟩</a><?php else: ?><span>⟩⟩</span><?php endif ?>
|
8 |
+
</div>
|
9 |
+
</div>
|
10 |
+
<div class="clear"></div>
|
11 |
+
<div class="xml resetable"><?php if (!empty($elements)) { $this->shrink_xml_element($elements->item($tagno - 1), true); $this->render_xml_element($elements->item($tagno - 1), true); } ?></div>
|
12 |
+
</div>
|
views/admin/import/template.php
ADDED
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<form class="template <?php echo ! $this->isWizard ? 'edit' : '' ?>" method="post">
|
2 |
+
<table class="layout">
|
3 |
+
<tr>
|
4 |
+
<td class="left">
|
5 |
+
<h2>
|
6 |
+
<?php $templates = new PMXI_Template_List() ?>
|
7 |
+
<span class="load-template">
|
8 |
+
<select name="load_template">
|
9 |
+
<option value=""><?php _e('Load Template...', 'pmxi_plugin') ?></option>
|
10 |
+
<?php foreach ($templates->getBy()->convertRecords() as $t): ?>
|
11 |
+
<option value="<?php echo $t->id ?>"><?php echo $t->name ?></option>
|
12 |
+
<?php endforeach ?>
|
13 |
+
</select><a href="#help" class="help" title="<?php _e('Select a <b>Template</b> from the dropdown and it will be preloaded', 'pmxi_plugin') ?>">?</a>
|
14 |
+
</span>
|
15 |
+
<?php if ($this->isWizard): ?>
|
16 |
+
<?php _e('Import XML/CSV - Step 3: Template Designer', 'pmxi_plugin') ?><br/><span class="taglines"><?php _e('arrange your data and design your posts', 'pmxi_plugin') ?></span>
|
17 |
+
<?php else: ?>
|
18 |
+
<?php _e('Edit Import Template', 'pmxi_plugin') ?>
|
19 |
+
<?php endif ?>
|
20 |
+
</h2>
|
21 |
+
<hr/>
|
22 |
+
|
23 |
+
<?php if ($this->errors->get_error_codes()): ?>
|
24 |
+
<?php $this->error() ?>
|
25 |
+
<?php endif ?>
|
26 |
+
|
27 |
+
<h3>Post Title</h3>
|
28 |
+
<div style="width:100%">
|
29 |
+
<input id="title" class="widefat" type="text" name="title" value="<?php echo esc_attr($post['title']) ?>" />
|
30 |
+
</div>
|
31 |
+
|
32 |
+
<h3>
|
33 |
+
<span class="header-option">
|
34 |
+
<input type="hidden" name="is_keep_linebreaks" value="0" />
|
35 |
+
<input type="checkbox" id="is_keep_linebreaks" name="is_keep_linebreaks" value="1" <?php echo $post['is_keep_linebreaks'] ? 'checked="checked"' : '' ?> />
|
36 |
+
<label for="is_keep_linebreaks"><?php _e('Keep line breaks from XML', 'pmxi_plugin') ?></label>
|
37 |
+
</span>
|
38 |
+
Post Content
|
39 |
+
</h3>
|
40 |
+
<div id="poststuff">
|
41 |
+
<div id="<?php echo user_can_richedit() ? 'postdivrich' : 'postdiv'; ?>" class="postarea">
|
42 |
+
|
43 |
+
<?php the_editor($post['content']) ?>
|
44 |
+
<table id="post-status-info" cellspacing="0">
|
45 |
+
<tbody>
|
46 |
+
<tr>
|
47 |
+
<td id="wp-word-count"></td>
|
48 |
+
<td class="autosave-info">
|
49 |
+
<span id="autosave"> </span>
|
50 |
+
</td>
|
51 |
+
</tr>
|
52 |
+
</tbody>
|
53 |
+
</table>
|
54 |
+
</div>
|
55 |
+
</div>
|
56 |
+
<p>
|
57 |
+
<?php wp_nonce_field('template', '_wpnonce_template') ?>
|
58 |
+
<input type="hidden" name="is_submitted" value="1" />
|
59 |
+
<?php if ($this->isWizard): ?>
|
60 |
+
<a href="<?php echo add_query_arg('action', 'element', $this->baseUrl) ?>" class="button back"><?php _e('Back', 'pmxi_plugin') ?></a>
|
61 |
+
|
62 |
+
<input type="submit" class="button-primary" value="<?php _e('Continue', 'pmxi_plugin') ?> >>" />
|
63 |
+
<input type="text" name="name" title="<?php _e('Save Template As...', 'pmxi_plugin') ?>" style="vertical-align:middle" value="<?php echo esc_attr($post['name']) ?>" />
|
64 |
+
|
65 |
+
<a href="#preview" class="button preview" title="<?php _e('Preview Post', 'pmxi_plugin') ?>"><?php _e('Preview', 'pmxi_plugin') ?></a>
|
66 |
+
<?php else: ?>
|
67 |
+
<input type="submit" class="button-primary" value="<?php _e('Edit', 'pmxi_plugin') ?>" />
|
68 |
+
<input type="text" name="name" title="<?php _e('Save Template As...', 'pmxi_plugin') ?>" style="vertical-align:middle" value="<?php echo esc_attr($post['name']) ?>" />
|
69 |
+
<?php endif ?>
|
70 |
+
</p>
|
71 |
+
</td>
|
72 |
+
<?php if ($this->isWizard or $this->isTemplateEdit): ?>
|
73 |
+
<td class="right">
|
74 |
+
<p><?php _e('Drag & Drop opening tag of an element for inserting corresponding XPath into template or title.', 'pmxi_plugin') ?></p>
|
75 |
+
<p><?php _e('<a href="http://www.wpallimport.com/template-syntax/" target="_blank">Template Syntax Documentation</a>', 'pmxi_plugin') ?> - includes information on looping and shortcodes.</p>
|
76 |
+
<?php $this->tag() ?>
|
77 |
+
</td>
|
78 |
+
<?php endif ?>
|
79 |
+
</tr>
|
80 |
+
</table>
|
81 |
+
</form>
|
views/admin/manage/bulk.php
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<h2>Bulk Delete Imports</h2>
|
2 |
+
|
3 |
+
<form method="post">
|
4 |
+
<input type="hidden" name="action" value="bulk" />
|
5 |
+
<input type="hidden" name="bulk-action" value="<?php echo esc_attr($action) ?>" />
|
6 |
+
<?php foreach ($ids as $id): ?>
|
7 |
+
<input type="hidden" name="items[]" value="<?php echo esc_attr($id) ?>" />
|
8 |
+
<?php endforeach ?>
|
9 |
+
|
10 |
+
<p><?php printf(__('Are you sure you want to delete <strong>%s</strong> selected %s?', 'pmxi_plugin'), $items->count(), _n('import', 'imports', $items->count(), 'pmxi_plugin')) ?></p>
|
11 |
+
<p><input type="checkbox" id="is_delete_posts" name="is_delete_posts" /> <label for="is_delete_posts">Delete associated posts as well</label></p>
|
12 |
+
|
13 |
+
<p class="submit">
|
14 |
+
<?php wp_nonce_field('bulk-imports', '_wpnonce_bulk-imports') ?>
|
15 |
+
<input type="hidden" name="is_confirmed" value="1" />
|
16 |
+
<input type="submit" class="button-primary" value="Delete" />
|
17 |
+
</p>
|
18 |
+
</form>
|
views/admin/manage/delete.php
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<h2><?php _e('Delete Import', 'pmxi_plugin') ?></h2>
|
2 |
+
|
3 |
+
<form method="post">
|
4 |
+
<p><?php printf(__('Are you sure you want to delete <strong>%s</strong> import?', 'pmxi_plugin'), $item->name) ?></p>
|
5 |
+
<p><input type="checkbox" id="is_delete_posts" name="is_delete_posts" /> <label for="is_delete_posts">Delete associated posts as well</label></p>
|
6 |
+
<p class="submit">
|
7 |
+
<?php wp_nonce_field('delete-import', '_wpnonce_delete-import') ?>
|
8 |
+
<input type="hidden" name="is_confirmed" value="1" />
|
9 |
+
<input type="submit" class="button-primary" value="Delete" />
|
10 |
+
</p>
|
11 |
+
|
12 |
+
</form>
|
views/admin/manage/index.php
ADDED
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<h2>
|
2 |
+
<?php _e('Manage Imports', 'pmxi_plugin') ?>
|
3 |
+
|
4 |
+
<a href="<?php echo esc_url(add_query_arg(array('page' => 'pmxi-admin-import'), admin_url('admin.php'))) ?>" class="add-new"><?php echo esc_html_x('New Import', 'pmxi_plugin'); ?></a>
|
5 |
+
</h2>
|
6 |
+
|
7 |
+
<?php if ($this->errors->get_error_codes()): ?>
|
8 |
+
<?php $this->error() ?>
|
9 |
+
<?php endif ?>
|
10 |
+
|
11 |
+
<form method="get">
|
12 |
+
<input type="hidden" name="page" value="<?php echo esc_attr($this->input->get('page')) ?>" />
|
13 |
+
<p class="search-box">
|
14 |
+
<label for="search-input" class="screen-reader-text"><?php _e('Search Imports', 'pmxi_plugin') ?>:</label>
|
15 |
+
<input id="search-input" type="text" name="s" value="<?php echo esc_attr($s) ?>" />
|
16 |
+
<input type="submit" class="button" value="<?php _e('Search Imports', 'pmxi_plugin') ?>">
|
17 |
+
</p>
|
18 |
+
</form>
|
19 |
+
|
20 |
+
<?php
|
21 |
+
// define the columns to display, the syntax is 'internal name' => 'display name'
|
22 |
+
$columns = array(
|
23 |
+
'id' => __('ID', 'pmxi_plugin'),
|
24 |
+
'name' => __('XML File', 'pmxi_plugin'),
|
25 |
+
'scheduled' => __('Recurring', 'pmxi_plugin'),
|
26 |
+
'registered_on' => __('Executed On', 'pmxi_plugin'),
|
27 |
+
'post_count' => __('Posts/Pages', 'pmxi_plugin'),
|
28 |
+
);
|
29 |
+
?>
|
30 |
+
<form method="post" id="import-list" action="<?php echo remove_query_arg('pmxi_nt') ?>">
|
31 |
+
<input type="hidden" name="action" value="bulk" />
|
32 |
+
<?php wp_nonce_field('bulk-imports', '_wpnonce_bulk-imports') ?>
|
33 |
+
|
34 |
+
<div class="tablenav">
|
35 |
+
<div class="alignleft actions">
|
36 |
+
<select name="bulk-action">
|
37 |
+
<option value="" selected="selected"><?php _e('Bulk Actions', 'pmxi_plugin') ?></option>
|
38 |
+
<option value="delete"><?php _e('Delete', 'pmxi_plugin') ?></option>
|
39 |
+
</select>
|
40 |
+
<input type="submit" value="<?php esc_attr_e('Apply', 'pmxi_plugin') ?>" name="doaction" id="doaction" class="button-secondary action" />
|
41 |
+
</div>
|
42 |
+
|
43 |
+
<?php if ($page_links): ?>
|
44 |
+
<div class="tablenav-pages">
|
45 |
+
<?php echo $page_links_html = sprintf(
|
46 |
+
'<span class="displaying-num">' . __('Displaying %s–%s of %s', 'pmxi_plugin') . '</span>%s',
|
47 |
+
number_format_i18n(($pagenum - 1) * $perPage + 1),
|
48 |
+
number_format_i18n(min($pagenum * $perPage, $list->total())),
|
49 |
+
number_format_i18n($list->total()),
|
50 |
+
$page_links
|
51 |
+
) ?>
|
52 |
+
</div>
|
53 |
+
<?php endif ?>
|
54 |
+
</div>
|
55 |
+
<div class="clear"></div>
|
56 |
+
|
57 |
+
<table class="widefat pmxi-admin-imports">
|
58 |
+
<thead>
|
59 |
+
<tr>
|
60 |
+
<th class="manage-column column-cb check-column" scope="col">
|
61 |
+
<input type="checkbox" />
|
62 |
+
</th>
|
63 |
+
<?php
|
64 |
+
$col_html = '';
|
65 |
+
foreach ($columns as $column_id => $column_display_name) {
|
66 |
+
$column_link = "<a href='";
|
67 |
+
$order2 = 'ASC';
|
68 |
+
if ($order_by == $column_id)
|
69 |
+
$order2 = ($order == 'DESC') ? 'ASC' : 'DESC';
|
70 |
+
|
71 |
+
$column_link .= esc_url(add_query_arg(array('order' => $order2, 'order_by' => $column_id), $this->baseUrl));
|
72 |
+
$column_link .= "'>{$column_display_name}</a>";
|
73 |
+
$col_html .= '<th scope="col" class="column-' . $column_id . ' ' . ($order_by == $column_id ? $order : '') . '">' . $column_link . '</th>';
|
74 |
+
}
|
75 |
+
echo $col_html;
|
76 |
+
?>
|
77 |
+
</tr>
|
78 |
+
</thead>
|
79 |
+
<tfoot>
|
80 |
+
<tr>
|
81 |
+
<th class="manage-column column-cb check-column" scope="col">
|
82 |
+
<input type="checkbox" />
|
83 |
+
</th>
|
84 |
+
<?php echo $col_html; ?>
|
85 |
+
</tr>
|
86 |
+
</tfoot>
|
87 |
+
<tbody id="the-pmxi-admin-import-list" class="list:pmxi-admin-imports">
|
88 |
+
<?php if ($list->isEmpty()): ?>
|
89 |
+
<tr>
|
90 |
+
<td colspan="<?php echo count($columns) + 1 ?>"><?php _e('No previous imports found.', 'pmxi_plugin') ?></td>
|
91 |
+
</tr>
|
92 |
+
<?php else: ?>
|
93 |
+
<?php
|
94 |
+
$periods = array( // scheduling periods
|
95 |
+
'*/5 * * * *' => __('every 5 min'),
|
96 |
+
'*/10 * * * *' => __('every 10 min'),
|
97 |
+
'*/30 * * * *' => __('half-hourly'),
|
98 |
+
'0 * * * *' => __('hourly'),
|
99 |
+
'0 */4 * * *' => __('every 4 hours'),
|
100 |
+
'0 */12 * * *' => __('half-daily'),
|
101 |
+
'0 0 * * *' => __('daily'),
|
102 |
+
'0 0 * * 1' => __('weekly'),
|
103 |
+
'0 0 1 * 1' => __('monthly'),
|
104 |
+
);
|
105 |
+
$class = '';
|
106 |
+
?>
|
107 |
+
<?php foreach ($list as $item): ?>
|
108 |
+
<?php $class = ('alternate' == $class) ? '' : 'alternate'; ?>
|
109 |
+
<tr class="<?php echo $class; ?>" valign="middle">
|
110 |
+
<th scope="row" class="check-column">
|
111 |
+
<input type="checkbox" id="item_<?php echo $item['id'] ?>" name="items[]" value="<?php echo esc_attr($item['id']) ?>" />
|
112 |
+
</th>
|
113 |
+
<?php foreach ($columns as $column_id => $column_display_name): ?>
|
114 |
+
<?php
|
115 |
+
switch ($column_id):
|
116 |
+
case 'id':
|
117 |
+
?>
|
118 |
+
<th valign="top" scope="row">
|
119 |
+
<?php echo $item['id'] ?>
|
120 |
+
</th>
|
121 |
+
<?php
|
122 |
+
break;
|
123 |
+
case 'scheduled':
|
124 |
+
?>
|
125 |
+
<td>
|
126 |
+
<?php echo $item['scheduled'] ? $periods[$item['scheduled']] : '' ?>
|
127 |
+
</td>
|
128 |
+
<?php
|
129 |
+
break;
|
130 |
+
case 'registered_on':
|
131 |
+
?>
|
132 |
+
<td>
|
133 |
+
<?php if ('0000-00-00 00:00:00' == $item['registered_on']): ?>
|
134 |
+
<em>never</em>
|
135 |
+
<?php else: ?>
|
136 |
+
<?php echo mysql2date(__('Y/m/d g:i a', 'pmxi_plugin'), $item['registered_on']) ?>
|
137 |
+
<?php endif ?>
|
138 |
+
</td>
|
139 |
+
<?php
|
140 |
+
break;
|
141 |
+
case 'name':
|
142 |
+
?>
|
143 |
+
<td>
|
144 |
+
<strong><?php echo $item['name'] ?></strong>
|
145 |
+
<?php if ($item['path']): ?>
|
146 |
+
- <em><?php echo preg_replace('%^(\w+://[^:]+:)[^@]+@%', '$1*****@', $item['path']) ?></em>
|
147 |
+
<?php endif ?>
|
148 |
+
<div class="row-actions">
|
149 |
+
<?php if (in_array($item['type'], array('url', 'ftp', 'file'))): ?>
|
150 |
+
<span class="update"><a class="update" href="<?php echo esc_url(add_query_arg(array('id' => $item['id'], 'action' => 'update'), $this->baseUrl)) ?>"><?php _e('Update', 'pmxi_plugin') ?></a></span> |
|
151 |
+
<?php endif ?>
|
152 |
+
<span class="delete"><a class="delete" href="<?php echo esc_url(add_query_arg(array('id' => $item['id'], 'action' => 'delete'), $this->baseUrl)) ?>"><?php _e('Delete', 'pmxi_plugin') ?></a></span>
|
153 |
+
</div>
|
154 |
+
</td>
|
155 |
+
<?php
|
156 |
+
break;
|
157 |
+
case 'post_count':
|
158 |
+
?>
|
159 |
+
<td>
|
160 |
+
<strong><?php echo $item['post_count'] ?></strong>
|
161 |
+
</td>
|
162 |
+
<?php
|
163 |
+
break;
|
164 |
+
default:
|
165 |
+
?>
|
166 |
+
<td>
|
167 |
+
<?php echo $item[$column_id] ?>
|
168 |
+
</td>
|
169 |
+
<?php
|
170 |
+
break;
|
171 |
+
endswitch;
|
172 |
+
?>
|
173 |
+
<?php endforeach; ?>
|
174 |
+
</tr>
|
175 |
+
<?php endforeach; ?>
|
176 |
+
<?php endif ?>
|
177 |
+
</tbody>
|
178 |
+
</table>
|
179 |
+
|
180 |
+
<div class="tablenav">
|
181 |
+
<?php if ($page_links): ?><div class="tablenav-pages"><?php echo $page_links_html ?></div><?php endif ?>
|
182 |
+
|
183 |
+
<div class="alignleft actions">
|
184 |
+
<select name="bulk-action2">
|
185 |
+
<option value="" selected="selected"><?php _e('Bulk Actions', 'pmxi_plugin') ?></option>
|
186 |
+
<?php if ('trash' != $type): ?>
|
187 |
+
<option value="delete"><?php _e('Delete', 'pmxi_plugin') ?></option>
|
188 |
+
<?php else: ?>
|
189 |
+
<option value="restore"><?php _e('Restore', 'pmxi_plugin')?></option>
|
190 |
+
<option value="delete"><?php _e('Delete Permanently', 'pmxi_plugin')?></option>
|
191 |
+
<?php endif ?>
|
192 |
+
</select>
|
193 |
+
<input type="submit" value="<?php esc_attr_e('Apply', 'pmxi_plugin') ?>" name="doaction2" id="doaction2" class="button-secondary action" />
|
194 |
+
</div>
|
195 |
+
</div>
|
196 |
+
<div class="clear"></div>
|
197 |
+
<a href="http://www.wpallimport.com/upgrade-to-pro?from=mi" target="_blank">Upgrade to pro to edit import options or perform re-imports/recurring imports.</a>
|
198 |
+
</form>
|
views/admin/manage/update.php
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<h2><?php _e('Update Import', 'pmxi_plugin') ?></h2>
|
2 |
+
|
3 |
+
<?php if ($this->errors->get_error_codes()): ?>
|
4 |
+
<?php $this->error() ?>
|
5 |
+
<?php endif ?>
|
6 |
+
|
7 |
+
<?php if ($item->path): ?>
|
8 |
+
<form method="post">
|
9 |
+
<p><?php printf(__('Are you sure you want to update <strong>%s</strong> import?', 'pmxi_plugin'), $item->name) ?></p>
|
10 |
+
<p><?php printf(__('Source path is <strong>%s</strong>', 'pmxi_plugin'), $item->path) ?></p>
|
11 |
+
|
12 |
+
<p class="submit">
|
13 |
+
<?php wp_nonce_field('update-import', '_wpnonce_update-import') ?>
|
14 |
+
<input type="hidden" name="is_confirmed" value="1" />
|
15 |
+
<input type="submit" class="button-primary ajax-update" value="Create Posts" />
|
16 |
+
</p>
|
17 |
+
|
18 |
+
</form>
|
19 |
+
<div id="process" style="display:none;">
|
20 |
+
<?php include PMXI_Plugin::ROOT_DIR . '/views/admin/import/process.php'; ?>
|
21 |
+
</div>
|
22 |
+
<?php else: ?>
|
23 |
+
<div class="error">
|
24 |
+
<p><?php _e('Update feature is not available for this import since it has no external path linked.') ?></p>
|
25 |
+
</div>
|
26 |
+
<?php endif ?>
|
views/admin/settings/index.php
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<form class="settings" method="post" action="<?php echo $this->baseUrl ?>">
|
2 |
+
|
3 |
+
<h2><?php _e('WP All Import Settings', 'pmxi_plugin') ?></h2>
|
4 |
+
<hr />
|
5 |
+
<?php if ($this->errors->get_error_codes()): ?>
|
6 |
+
<?php $this->error() ?>
|
7 |
+
<?php endif ?>
|
8 |
+
|
9 |
+
<h3><?php _e('Saved Templates', 'pmxi_plugin') ?></h3>
|
10 |
+
<?php $templates = new PMXI_Template_List(); $templates->getBy()->convertRecords() ?>
|
11 |
+
<?php if ($templates->total()): ?>
|
12 |
+
<table>
|
13 |
+
<?php foreach ($templates as $t): ?>
|
14 |
+
<tr>
|
15 |
+
<td><input id="template-<?php echo $t->id ?>" type="checkbox" name="templates[]" value="<?php echo $t->id ?>" /></td>
|
16 |
+
<td><label for="template-<?php echo $t->id ?>"><?php echo $t->name ?></label></td>
|
17 |
+
</tr>
|
18 |
+
<?php endforeach ?>
|
19 |
+
</table>
|
20 |
+
<p class="submit-buttons">
|
21 |
+
<?php wp_nonce_field('delete-templates', '_wpnonce_delete-templates') ?>
|
22 |
+
<input type="hidden" name="is_templates_submitted" value="1" />
|
23 |
+
<input type="submit" class="button-primary" value="<?php _e('Delete Selected', 'pmxi_plugin') ?>" />
|
24 |
+
</p>
|
25 |
+
<?php else: ?>
|
26 |
+
<em><?php _e('There are no templates saved', 'pmxi_plugin') ?></em>
|
27 |
+
<?php endif ?>
|
28 |
+
</form>
|
29 |
+
<br />
|
30 |
+
|
31 |
+
<form name="settings" method="post" action="<?php echo $this->baseUrl ?>">
|
32 |
+
<h3><?php _e('History', 'pmxi_plugin') ?></h3>
|
33 |
+
<div><?php printf(__('Store maximum of %s of the most recent files imported. 0 = unlimited', 'pmxi_plugin'), '<input class="small-text" type="text" name="history_file_count" value="' . esc_attr($post['history_file_count']) . '" />') ?></div>
|
34 |
+
<div><?php printf(__('Store imported file history for a maximum of %s of days. 0 = unlimited', 'pmxi_plugin'), '<input class="small-text" type="text" name="history_file_age" value="' . esc_attr($post['history_file_age']) . '" />') ?></div>
|
35 |
+
<h3><?php _e('Your server setting', 'pmxi_plugin') ?></h3>
|
36 |
+
<div><?php printf(__('upload_max_filesize %s', 'pmxi_plugin'), ini_get('upload_max_filesize')) ?></div>
|
37 |
+
<div><?php printf(__('post_max_size %s', 'pmxi_plugin'), ini_get('post_max_size')) ?></div>
|
38 |
+
<div><?php printf(__('max_execution_time %s', 'pmxi_plugin'), ini_get('max_execution_time')) ?></div>
|
39 |
+
<div><?php printf(__('max_input_time %s', 'pmxi_plugin'), ini_get('max_input_time')) ?></div>
|
40 |
+
<h3><?php _e('XML parsing filters', 'pmxi_plugin') ?></h3>
|
41 |
+
|
42 |
+
<div><?php printf(__('Filter XML contains HTML entities %s', 'pmxi_plugin'), '<input type="radio" name="html_entities" value="1" '.((!empty($post['html_entities'])) ? 'checked="checked"' : '').' /> Yes <input type="radio" name="html_entities" value="0" '.((empty($post['html_entities'])) ? 'checked="checked"' : '').' /> No') ?></div>
|
43 |
+
<p class="submit-buttons">
|
44 |
+
<?php wp_nonce_field('edit-settings', '_wpnonce_edit-settings') ?>
|
45 |
+
<input type="hidden" name="is_settings_submitted" value="1" />
|
46 |
+
<input type="submit" class="button-primary" value="Save Settings" />
|
47 |
+
</p>
|
48 |
+
|
49 |
+
</form>
|
views/controller/error.php
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
1 |
+
<?php foreach ($errors as $msg): ?>
|
2 |
+
<div class="error"><p><?php echo $msg ?></p></div>
|
3 |
+
<?php endforeach ?>
|