Fix serialized data broken due to editing MySQL database in a text editor? - wordpress

Background: I downloaded a *.sql backup of my WordPress site's database, and replaced all instances of the old database table prefix with a new one (e.g. from the default wp_ to something like asdfghjkl_).
I've just learnt that WordPress uses serialized PHP strings in the database, and what I did will have messed with the integrity of the serialized string lengths.
The thing is, I deleted the backup file just before I learnt about this (as my website was still functioning fine), and installed a number of plugins since. So, there's no way I can revert back, and I therefore would like to know two things:
How can I fix this, if at all possible?
What kind of problems could this cause?
(This article states that, a WordPress blog for instance, could lose its settings and widgets. But this doesn't seem to have happened to me as all the settings for my blog are still intact. But I have no clue as to what could be broken on the inside, or what issues it'd pose in the future. Hence this question.)

Visit this page: http://unserialize.onlinephpfunctions.com/
On that page you should see this sample serialized string: a:1:{s:4:"Test";s:17:"unserialize here!";}. Take a piece of it-- s:4:"Test";. That means "string", 4 characters, then the actual string. I am pretty sure that what you did caused the numeric character count to be out of sync with the string. Play with the tool on the site mentioned above and you will see that you get an error if you change "Test" to "Tes", for example.
What you need to do is get those character counts to match your new string. If you haven't corrupted any of the other encoding-- removed a colon or something-- that should fix the problem.

I came to this same problem after trying to change the domain from localhost to the real URL. After some searching I found the answer in Wordpress documentation:
https://codex.wordpress.org/Moving_WordPress
I will quote what is written there:
To avoid that serialization issue, you have three options:
Use the Better Search Replace or Velvet Blues Update URLs plugins if you can > access your Dashboard.
Use WP-CLI's search-replace if your hosting provider (or you) have installed WP-CLI.
Run a search and replace query manually on your database. Note: Only perform a search and replace on the wp_posts table.
I ended up using WP-CLI which is able to replace things in the database without breaking serialization: http://wp-cli.org/commands/search-replace/

I know this is an old question, but better late than never, I suppose. I ran into this problem recently, after inheriting a database that had had a find/replace executed on serialized data. After many hours of researching, I discovered that this was because the string counts were off. Unfortunately, there was so much data with lots of escaping and newlines and I didn't know how to count in some cases and I had so much data that I needed something automated.
Along the way, I stumbled across this question and Benubird's post helped put me on the right path. His example code did not work in production use on complex data, containing numerous special characters and HTML, with very deep levels of nesting, and it did not properly handle certain escaped characters and encoding. So I modified it a bit and spent countless hours working through additional bugs to get my version to "fix" the serialized data.
// do some DB query here
while($res = db_fetch($qry)){
$str = $res->data;
$sCount=1; // don't try to count manually, which can be inaccurate; let serialize do its thing
$newstring = unserialize($str);
if(!$newstring) {
preg_match_all('/s:([0-9]+):"(.*?)"(?=;)/su',$str,$m);
# preg_match_all("/s:([0-9]+):(\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\")(?=;)/u",$str,$m); // alternate: almost works but leave quotes in $m[2] output
# print_r($m); exit;
foreach($m[1] as $k => $len) {
/*** Possibly specific to my case: Spyropress Builder in WordPress ***/
$m_clean = str_replace('\"','"',$m[2][$k]); // convert escaped double quotes so that HTML will render properly
// if newline is present, it will output directly in the HTML
// nl2br won't work here (must find literally; not with double quotes!)
$m_clean = str_replace('\n', '<br />', $m_clean);
$m_clean = nl2br($m_clean); // but we DO need to convert actual newlines also
/*********************************************************************/
if($sCount){
$m_new = $m[0][$k].';'; // we must account for the missing semi-colon not captured in regex!
// NOTE: If we don't flush the buffers, things like <img src="http://whatever" can be replaced with <img src="//whatever" and break the serialize count!!!
ob_end_flush(); // not sure why this is necessary but cost me 5 hours!!
$m_ser = serialize($m_clean);
if($m_new != $m_ser) {
print "Replacing: $m_new\n";
print "With: $m_ser\n";
$str = str_replace($m_new, $m_ser, $str);
}
}
else{
$m_len = (strlen($m[2][$k]) - substr_count($m[2][$k],'\n'));
if($len != $m_len) {
$newstr='s:'.$m_len.':"'.$m[2][$k].'"';
echo "Replacing: {$m[0][$k]}\n";
echo "With: $newstr\n\n";
$str = str_replace($m_new, $newstr, $str);
}
}
}
print_r($str); // this is your FIXED serialized data!! Yay!
}
}
A little geeky explanation on my changes:
I found that trying to count with Benubird's code as a base was too inaccurate for large datasets, so I ended up just using serialize to be sure the count was accurate.
I avoided the try/catch because, in my case, the try would succeed but just returned an empty string. So, I check for empty data instead.
I tried numerous regex's but only a mod on Benubird's would accurately handle all cases. Specifically, I had to modify the part that checked for the ";" because it would match on CSS like "width:100%; height:25px;" and broke the output. So, I used a positive lookahead to only match when the ";" was outside of the set of double quotes.
My case had lots of newlines, HTML, and escaped double quotes, so I had to add a block to clean that up.
There were a couple of weird situations where data would be replaced incorrectly by the regex and then the serialize would count it incorrectly as well. I found NOTHING on any sites to help with this and finally thought it might be related to caching or something like that and tried flushing the output buffer (ob_end_flush()), which worked, thank goodness!
Hope this helps someone... Took me almost 20 hours including the research and dealing with weird issues! :)

This script (https://interconnectit.com/products/search-and-replace-for-wordpress-databases/) can help to update an sql database with proper URLs everywhere, without encountering serialized data issues, because it will update the "characters count" that could throw your URLs out of sync whenever serialized data occurs.
The steps would be:
if you already have imported a messed up database (widgets not
working, theme options not there, etc), just drop that database
using PhpMyAdmin. That is, remove everything on it. Then export and
have at hand an un-edited dump of the old database.
Now you have to import the (un-edited) old database into the
newly created one. You can do this via an import, or copying over
the db from PhpMyAdmin. Notice that so far, we haven't done any
search and replace yet; we just have an old database content and
structure into a new database with its own user and password. Your site will be probably unaccessible at this point.
Make sure you have your WordPress files freshly uploaded to the
proper folder on the server, and edit your wp-config.php to make it
connect with the new database.
Upload the script into a "secret" folder - just for security
reasons - at the same level than wp-admin, wp-content, and wp-includes. Do not forget to remove it all once the search and
replace have taken place, because you risk to offer your DB details
open to the whole internet.
Now point your browser to the secret folder, and use the script's fine
interface. It is very self-explanatory. Once used, we proceed to
completely remove it from the server.
This should have your database properly updated, without any serialized data issues around: the new URL will be set everywhere, and serialized data characters counts will be accordingly updated.
Widgets will be passed over, and theme settings as well - two of the typical places that use serialized data in WordPress.
Done and tested solution!

If the error is due to the length of the strings being incorrect (something I have seen frequently), then you should be able to adapt this script to fix it:
foreach($strings as $key => $str)
{
try {
unserialize($str);
} catch(exception $e) {
preg_match_all('#s:([0-9]+):"([^;]+)"#',$str,$m);
foreach($m[1] as $k => $len) {
if($len != strlen($m[2][$k])) {
$newstr='s:'.strlen($m[2][$k]).':"'.$m[2][$k].'"';
echo "len mismatch: {$m[0][$k]}\n";
echo "should be: $newstr\n\n";
$strings[$key] = str_replace($m[0][$k], $newstr, $str);
}
}
}
}

I personally don't like working in PHP, or placing my DB credentials in an public file. I created a ruby script to fix serializations that you can run locally:
https://github.com/wsizoo/wordpress-fix-serialization
Context Edit:
I approached fixing serialization by first identifying serialization via regex, and then recalculating the byte size of the contained data string.
$content_to_fix.gsub!(/s:([0-9]+):\"((.|\n)*?)\";/) {"s:#{$2.bytesize}:\"#{$2}\";"}
I then update the specified data via an escaped sql update query.
escaped_fix_content = client.escape($fixed_content)
query = client.query("UPDATE #{$table} SET #{$column} = '#{escaped_fix_content}' WHERE #{$column_identifier} LIKE '#{$column_identifier_value}'")

Related

SilverStripe 4.1 Email->addAttachment()?

I have a contact form that accepts a file input, I'd like to attach the file to the email that gets sent from the form.
Looking at the API reference isn't really helping, it states that the function expects a filepath with no clarification on anything beyond that.
The submit action will save a record of the into the database and this works correctly, something like:
$submission = MyDataObject::create();
$form->saveInto($submission);
$submission->write();
an Email object then gets created and sent. Both of these are functioning and working as expected.
Trying to attach the File I've tried:
$email->addAttachemnt($submission->MyFile()->Link());
which is the closest I can get to getting a filepath for the document. Dumping and pasting the resulting filepath being output by that call will download the form but that line throws an error and can't seem to locate the file.
I suspect that I'm misunderstanding what's supposed to be given to the function, clarification would be very much appreciated.
P.S. I don't currently have access to the code, I'm looking for some clarification on the function itself not an exact answer :).
In SilverStripe 4 the assets are abstracted away, so you can't guarantee that the file exists on your webserver. It generally will, but it could equally exist on a CDN somewhere for example.
When you handle files in SilverStripe 4 you should always use the contents of the file and whatever other metadata you have available, rather than relying on filesystem calls to load it.
This is how the silverstripe/userforms module attaches files to emails:
/** #var SilverStripe\Control\Email\Email $email */
$email->addAttachmentFromData(
$file->getString(), // stream of file contents
$file->getFilename(), // original filename
$file->getMimeType() // mime type
);
I would try $email->addAttachment($submission->MyFile()->Filename); If it doesn't work, you may need to prepend $_SERVER['DOCUMENT_ROOT'] to the filename.
$email->addAttachment($_SERVER['DOCUMENT_ROOT'] . $submission->MyFile()->Filename);

Typo3 Extbase Repository->findAll() returns empty

I just can't findAll() make return anything even though I am able to access a specific record by findByUid().
I have taken note (and tried to workaround / set up) of the typoscript solution and the record storage page bug without any success.
I am using a dummy extension code made by the extension builder in TYPO3 (current version) for your convenience. I have tested with data manually added through the TYPO3 config ui.
Any help would be much appreciated.
All the best and thanks in advance
Mario
For 99.9 % you didn't set your storagePid properly, it has to be the PID of the page where your records are stored. Effect: findAll() uses this PID to filter the records, while findByUid(uid) ignores PID (it searches by UID wherever record is stored.
Go to the main page > Templates and make sure that you included TypoScript from your new ext, then go to constant editor and set proper PID of page with your records. Clear whole cache at the end!
Also you can debug your SQL statement like in this answer, most probably at the end of statement you'll see something like AND your_table_name.pid IN (0) which definitely means that you didn't set storagePid or you didn't clear the cache.

CMS links on frontend not converting ie href=[sitetree_link_id=xx]

An issue has been noticed on one of our old sites running 2.4 where when the user creates a link in the CMS content, selecting an existing page to link to, the link is not being converted to the actual URL on the front end and all links are coming through in the format of <a href="[sitetree_link_id=12]">
What would be causing this and how do I fix it?
The tag looks like it's being set incorrectly. It should be [sitetree_link id=12], not [sitetree_link_id=12].
We later added support to the parser for [sitetree_link,id=12] so that links didn't need to contain spaces, but I can't recall if that's in 2.4 or only 3.0+.
Can you confirm that your WYSIWYG insertion is putting in that errant _? If so, you might want to checkout the handleaction_insert function in tiny_mce_imporvements.js to confirm that it has a line like so:
case 'internal':
href = '[sitetree_link id=' + this.elements.internal.value + ']';
If the inserted links don't actually have the errant _ but they aren't being parsed, then try checking your sapphire/_config.php file for this:
ShortcodeParser::get('default')->register('sitetree_link', array('SiteTree', 'link_shortcode_handler'));
If your site makes changes to the ShortcodeParser at all you might have inadvertently turned off sitetree_link support.
If all of that looks in order, perhaps the ShortcodeParser isn't being called for some reason. In HTMLText::forTemplate(), put a debug statement (I like die("I got here!");) to confirm that HTMLText::forTemplate() is actually getting called. If it's not, you might need to manually call it in some pre-processing of your Content variable. Instead of this:
$content = $this->Content;
Do this:
$content = $this->obj('Content')->forTemplate();
I hope that one of those answers help. Either way, it would be great if you could post back, so we could isolate what caused this. It might help us make the API easier to use in SilverStripe 3.1.

Bought Magento Extension - weird encryption worried it's malicious

I just bought an extension for Magento, once I checked the files I saw that some are encrpyted in a really weird way, never saw that before and from some of the function names and includes it looks like at some places it gets content from external files...
Anyways, i would like to be able to decode this to see if the extension does anything malicious or not. I paid $300 for it and I'm a little bit worried to put that in my shop if I don't know that the extension is clean.
The code in the encrypted files looks like this:
if (isset ($††††††††††††††††††††††->Items->Item)){if (is_array($††††††††††††††††††††††->Items->Item)){$†††††††††††††††††††††††=$††††††††††††††††††††††->Items->Item;}else {$†††††††††††††††††††††††=array ($††††††††††††††††††††††->Items->Item);}}else {return array (0,0);}self::_getExistingsProducts(chr(97).chr(109).chr(97).chr(122).chr(111).chr(110).chr(105).chr(109).chr(112).chr(111).chr(114).chr(116).chr(98).chr(111).chr(111).chr(107).chr(115));$††††††††††††††††††††††††=array ();$†††††††††††††††††††††††††=array ();foreach ($††††††††††††††††††††††† as $††††††††††††††††††††††††††=>$††††††††††††††){$†††††††††††††††††††††††††††=array (chr(97).chr(115).chr(105).chr(110)=>$††††††††††††††->ASIN,chr(115).chr(107).chr(117)=>self::_getProductSku($††††[chr(115).chr(107).chr(117)],$††††††††††††††),);if (in_array($†††††††††††††††††††††††††††[chr(97).chr(115).chr(105).chr(110)],$††††††††††††††††††††††††)|| in_array($†††††††††††††††††††††††††††[chr(115).chr(107).chr(117)],$†††††††††††††††††††††††††)|| self::_existsProduct($†††††††††††††††††††††††††††,true)){$††††††††††††††††††++ ;continue ;}try {$††††††††††††††††††††††††[]=$†††††††††††††††††††††††††††[chr(97).chr(115).chr(105).chr(110)];$†††††††††††††††††††††††††[]=$†††††††††††††††††††††††††††[chr(115).chr(107).chr(117)];$††††††††††††††††††††††††††††[]=$††††[chr(108).chr(111).chr(99).chr(97).chr(108)];$††††††††††††††††††††††††††††[]=$†††††††††††††††††††††††††††[chr(97).chr(115).chr(105).chr(110)];$††††††††††††††††††††††††††††[]=$†††††††††††††††††††††††††††[chr(115).chr(107).chr(117)];$††††††††††††††††††††††††††††[]=self::getProductCategories($††††††††††††††,true);$††††††††††††††††††††††††††††[]=isset ($††††††††††††††->ItemAttributes->Title)?$††††††††††††††->ItemAttributes->Title:$††††††††††††††->ASIN;$††††††††††††††††††††††††††††[]=self::_getImagesCount($††††††††††††††);$††††††††††††††††††††††††††††[]=$††††††††††††††->DetailPageURL;list ($†††††††††††††††††††††††††††††,$††††††††††††††††††††††††††††††,$†††††††††††††††††††††††††††††††,$††††††††††††††††††††††††††††††††,$†††††††††††††††††††††††††††††††††)=WP_Amazonimportproducts_Model_Amazonoffer::getOfferInfo($††††††††††††††,$††††[chr(100).chr(101).chr(102).chr(97).chr(117).chr(108).chr(116).chr(80).chr(114).chr(105).chr(99).chr(101)]);$††††††††††††††††††††††††††††[]=$†††††††††††††††††††††††††††††;$††††††††††††††††††††††††††††[]=floatval($††††[chr(100).chr(101).chr(102).chr(97).chr(117).chr(108).chr(116).chr(80).chr(114).chr(105).chr(99).chr(101).chr(80).chr(108).chr(117).chr(115).chr(80).chr(101).chr(114).chr(99).chr(101).chr(110).chr(116)]);$††††††††††††††††††††††††††††[]=floatval($††††[chr(100).chr(101).chr(102).chr(97).chr(117).chr(108).chr(116).chr(80).chr(114).chr(105).chr(99).chr(101).chr(80).chr(108).chr(117).chr(115).chr(85).chr(110).chr(105).chr(116)]);$††††††††††††††††††††††††††††[]=$††††††††††††††††††††††††††††††;$††††††††††††††††††††††††††††[]=$†††††††††††††††††††††††††††††††;$††††††††††††††††††††††††††††[]=$††††††††††††††††††††††††††††††††;$††††††††††††††††††††††††††††[]=$†††††††††††††††††††††††††††††††††;$††††††††††††††††††††††††††††[]=self::_getProductDetail($††††††††††††††);$††††††††††††††††††††††††††††[]=serialize($††††††††††††††);$††††††††††††††††††††††††††††[]=$††††††††††††††††††††;$††††††††††††††††††††††††††††[]=0;$††††††††††††††††††††††††††††[]=date(chr(89).chr(45).chr(109).chr(45).chr(100).chr(32).chr(72).chr(58).chr(105).chr(58).chr(115));$††††††††††††††††††††††††††††[]=$††††[chr(100).chr(101).chr(102).chr(97).chr(117).chr(108).chr(116).chr(80).chr(114).chr(105).chr(99).chr(101)];$††††††††††††††††††††††††††††[]=$††††[chr(100).chr(101).chr(102).chr(97).chr(117).chr(108).chr(116).chr(67).chr(111).chr(110).chr(100).chr(105).chr(116).chr(105).chr(111).chr(110)];$†††††††††††++ ;}catch (Exception $†††††††††††††††††){Mage::helper(chr(97).chr(109).chr(97).chr(122).chr(111).chr(110).chr(105).chr(109).chr(112).chr(111).chr(114).chr(116).chr(112).chr(114).chr(111).chr(100).chr(117).chr(99).chr(116).chr(115))->{"\x6c\x6f\x67"}($†††††††††††††††††->{"\x67\x65\x74\x4d\x65\x73\x73\x61\x67\x65"}(),chr(71).chr(101).chr(116).chr(32).chr(66).chr(111).chr(111).chr(107).chr(32).chr(73).chr(110).chr(102).chr(111).chr(32).chr(102).chr(114).chr(111).chr(109).chr(32).chr(65).chr(109).chr(97).chr(122).chr(111).chr(110).chr(32).chr(65).chr(80).chr(73).chr(44).chr(32).chr(82).chr(101).chr(115).chr(112).chr(111).chr(110).chr(115).chr(101).chr(32).chr(69).chr(114).chr(114).chr(111).chr(114),chr(105).chr(109).chr(112).chr(111).chr(114).chr(116));}}
This is only a small excerpt from one of the encrypted files, and I'm looking for a way to display the code unencoded so I can check it. Sorry for the format, I am still trying to figure out how to format huge blocks of code correctly, maybe someone is nice enough to edit it for me?
Well, you will have to work with find-and-replace tools. Firstly you get the result of a php function call (php -r "echo chr(107)") and then replace all occurrences of chr(107) to the result ("k"). Than replace "\x67\x65\x74\x4d\x65\x73\x73\x61\x67\x65" (php -r 'echo "\x67\x65\x74\x4d\x65\x73\x73\x61\x67\x65";'). Than replace all †-variables in methods to human-readable names.

Migration to new domain

I'm working on a drupal 6 site at mydomain.com/drupalsite, and the designer has put a lot of hardcoded image paths in there, for instance a custom img folder in mydomain.com/drupalsite/img. So a lot of the site uses links to /drupalsite/img/myimg1.png.
Here's the problem -- the site is eventually moving to finaldomain.com, via pointing finaldomain.com to mydomain.com/drupalsite. So now paths like /drupalsite/img/myimg1.png will resolve to finaldomain.com/drupalsite/img/myimg1.png, instead of what should be finaldomain.com/img/myimg1.png. The finaldomain.com site has to point to that subdirectory so it hits the index.php.
My first instinct is to use an .htaccess file to replace the /drupalsite with "", but I've tried about a dozen different solutions and they haven't worked. My hack of a solution was to use some ln -s links but I really don't like it :) tia
Andrew
The best method, in hindsight, is to ensure folks use Drupal functions to make all links:
l (that's the letter L)
drupal_get_path()
base_path()
The l() function takes care of base path worries, and provides a systematic way to define your URL's. Using things like theme_image() plus the l() function are a sure win. Use the second and third functions above if you have to write your own <a> tags and for use inside theme functions like theme_image().
But for your current situation:
As regards Andy's solution, it would be better if you could limit your changes to certain database fields where you know the links are located.
So write a query to select all those fields (e.g. all body fields):
$my_query = db_query("SELECT vid, body FROM {node_revisions}");
This, for example, will get you every body field in the node_revisions table, so even your old revisions would have proper links.
Then run through those results, do str_replace() on each, and then write the changes back:
while($node = db_fetch_object($my_query)) {
$new_body = str_replace('what you have', 'what you want', $node->body);
db_query("UPDATE {node_revisions} SET body = '%s' WHERE vid = %d", $new_body, $node->vid);
}
I'd obviously try it on one record first, to make sure your code behaves as intended (just add a WHERE vid = 5, for example, to narrow it down to one revision). Furthermore, I haven't taken advantage of node_load and node_save, which are better for loading and saving nodes properly, so as to provide a more general solution (for you to replace text in blocks, etc.).
For your files, I'd suggest a good ol' sed command, by running something like the following from within your "sites" folder:
find ./ -type f -exec sed -i ’s/string1/string2/’ {} \;
Nabbed that from here, so take a look on that site for more explanation. If you're going to be working with paths, you'll either need to escape the / of the paths in your version of the sed command, or use a different sed separator (i.e. you can write s#string1#string2# instead of s/string1/string2/, so you could write s#/drupalsite/img/#/img# instead of s/\/drupalsite\/img\//\/img/ :-). See also Drupal handbook page for quick sed commands: http://drupal.org/node/128513.
A bit of a mess, which is why I try to enforce using the proper functions up front. But this is difficult if you want themers to create Drupal content but you don't want to give them access to the "PHP Filter" input format, or they simply don't know PHP. Proper Drupal theming, at any point past basic HTML/CSS work, requires a knowledge of PHP and Drupal's theme-related functions.
I've done this before by taking a full database dump, opening it in a text editor, and doing a global search and replace on the paths. Then on the new host, load the modified dump file, and it will have the correct paths in.
You could try Pathologic, it should be able to correct paths like this.

Resources