Changeset 3136940
- Timestamp:
- 08/17/2024 07:26:18 AM (20 months ago)
- Location:
- orbisius-seo-editor/trunk
- Files:
-
- 4 edited
-
lib/csv.php (modified) (8 diffs)
-
lib/util.php (modified) (1 diff)
-
orbisius-seo-editor.php (modified) (1 diff)
-
readme.txt (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
orbisius-seo-editor/trunk/lib/csv.php
r2838198 r3136940 5 5 * This premium WooCommerce extension allows you to change product prices (up/down) for all products or for a selected category and its subcategories. 6 6 * You can review them before actually making the changes. 7 * 7 * 8 8 * @see https://orbisius.com/products/wordpress-plugins/woocommerce-extensions/orbisius-seo-editor/ 9 9 * @author jaywilliams | myd3.com | https://gist.github.com/jaywilliams 10 10 * @author Svetoslav Marinov (SLAVI) | https://orbisius.com 11 11 */ 12 Class Orbisius_SEO_Editor_CSV { 13 protected $buff_size = 1024; 12 class Orbisius_SEO_Editor_CSV 13 { 14 protected $buff_size = 2048; 14 15 protected $delimiter = ','; 15 const APPEND = 2; 16 const DO_EXIT = 4; 17 const DONT_EXIT = 8; 18 19 public function delimiter($delimiter = '') { 16 const APPEND = 2; 17 const DO_EXIT = 4; 18 const DONT_EXIT = 8; 19 20 public function delimiter($delimiter = '') 21 { 20 22 if (!empty($delimiter)) { 21 23 $this->delimiter = $delimiter; … … 28 30 29 31 /** 30 * Reads a CSV file and returns array of arrays. 31 * The header rows are not returned. We can extract them by getting the keys of the first array element. 32 * @link https://gist.github.com/385876 33 * @return array / false on error 34 */ 35 public function read($filename = '', $flags = 0) { 36 if (!file_exists($filename) || !is_readable($filename)) { 37 trigger_error(sprintf("File [%s] doesn't exist or not readable.", esc_attr($filename)), E_USER_WARNING); 38 return false; 39 } 40 41 $data = []; 42 $header = []; 43 44 try { 45 // In some cases (Windows/Linux) the line endings are not read correctly so we'll hint php to detect the line endings. 46 $old_auto_detect_line_endings_flag = ini_get("auto_detect_line_endings"); 47 ini_set("auto_detect_line_endings", true); 48 49 $handle = fopen($filename, 'rb'); 50 51 if (empty($handle)) { 52 trigger_error(sprintf( 53 "Cannot open file [%s] for reading", 54 esc_attr(basename($filename)) 55 ), 56 E_USER_WARNING 57 ); 58 return false; 59 } 60 61 // we just need a read lock 62 flock($handle, LOCK_SH); 63 64 while (($row = fgetcsv($handle, $this->buff_size, $this->delimiter)) !== false) { 65 if (empty($row)) { 66 continue; 67 } 68 69 $row = array_map('trim', $row); 70 71 if (empty($row)) { 72 continue; 73 } 74 75 // Empty lines could produce empty columns 76 $row_alt_empty_check = array_filter($row); 77 78 if (empty($row_alt_empty_check)) { 79 continue; 80 } 81 82 // No header row OR if the data contains header row somewhere instead of data 83 if (empty($header) || count(array_diff($header, $row)) == 0) { 84 // Validate heading row and in case the user had decided to remove it for some reason 85 // the rows must be alphanumeric + underscore and may contain numbers 86 // correct cols must match be the same elements as the array i.e. all must be correct 87 $valid_cols = 0; 88 $correct_cols_regex = '#^\s*[a-z]+[\w\-]+\s*$#si'; 89 90 foreach ($row as $val) { 91 if (!preg_match($correct_cols_regex, $val)) { 92 break; // do need to check others since at least one didn't validate. 93 } 94 95 $valid_cols++; 96 } 97 98 if ($valid_cols != count($row)) { 99 throw new Exception("The heading row is missing or invalid. It is necessary as we use it to map fields."); 100 } 101 102 if ($flags & self::FORMAT_HEADER_COLS) { 103 foreach ($row as $idx => $val) { 104 $val = strtolower($val); 105 $val = preg_replace('#[^\w]#si', '_', $val); 106 $val = preg_replace('#\_+#si', '_', $val); 107 $val = trim($val, ' _'); 108 $row[$idx] = $val; 109 } 110 } 111 112 $header = $row; 113 } else { 114 $data[] = @array_combine($header, $row); 115 } 116 } 117 } finally { 118 ini_set("auto_detect_line_endings", $old_auto_detect_line_endings_flag); // restore previous value 119 120 if (!empty($handle)) { 121 flock($handle, LOCK_UN); 122 fclose($handle); 123 } 124 } 125 126 return $data; 127 128 /* 129 * Return value 130 array ( 131 0 => 132 array ( 133 'product_title' => 'Bottle', 134 'parent_product_id' => '0', 135 'product_id' => '308', 136 'old_regular_price' => '1.00', 137 'new_regular_price' => '5', 138 'old_sale_price' => '1.00', 139 'new_sale_price' => '2.00', 140 'currency' => 'GBP', 141 'cur_user_id' => '20', 142 'cur_user_email' => 'user@email.com', 143 'cur_date' => '2014-06-09 10:59:37', 144 ), 145 1 => 146 array ( 147 'product_title' => 'Bottles (Case)', 148 'parent_product_id' => '0', 149 'product_id' => '309', 150 'old_regular_price' => '100.00', 151 'new_regular_price' => '50.00', 152 'old_sale_price' => '10.00', 153 'new_sale_price' => '10.00', 154 'currency' => 'GBP', 155 'cur_user_id' => '20', 156 'cur_user_email' => 'user@email.com', 157 'cur_date' => '2014-06-09 10:59:37', 158 ), 159 ) 160 */ 32 * Reads a CSV file and returns array of arrays. 33 * The header rows are not returned. We can extract them by getting the keys of the first array element. 34 * @link https://gist.github.com/385876 35 * @return array|false on error 36 */ 37 public function read($filename = '', $flags = 0) 38 { 39 if (!file_exists($filename) || !is_readable($filename)) { 40 trigger_error(sprintf("File [%s] doesn't exist or not readable.", esc_attr($filename)), E_USER_WARNING); 41 return false; 42 } 43 44 $data = []; 45 $header = []; 46 47 try { 48 // In some cases (Windows/Linux) the line endings are not read correctly, so we'll hint php to detect the line endings. 49 if (version_compare(PHP_VERSION, '8.1.0', '<')) { 50 $old_auto_detect_line_endings_flag = ini_get("auto_detect_line_endings"); 51 ini_set("auto_detect_line_endings", true); 52 } 53 54 $handle = fopen($filename, 'rb'); 55 56 if (empty($handle)) { 57 trigger_error(sprintf( 58 "Cannot open file [%s] for reading", 59 esc_attr(basename($filename)) 60 ), 61 E_USER_WARNING 62 ); 63 return false; 64 } 65 66 // we just need a read lock 67 flock($handle, LOCK_SH); 68 69 // Thsi is 70 $row_num = 0; // all rows including the empty ones 71 $row_idx = 0; 72 73 while (($row = fgetcsv($handle, $this->buff_size, $this->delimiter)) !== false) { 74 $row_num++; 75 76 if (empty($row)) { 77 continue; 78 } 79 80 $row = array_map('trim', $row); 81 82 if (empty($row)) { 83 continue; 84 } 85 86 // Empty lines could produce empty columns 87 $row_alt_empty_check = array_filter($row); 88 89 if (empty($row_alt_empty_check)) { 90 continue; 91 } 92 93 $row_idx++; 94 95 // No header row OR if the data contains header row somewhere instead of data 96 if (empty($header) || count(array_diff($header, $row)) == 0) { 97 // Validate heading row and in case the user had decided to remove it for some reason 98 // the rows must be alphanumeric + underscore and may contain numbers 99 // correct cols must match be the same elements as the array i.e. all must be correct 100 $valid_cols = 0; 101 $correct_cols_regex = '#^\s*[a-z]+[\w\-]+\s*$#si'; 102 103 foreach ($row as $val) { 104 if (!preg_match($correct_cols_regex, $val)) { 105 break; // do need to check others since at least one didn't validate. 106 } 107 108 $valid_cols++; 109 } 110 111 if ($valid_cols != count($row)) { 112 // Skip the first 5 non-empty rows if they have X or fewer columns. 113 // Sometimes people enter content before the heading columns 114 if ($row_idx <= 5 && $valid_cols <= 2) { 115 continue; 116 } 117 118 throw new Exception("The heading row, which must include id, title etc, is missing or invalid. It is necessary as we use it to map fields."); 119 } 120 121 if ($flags & self::FORMAT_HEADER_COLS) { 122 foreach ($row as $idx => $val) { 123 $val = strtolower($val); 124 $val = preg_replace('#[^\w]#si', '_', $val); 125 $val = preg_replace('#\_+#si', '_', $val); 126 $val = trim($val, ' _'); 127 $row[$idx] = $val; 128 } 129 } 130 131 // let's skip any textual rows before the headers 132 if ($row_idx <= 5 && count($row) <= 3) { 133 continue; 134 } 135 136 // so several rows and not enough header cols this is an error 137 if ($row_idx > 5 && count($row) <= 3) { 138 throw new Exception("The heading row has fewer than expected fields It is necessary as we use it to map fields."); 139 } 140 141 $header = $row; 142 } else { 143 $data[] = @array_combine($header, $row); 144 } 145 } 146 } finally { 147 if (version_compare(PHP_VERSION, '8.1.0', '<')) { 148 ini_set("auto_detect_line_endings", $old_auto_detect_line_endings_flag); // restore previous value 149 } 150 151 if (!empty($handle)) { 152 flock($handle, LOCK_UN); 153 fclose($handle); 154 } 155 } 156 157 return $data; 158 159 /* 160 * Return value 161 array ( 162 0 => 163 array ( 164 'product_title' => 'Bottle', 165 'parent_product_id' => '0', 166 'product_id' => '308', 167 'old_regular_price' => '1.00', 168 'new_regular_price' => '5', 169 'old_sale_price' => '1.00', 170 'new_sale_price' => '2.00', 171 'currency' => 'GBP', 172 'cur_user_id' => '20', 173 'cur_user_email' => 'user@email.com', 174 'cur_date' => '2014-06-09 10:59:37', 175 ), 176 1 => 177 array ( 178 'product_title' => 'Bottles (Case)', 179 'parent_product_id' => '0', 180 'product_id' => '309', 181 'old_regular_price' => '100.00', 182 'new_regular_price' => '50.00', 183 'old_sale_price' => '10.00', 184 'new_sale_price' => '10.00', 185 'currency' => 'GBP', 186 'cur_user_id' => '20', 187 'cur_user_email' => 'user@email.com', 188 'cur_date' => '2014-06-09 10:59:37', 189 ), 190 ) 191 */ 161 192 } 162 193 … … 170 201 * @return bool true/false depending on success 171 202 */ 172 public function write($data, $title_row = array(), $file = '', $flags = 0) { 173 static $browser_handle = null; 174 $headers_sent = 0; 203 public function write($data, $title_row = array(), $file = '', $flags = 0) 204 { 205 static $browser_handle = null; 206 $headers_sent = 0; 175 207 176 208 $local_file = !empty($file); … … 180 212 181 213 if ($output_in_browser) { // browser 182 if (is_null($browser_handle)) {183 $this->sendDownloadHeadersForCSVDownload($file);184 $headers_sent = 1;185 $handle = fopen('php://output', 'wb');186 $browser_handle = $handle;187 } else {188 $handle = $browser_handle;189 }214 if (is_null($browser_handle)) { 215 $this->sendDownloadHeadersForCSVDownload($file); 216 $headers_sent = 1; 217 $handle = fopen('php://output', 'wb'); 218 $browser_handle = $handle; 219 } else { 220 $handle = $browser_handle; 221 } 190 222 } else { 191 223 $handle = fopen($file, $append ? 'ab' : 'wb'); … … 199 231 // this resulted in header column being written multiple times. 200 232 // that's why we clear the cache every time we write. 201 if ($local_file) {202 clearstatcache();203 flock( $handle, LOCK_EX);204 }205 206 $add_title_row = 0;207 208 if (!empty($title_row)) {209 if ($output_in_browser) {210 if ($headers_sent) { // first time output is opened211 $add_title_row = 1;212 }213 } elseif (!$append) {214 $add_title_row = 1;215 } elseif (!empty($file) && filesize($file) < 10) {216 $add_title_row = 1;217 }218 }233 if ($local_file) { 234 clearstatcache(); 235 flock($handle, LOCK_EX); 236 } 237 238 $add_title_row = 0; 239 240 if (!empty($title_row)) { 241 if ($output_in_browser) { 242 if ($headers_sent) { // first time output is opened 243 $add_title_row = 1; 244 } 245 } elseif (!$append) { 246 $add_title_row = 1; 247 } elseif (!empty($file) && filesize($file) < 10) { 248 $add_title_row = 1; 249 } 250 } 219 251 220 252 // add the title row only if we're not appending or if the file size is minimal (fewer than 10 bytes). … … 226 258 // In case the dev has passed just a row and not array of rows 227 259 if (empty($data[0]) || !is_array($data[0])) { 228 $data = [ $data];260 $data = [$data]; 229 261 } 230 262 … … 234 266 235 267 if ($local_file) { 236 flock( $handle, LOCK_UN);268 flock($handle, LOCK_UN); 237 269 fclose($handle); 238 270 } … … 241 273 die; 242 274 } 243 275 244 276 return true; 245 277 } 246 278 247 /** 248 * Sends the headers 249 * @return void 250 */ 251 public function sendDownloadHeadersForCSVDownload($inp_filename = '') { 252 if (headers_sent()) { 253 return; 254 } 255 256 if (empty($inp_filename)) { 257 $inp_filename = $this->getDownloadFileName(); 258 } 259 260 if (empty($inp_filename)) { 261 $file_prefix = basename(ORBISIUS_SEO_EDITOR_BASE_PLUGIN); 262 $file_prefix = str_replace('.php', '', $file_prefix); 263 $file_prefix = sanitize_title( $file_prefix ); 264 $file_prefix = str_replace( '-', '_', $file_prefix ); 265 $file_prefix .= '_'; 266 $filename = $file_prefix . date( 'Ymd' ) . '_' . date( 'His' ) . '.csv'; 267 } else { 268 $filename = basename($inp_filename); 269 } 270 271 // Output CSV-specific headers 272 // https://stackoverflow.com/questions/393647/response-content-type-as-csv 273 header("Pragma: public"); 274 header('X-Content-Type-Options: nosniff'); 275 header("Expires: 0"); 276 header("Cache-Control: must-revalidate, post-check=0, pre-check=0"); 277 header("Cache-Control: private", false); 278 header("Content-Type: text/csv"); 279 header("Content-Disposition: attachment; filename=\"$filename\";" ); 280 header("Content-Transfer-Encoding: binary"); 281 } 282 283 private $dl_file = ''; 284 285 /** 286 * @return string 287 */ 288 public function getDownloadFileName(): string { 289 return $this->dl_file; 290 } 291 292 /** 293 * @param string $dl_file 294 */ 295 public function setDownloadFileName( string $dl_file ): void { 296 $this->dl_file = $dl_file; 297 } 279 /** 280 * Sends the headers 281 * @return void 282 */ 283 public function sendDownloadHeadersForCSVDownload($inp_filename = '') 284 { 285 if (headers_sent()) { 286 return; 287 } 288 289 if (empty($inp_filename)) { 290 $inp_filename = $this->getDownloadFileName(); 291 } 292 293 if (empty($inp_filename)) { 294 $file_prefix = basename(ORBISIUS_SEO_EDITOR_BASE_PLUGIN); 295 $file_prefix = str_replace('.php', '', $file_prefix); 296 $file_prefix = sanitize_title($file_prefix); 297 $file_prefix = str_replace('-', '_', $file_prefix); 298 $file_prefix .= '_'; 299 $filename = $file_prefix . date('Ymd') . '_' . date('His') . '.csv'; 300 } else { 301 $filename = basename($inp_filename); 302 } 303 304 // Output CSV-specific headers 305 // https://stackoverflow.com/questions/393647/response-content-type-as-csv 306 header("Pragma: public"); 307 header('X-Content-Type-Options: nosniff'); 308 header("Expires: 0"); 309 header("Cache-Control: must-revalidate, post-check=0, pre-check=0"); 310 header("Cache-Control: private", false); 311 header("Content-Type: text/csv"); 312 header("Content-Disposition: attachment; filename=\"$filename\";"); 313 header("Content-Transfer-Encoding: binary"); 314 } 315 316 private $dl_file = ''; 317 318 /** 319 * @return string 320 */ 321 public function getDownloadFileName(): string 322 { 323 return $this->dl_file; 324 } 325 326 /** 327 * @param string $dl_file 328 */ 329 public function setDownloadFileName(string $dl_file): void 330 { 331 $this->dl_file = $dl_file; 332 } 298 333 } -
orbisius-seo-editor/trunk/lib/util.php
r2838198 r3136940 631 631 $msg_esc = esc_html($msg); 632 632 $msg_esc = str_ireplace('__ESC_BR__', '<br/>', $msg_esc); 633 $str = "<div id='$id-notice' class='$cls' style='$inline_css'><strong> $msg_esc</strong></div>";633 $str = "<div id='$id-notice' class='$cls' style='$inline_css'><strong><p>$msg_esc</p></strong></div>"; 634 634 635 635 return $str; -
orbisius-seo-editor/trunk/orbisius-seo-editor.php
r2865670 r3136940 4 4 Plugin URI: https://orbisius.com/products/wordpress-plugins/orbisius-seo-editor/ 5 5 Description: Allows you to bulk update meta titles, descriptions summary of pages, posts and WooCommerce products 6 Version: 1.0. 26 Version: 1.0.3 7 7 Author: Svetoslav Marinov (Slavi) 8 8 Author URI: https://orbisius.com -
orbisius-seo-editor/trunk/readme.txt
r2865670 r3136940 4 4 Tags: seo,seo plugin,seo,bulk edit seo,wpseo,yoast seo,yoast,autodescription,rank-math,twitter card,meta title, meta description, woocommerce seo,wc seo,seo plugin,WordPress SEO,SEO Rank Math import, WordPress SEO import 5 5 Requires at least: 5.0 6 Tested up to: 6. 16 Tested up to: 6.6 7 7 Requires PHP: 5.6 8 Stable tag: 1.0. 28 Stable tag: 1.0.3 9 9 License: GPLv2 or later 10 10 … … 60 60 == Installation == 61 61 62 1. Unzip the package, and upload `orbisius-seo-editor` to the `/wp-content/plugins/` directory 63 2. Activate the plugin through the 'Plugins' menu in WordPress 62 1. Go to WP-Admin > Plugins > Add New 63 1. Upload the plugin's zip the package or search for SEO Editor 64 1. Activate the plugin through the 'Plugins' menu in WordPress 65 1. To go Tools > SEO Editor 64 66 65 67 == Frequently Asked Questions == … … 73 75 == Changelog == 74 76 77 = 1.0.3 = 78 * Tested with latest WP 79 * Fixed Deprecated: auto_detect_line_endings is deprecated 80 * Updated csv parsing buffer to 2k 81 * Skip the first 5 non-empty rows if they have X or fewer columns. Sometimes people enter content before the heading columns. 82 * Fixes 83 75 84 = 1.0.2 = 76 85 * Added focus keyword to the WordPress SEO
Note: See TracChangeset
for help on using the changeset viewer.