Import any XML or CSV File to WordPress - Version 2.12

Version Description

  • Initial release on WordPress.org.
Download this release

Release Info

Developer soflyy
Plugin Icon 128x128 Import any XML or CSV File to WordPress
Version 2.12
Comparing to
See all releases

Version 2.12

Files changed (115) hide show
  1. actions/admin_menu.php +22 -0
  2. actions/admin_notices.php +28 -0
  3. actions/delete_post.php +6 -0
  4. actions/wp_loaded.php +9 -0
  5. classes/config.php +91 -0
  6. classes/helper.php +136 -0
  7. classes/input.php +72 -0
  8. config/options.php +17 -0
  9. controllers/admin/help.php +12 -0
  10. controllers/admin/home.php +12 -0
  11. controllers/admin/import.php +1305 -0
  12. controllers/admin/manage.php +108 -0
  13. controllers/admin/settings.php +56 -0
  14. controllers/controller.php +102 -0
  15. controllers/controller/admin.php +91 -0
  16. helpers/backward.php +40 -0
  17. helpers/get_taxonomies_by_object_type.php +25 -0
  18. helpers/str_getcsv.php +17 -0
  19. helpers/wp_delete_attachments.php +10 -0
  20. helpers/wp_redirect_or_javascript.php +17 -0
  21. libraries/XmlImportConfig.php +88 -0
  22. libraries/XmlImportCsvParse.php +2427 -0
  23. libraries/XmlImportException.php +10 -0
  24. libraries/XmlImportParser.php +106 -0
  25. libraries/XmlImportReaderInterface.php +21 -0
  26. libraries/XmlImportStringReader.php +67 -0
  27. libraries/XmlImportTemplate.php +78 -0
  28. libraries/XmlImportTemplateCodeGenerator.php +341 -0
  29. libraries/XmlImportTemplateParser.php +393 -0
  30. libraries/XmlImportTemplateScanner.php +403 -0
  31. libraries/XmlImportToken.php +167 -0
  32. libraries/ast/XmlImportAstElseif.php +76 -0
  33. libraries/ast/XmlImportAstExpression.php +15 -0
  34. libraries/ast/XmlImportAstFloat.php +15 -0
  35. libraries/ast/XmlImportAstForeach.php +14 -0
  36. libraries/ast/XmlImportAstFunction.php +90 -0
  37. libraries/ast/XmlImportAstIf.php +161 -0
  38. libraries/ast/XmlImportAstInteger.php +15 -0
  39. libraries/ast/XmlImportAstLiteral.php +52 -0
  40. libraries/ast/XmlImportAstMath.php +73 -0
  41. libraries/ast/XmlImportAstPrint.php +60 -0
  42. libraries/ast/XmlImportAstSequence.php +161 -0
  43. libraries/ast/XmlImportAstStatement.php +15 -0
  44. libraries/ast/XmlImportAstString.php +15 -0
  45. libraries/ast/XmlImportAstText.php +59 -0
  46. libraries/ast/XmlImportAstWith.php +15 -0
  47. libraries/ast/XmlImportAstXPath.php +15 -0
  48. libraries/ast/XmlImportAstXpathClause.php +83 -0
  49. models/file/list.php +32 -0
  50. models/file/record.php +78 -0
  51. models/import/list.php +8 -0
  52. models/import/record.php +728 -0
  53. models/model.php +198 -0
  54. models/model/list.php +149 -0
  55. models/model/record.php +176 -0
  56. models/post/list.php +10 -0
  57. models/post/record.php +15 -0
  58. models/template/list.php +8 -0
  59. models/template/record.php +13 -0
  60. plugin.php +479 -0
  61. readme.txt +69 -0
  62. schema.php +64 -0
  63. screenshot-1.png +0 -0
  64. screenshot-2.png +0 -0
  65. screenshot-3.png +0 -0
  66. screenshot-4.png +0 -0
  67. static/css/admin-ie.css +13 -0
  68. static/css/admin.css +469 -0
  69. static/img/date-picker.gif +0 -0
  70. static/img/down.gif +0 -0
  71. static/img/help.png +0 -0
  72. static/img/loading.gif +0 -0
  73. static/img/screen-options-right-up.gif +0 -0
  74. static/img/screen-options-right.gif +0 -0
  75. static/img/xmlicon.png +0 -0
  76. static/js/admin.js +421 -0
  77. static/js/jquery/css/smoothness/images/tipsy.gif +0 -0
  78. static/js/jquery/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  79. static/js/jquery/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  80. static/js/jquery/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  81. static/js/jquery/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  82. static/js/jquery/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  83. static/js/jquery/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  84. static/js/jquery/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  85. static/js/jquery/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  86. static/js/jquery/css/smoothness/images/ui-icons_222222_256x240.png +0 -0
  87. static/js/jquery/css/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  88. static/js/jquery/css/smoothness/images/ui-icons_454545_256x240.png +0 -0
  89. static/js/jquery/css/smoothness/images/ui-icons_888888_256x240.png +0 -0
  90. static/js/jquery/css/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  91. static/js/jquery/css/smoothness/jquery-ui.css +405 -0
  92. static/js/jquery/css/smoothness/jquery.tipsy.css +11 -0
  93. static/js/jquery/jquery.tipsy.js +198 -0
  94. static/js/jquery/ui.autocomplete.js +606 -0
  95. static/js/jquery/ui.datepicker.js +1636 -0
  96. static/js/pmxi.js +14 -0
  97. views/admin/help/index.php +2 -0
  98. views/admin/home/index.php +16 -0
  99. views/admin/import/element.php +1 -0
  100. views/admin/import/element_after.php +41 -0
  101. views/admin/import/error.php +3 -0
  102. views/admin/import/evaluate.php +15 -0
  103. views/admin/import/index.php +106 -0
  104. views/admin/import/options.php +309 -0
  105. views/admin/import/preview.php +12 -0
  106. views/admin/import/process-complete.php +0 -0
  107. views/admin/import/process.php +6 -0
  108. views/admin/import/tag.php +12 -0
  109. views/admin/import/template.php +81 -0
  110. views/admin/manage/bulk.php +18 -0
  111. views/admin/manage/delete.php +12 -0
  112. views/admin/manage/index.php +198 -0
  113. views/admin/manage/update.php +26 -0
  114. views/admin/settings/index.php +49 -0
  115. 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') . ' &lsaquo; ' . __('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') . ' &lsaquo; ' . __('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') . ' &lsaquo; ' . __('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') . ' &lsaquo; ' . __('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">&lt;<span class="xml-tag-name">' . $el->nodeName . '</span>'; $this->render_xml_attributes($el, $path . '/'); echo '&gt;</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">[ &dArr; ' . sprintf(__('<strong>%s</strong> %s more', 'pmxi_plugin'), $no, _n('element', 'elements', $no, 'pmxi_plugin')) . ' &dArr; ]</div>';
1143
+ }
1144
+ }
1145
+ }
1146
+ echo '</div>';
1147
+ }
1148
+ echo '<div class="xml-tag closing">&lt;/<span class="xml-tag-name">' . $el->nodeName . '</span>&gt;</div>';
1149
+ } else {
1150
+ echo '<div class="xml-tag opening empty">&lt;<span class="xml-tag-name">' . $el->nodeName . '</span>'; $this->render_xml_attributes($el); echo '/&gt;</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">[ &dArr; ' . sprintf(__('<strong>%s</strong> %s more', 'pmxi_plugin'), $no, _n('element', 'elements', $no, 'pmxi_plugin')) . ' &dArr; ]</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&#8203;', $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&#8203;', $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' => __('&laquo;', 'pmxi_plugin'),
53
+ 'next_text' => __('&raquo;', '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() : '&#xa0;') : // 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)) ? '&#xa0;' : '');
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 ? '&#xa0;' : '') + 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
+ &nbsp;
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') ?> &gt;&gt;" />
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') ?> &gt;&gt;" />
96
+ </p>
97
+ </div>
98
+ <br />
99
+ <table><tr><td class="note"><b>*</b>&nbsp;</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
+ &nbsp;
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...&nbsp;<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...&nbsp;<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
+ &nbsp;
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
+ &nbsp;
275
+ <input type="submit" class="button-primary ajax-import" value="<?php _e('Save &amp; 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 &amp; 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">&lang;&lang;</a><?php else: ?><span>&lang;&lang;</span><?php endif ?>
7
+ <?php if ($tagno < $elements->length): ?><a href="#next">&rang;&rang;</a><?php else: ?><span>&rang;&rang;</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">&nbsp;</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
+ &nbsp;
62
+ <input type="submit" class="button-primary" value="<?php _e('Continue', 'pmxi_plugin') ?> &gt;&gt;" />
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
+ &nbsp;
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 &amp; 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
+ &nbsp;
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&#8211;%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 ?>