Half-Elf on Tech

Thoughts From a Professional Lesbian

Tag: wordpress

  • Replacing the W in Your Admin Bar

    Replacing the W in Your Admin Bar

    This is a part of ‘white labeling’, which basically means rebranding.

    When you have a website that is not used by people who really need to mess with WordPress, nor learn all about it (because you properly manage your own documentation for your writers), then that W in your admin toolbar is a bit odd, to say the least.

    This doesn’t mean I don’t want my editors to know what WordPress is, we have a whole about page, and the powered-by remains everywhere in the admin pages, but that logo…

    Well anyway, I decided to nuke it.

    Remove What You Don’t Need

    First I made a function that removes everything I don’t need:

    function cleanup_admin_bar(): void {
    	global $wp_admin_bar;
    
    	// Remove customizer link
    	$wp_admin_bar->remove_menu( 'customize' );
    
    	// Remove WP Menu things we don't need.
    	$wp_admin_bar->remove_menu( 'contribute' );
    	$wp_admin_bar->remove_menu( 'wporg' );
    	$wp_admin_bar->remove_menu( 'learn' );
    	$wp_admin_bar->remove_menu( 'support-forums' );
    	$wp_admin_bar->remove_menu( 'feedback' );
    
    	// Remove comments
    	$wp_admin_bar->remove_node( 'comments' );
    }
    
    add_action( 'wp_before_admin_bar_render','cleanup_admin_bar' );
    

    I also removed the comments node and the customizer because this site doesn’t use comments, and also how many times am I going to that Customizer anyway? Never. But the number of times I miss-click on my tablet? A lot.

    But you may notice I did not delete everything. That’s on purpose.

    Make Your New Nodes

    Instead of recreating everything, I reused some things!

    function filter_admin_bar( $wp_admin_bar ): void {
    	// Remove Howdy and Name, only use avatar.
    	$my_account = $wp_admin_bar->get_node( 'my-account' );
    
    	if ( isset( $my_account->title ) ) {
    		preg_match( '/<img.*?>/', $my_account->title, $matches );
    
    		$title = ( isset( $matches[0] ) ) ? $matches[0] : '<img src="fallback/images/avatar.png" alt="SITE NAME" class="avatar avatar-26 photo" height="26" width="26" />';
    
    		$wp_admin_bar->add_node(
    			array(
    				'id'    => 'my-account',
    				'title' => $title,
    			)
    		);
    	}
    
    	// Customize the Logo
    	$wp_logo = $wp_admin_bar->get_node( 'wp-logo' );
    	if ( isset( $wp_logo->title ) ) {
    		$logo = file_get_contents( '/images/site-logo.svg' );
    		$wp_admin_bar->add_node(
    			array(
    				'id'     => 'wp-logo',
    				'title'  => '<span class="my-site-icon" role="img">' . $logo . '</span>',
    				'parent' => null,
    				'href'   => '/wp-admin/admin.php?page=my-site',
    				'group'  => null,
    				'meta'   => array(
    					'menu_title' => 'About SITE',
    				),
    			),
    		);
    		$wp_admin_bar->add_node(
    			array(
    				'parent' => 'wp-logo',
    				'id'     => 'about',
    				'title'  => __( 'About SITE' ),
    				'href'   => '/about/',
    			)
    		);
    		$wp_admin_bar->add_node(
    			array(
    				'parent' => 'wp-logo-external',
    				'id'     => 'documentation',
    				'title'  => __( 'Documentation' ),
    				'href'   => 'https://docs.example.com/',
    			)
    		);
    		$wp_admin_bar->add_node(
    			array(
    				'parent' => 'wp-logo-external',
    				'id'     => 'slack',
    				'title'  => __( 'Slack' ),
    				'href'   => 'https://example.slack.com/',
    			)
    		);
    		$wp_admin_bar->add_node(
    			array(
    				'parent' => 'wp-logo-external',
    				'id'     => 'validation',
    				'title'  => __( 'Data Validation' ),
    				'href'   => '/wp-admin/admin.php?page=data_check',
    			)
    		);
    		$wp_admin_bar->add_node(
    			array(
    				'parent' => 'wp-logo-external',
    				'id'     => 'monitors',
    				'title'  => __( 'Monitors' ),
    				'href'   => '/wp-admin/admin.php?page=monitor_check',
    			)
    		);
    	}
    }
    
    add_filter( 'admin_bar_menu', array( $this, 'filter_admin_bar' ), PHP_INT_MAX );
    

    I replaced the default ‘about’ with my site’s about URL. I replaced the documentation node with my own. Everything else is new.

    Now the image… I have an SVG of our logo, and by making my span class named my-site-icon, I was able to spin up some simple CSS:

    #wpadminbar span.my-site-icon svg {
    	width: 25px;
    	height: 25px;
    }
    

    And there you are.

    Result

    What’s it look like?

    Screenshot of an alternative dropdown of what was the W logo into something customized.

    All those links are to our tools or documentation.

  • Editor Sidebar Madness and Gutenberg

    Editor Sidebar Madness and Gutenberg

    Way back when WP was simple and adding a sidebar to the editor was a simple metabox, I had a very straightforward setup with a box that, on page load, would tell you if the data in the post matched the remote API, and if not would tell you what to update.

    My plan was to have it update on refresh, and then auto-correct if you press a button (because sometimes the API would be wrong, or grab the wrong account — loose searching on people’s names is always rough). But my plan was ripped asunder by this new editor thingy, Gutenberg.

    I quickly ported over my simple solution and added a note “This does not refresh on page save, sorry.” and moved on.

    Years later brings us to 2024 and November being my ‘funenployment’ month, where I worked on little things to keep myself sharp before I started at AwesomeMotive. Most of the work was fixing security issues, moving the plugin into the theme so there was less to manage, modernizing processes, upgrading libraries, and so on.

    But one of those things was also making a real Gutenbergized sidebar that autoupdates (mostly).

    What Are We Doing?

    On LezWatch.TV, we collect actor information that is public and use it to generate our pages. So if you wanted to add in an actor, you put in their name, a bio, an image, and then all this extra data like websites, social media, birthdates, and so on. WikiData actually uses us to help determine gender and sexuality, so we pride ourselves on being accurate and regularly updated.

    In return, we use WikiData to help ensure we’re showing the data for the right person! We do that via a simple search based on either their WikiData ID (QID), IMDb ID, or their name. The last one is pretty loose since actors can have the same name now (oh for the days when SAG didn’t allow that…). We use the QID to override the search in cases where it grabs the wrong person.

    I built a CLI command that, once a week, checks actors for data validity. It makes sure the IMDb IDs and socials are formatted properly, it makes sure the dates are valid, and it pings WikiData to make sure the birth/death etc data is also correct.

    With that already in place, all I needed was to call it.

    You Need an API

    The first thing you need to know about this, is that Gutenberg uses the JSON API to pull in data. You can have it pull in everything by custom post meta, but as I already have a CLI tool run by cron to generate that information, making a custom API call was actually going to be faster.

    I went ahead and made it work in a few different ways (you can call it by IMDb ID, post ID, QID, and the slug) because I planned for the future. But really all any of them are doing is a search like this:

    	/**
    	 * Get Wikidata by Post ID
    	 *
    	 * @param int $post_id
    	 * @return array
    	 */
    	private function get_wikidata_by_post_id( $post_id ): array {
    		if ( get_post_type( $post_id ) !== 'post_type_actors' ) {
    			return array(
    				'error' => 'Invalid post ID',
    			);
    		}
    
    		$wikidata = ( new Debug_Actors() )->check_actors_wikidata( $post_id );
    
    		return array( $wikidata );
    	}
    

    The return array is a list of the data we check for, and it either is a string of ‘matches’ /true, or it’s an array with WikiData’s value and our value.

    Making a Sidebar

    Since we have our API already, we can jump to making a sidebar. Traditionally in Gutenberg, we make a sidebar panel for the block we’re adding in. If you want a custom panel, you can add in one with an icon on the Publish Bar:

    A screenshot of the Gutenberg Publish bar, with the Jetpack and YoastSEO icons

    While that’s great and all, I wanted this to be on the side by default for the actor, like Categories and Tags. Since YoastSEO (among others) can do this, I knew it had to be possible:

    Screenshot of the Gutenberg Sidebar, with YoastSEO's custom example.

    But when I started to search around, all anyone told me was how I had to use a block to make that show.

    I knew it was bullshit.

    Making a Sidebar – The Basics

    The secret sauce I was looking for is decidedly simple.

    	const MetadataPanel = () => (
    		<PluginDocumentSettingPanel
    			name="lwtv-wikidata-panel"
    			title="WikiData Checker"
    			className="lwtv-wikidata-panel"
    		>
    			<PanelRow>
    				<div>
    					[PANEL STUFF HERE]
    				</div>
    			</PanelRow>
    		</PluginDocumentSettingPanel>
    	);
    

    I knew about PanelRow but finding PluginDocumentSettingPanel took me far longer than it should have! The documentation doesn’t actually tell you ‘You can use this to make a panel on the Document settings!’ but it is obvious once you’ve done it.

    Making it Refresh

    This is a pared down version of the code, which I will link to at the end.

    The short and simple way is I’m using UseEffect to refresh:

    useEffect(() => {
    		if (
    			postId &&
    			postType === 'post_type_actors' &&
    			postStatus !== 'auto-draft'
    		) {
    			const fetchData = async () => {
    				setIsLoading(true);
    				try {
    					const response = await fetch(
    						`${siteURL}/wp-json/lwtv/v1/wikidata/${postId}`
    					);
    					if (!response.ok) {
    						throw new Error(
    							`HTTP error! status: ${response.status}`
    						);
    					}
    					const data = await response.json();
    					setApiData(data);
    					setError(null);
    				} catch (err) {
    					setError(err.message);
    					setApiData(null);
    				} finally {
    					setIsLoading(false);
    				}
    			};
    			fetchData();
    		}
    	}, [postId, postType, postStatus, siteURL, refreshCounter]);
    

    The reason I’m checking post type and status, is that I don’t want to try and run this if it’s not an actor, and if it’s not at least a real draft.

    The constants are as follows:

    	const [apiData, setApiData] = useState(null);
    	const [isLoading, setIsLoading] = useState(true);
    	const [error, setError] = useState(null);
    

    Right below this I have a second check:

    	if (postType !== 'post_type_actors') {
    		return null;
    	}
    

    That simply prevents the rest of the code from trying to run. You have to have it after the UseEffect because JS is weird and does things in an order. If you have a return before it, it fails to pass a lint (and I enforce linting on this project).

    How it works is on page load of an auto-draft, it tells you to save the post before it will check. As soon as you do save the post (with a title), it refreshes and tells you what it found, speeding up initial data entry!

    But then there’s the issue of refreshing on demand.

    HeartBeat Flatline – Use a Button

    I did, at one point, have a functioning heartbeat checker. That can get pretty expensive and it calls the API too many times if you leave a window open. Instead, I made a button that uses a constant:

    const [refreshCounter, setRefreshCounter] = useState(0);
    

    and a handler:

    	const handleRefresh = () => {
    		setRefreshCounter((prevCounter) => prevCounter + 1);
    	};
    

    Then the button itself:

    &lt;Button
    	variant="secondary"
    	onClick={handleRefresh}
    	isBusy={isLoading}
    >
    	{isLoading ? 'Refreshing...' : 'Refresh'}
    &lt;/Button>
    

    Works like a champ.

    Output the Data

    The data output is the interesting bit, because I’m still not fully satisfied with how it looks.

    I set up a filter to process the raw data:

    	const filteredPersonData = (personData) => {
    		const filteredEntries = Object.entries(personData).filter(
    			([key, value]) => {
    				const lowerCaseValue = String(value).toLowerCase();
    				return (
    					lowerCaseValue !== 'match' &&
    					lowerCaseValue !== 'n/a' &&
    					!['wikidata', 'id', 'name'].includes(key.toLowerCase())
    				);
    			}
    		);
    		return Object.fromEntries(filteredEntries);
    	};
    

    The API returns the WikiData ID, the post ID, and the name, none of which need to be checked here, so I remove them. Otherwise it capitalizes things so they look grown up.

    Then there’s a massive amount of code in the panel itself:

    <div>
    	{isLoading && <Spinner />}
    	{error && <p>Error: {error}</p>}
    	{!isLoading && !error && apiData && (
    		<>
    			{apiData.map((item) => {
    				const [key, personData] = Object.entries(item)[0];
    				const filteredData = filteredPersonData(personData);
    				return (
    					<div key={key}>
    						<h3>{personData.name}</h3>
    						{Object.keys(filteredData).length ===
    						0 ? (
    							<p>[All data matches]</p>
    						) : (
    							<div>
    								{Object.entries( filteredData ).map(([subKey, value]) => (
    									<div key={subKey}>
    										<h4>{subKey}</h4>
    										{value && (
    											<ul>
    												{Object.entries( value ).map( ([ innerKey, innerValue, ]) => (
    													<li key={ innerKey }>
    														<strong>{innerKey}</strong>:{' '} <code>{innerValue || 'empty'}</code>
    													</li>
    												)
    										)}
    											</ul>
    										)}
    										{!value && 'empty'}
    									</div>
    								))}
    							</div>
    						)}
    					</div> );
    			})}
    		</>
    	)}
    
    	{!isLoading && !error && !apiData && (
    		<p>No data found for this post.</p>
    	)}
    	<Button />
    </div>
    

    <Spinner /> is from '@wordpress/components' and is a default component.

    Now, innerKey is actually not a simple output. I wanted to capitalize the first letter and unlike PHP, there’s no ucfirst() function, so it looks like this:

    {innerKey .charAt( 0 ).toUpperCase() + innerKey.slice( 1 )}

    Sometimes JavaScript makes me want to drink.

    The Whole Code

    You can find the whole block, with some extra bits I didn’t mention but I do for quality of life, on our GitHub repo for LezWatch.TV. We use the @wordpress/scripts tooling to generate the blocks.

    The source code is located in folders within /src/ – that’s where most (if not all) of your work will happen. Each new block gets a folder and in each folder there must be a block.json file that stores all the metadata. Read Metadata in block.json if this is your first rodeo.

    The blocks will automagically build anytime anyone runs npm run build from the main folder. You can also run npm run build from the blocks folder.

    All JS and CSS from blocks defined in blocks/*/block.json get pushed to the blocks/build/ folder via the build process. PHP scans this directory and registers blocks in php/class-blocks.php. The overall code is called from the /blocks/src/blocks.php file.

    The build subfolders are NOT stored in Git, because they’re not needed to be. We run the build via actions on deploy.

    What It Looks Like

    Gif showing how it auto-loads the data on save.

    One of the things I want to do is have a way to say “use WikiData” or “use ours” to fill in each individual data point. Sadly sometimes it gets confused and uses the wrong person (there’s a Katherine with an E Hepburn!) so we do have a QID override, but even so there can be incorrect data.

    WikiData often lists socials and websites that are defunct. Mostly that’s X these days.

    Takeaways

    It’s a little frustrating that I either have to do a complex ‘normal’ custom meta box with a lot of extra JS, or make an API. Since I already had the API, it’s no big, but sometimes I wish Gutenberg was a little more obvious with refreshing.

    Also finding the right component to use for the sidebar panel was absolutely maddening. Every single document was about doing it with a block, and we weren’t adding blocks.

    Finally, errors in Javascript remain the worst. Because I’m compiling code for Gutenberg, I have to hunt down the likely culprit, which is hard when you’re still newish to the code! Thankfully, JJ from XWP was an angel and taught me tons in my 2 years there. I adore her.

  • Open for Employment – No Longer!

    Open for Employment – No Longer!

    As of Dec 2, 2024, I am employed with AwesomeMotive! I am no longer in need of a new gig. I am leaving this post up in case someone does want to pay me to work on LezWatch.TV and make bagels all day.


    As of 31 October 2024, my engagement with XWP will end. I am incredibly thankful for the time I spent with them and the trust they placed in me. Don’t get me wrong, it sucks, but the world just works like this sometimes.

    What am I looking for?

    Honestly as much as I’d love someone to pay me to just work on LezWatch.TV and make bagels all day, it’s pretty unlikely (though if you do…).

    What I’m looking for is a full time job where I get to make cool things, get a fair paycheck that allows me to save to buy a house, and provides enough vacation time that I’m not spending all of it on Jewish Holidays and can actually take a trip now and then.

    What do I want to do?

    This is likely a tech stack question. I can do WordPress, I’m very good at it, but I also know Hugo and can pick up other stacks pretty quickly. I’ve done full stack work before (server birth to death), worked in automation, and myriad other platforms like MediaWiki, ZenPhoto, and more. I’m game for learning any CMS.

    Would I really still work in WordPress?

    I would.

    Look, I know there’s a lot of volatility in the WordPress world but honestly with that in mind, you need someone like me! Why? Because I know WordPress plugins! If something happens and you can’t access .org, I’m your girl. I know how to scan plugins (and themes) for backdoors and bad code, as well as write the good stuff. I know risk assessment and management, which means I can help you when plugin ownership is in doubt.

    I also know a lot of backstory to a lot of development shops and how they treat people. What? You knew I took notes about plugin devs!

    There are millions of WordPress sites out there. They still need devs.

    Would I leave WordPress for anything else?

    Absolutely! Nothing against WordPress (or the current state of affairs), but my love for it is not absolute. It’s tempered in reality. WordPress is not the perfect solution for everyone, after all. I’m game to learn new things, to integrate, to test, and to break things.

    And if you’re transitioning a site to or away from WordPress? Hey! I’m uniquely positioned to be able to tell you exactly what that code was doing and, in most cases, why!

    Would I work for Automattic?

    No. That ship sailed about 15 years ago. I interviewed pretty much around now back then, and in talking with Matt directly we both agreed I would be a bad fit. No harm, no foul. I think that was the right choice, all this time later, and I have no regrets.

    Would I go back into Hosting?

    Sure. I liked that work. It’s fun, challenging, and I learned a lot of new platforms and specifics. I got to play with servers and it gave me a deeper understanding in how to approach asking a host for help. Bonus? I know devs, so I can help debug your code on servers!

    (If DreamHost calls me up right after this post, I would absolutely talk with them about opportunities without a second thought!)

    What about Agency Work?

    Depends on the agency.

    Some agencies are real meat grinders, and some are less so. The hardest part about agency life is how fast everyone and everything has to move. Also it’s incredibly volatile! If the company who hired you isn’t doing well, fffftttt you’re screwed.

    (Again, if XWP called me tomorrow, I would happily talk with them.)

    Would I work for a plugin shop?

    Yes, I would. I know plugins, I know the repo (sure things have changed but the basics aren’t going to), and I know the forums. Plugins are a lot of work of course.

    How about a security company?

    That would be epic fun. Yes. Finding issues, reporting them reasonably and privately, getting them fixed, and helping everyone? I miss that from Plugin Reviews.

    What about just plain ol’ IT?

    I’ve done it before. I’m sure some of my info is out of date (anyone need a Windows NT Server certified dev?), but again, I’m willing and able to learn. Basic IT has some joy you know, and users do some wild and crazy things you don’t expect.

    Didn’t anyone tell me not to sell ME in a resume?

    Many. But the thing is, you’re not hiring a machine, you’re hiring a person. If you want a grunt to grind? That ain’t me.

    If you want a well reasoned, insightful, and creative individual who thinks for herself and is willing to try things even if they fail, because those lessons help you going forward? Who fights for the users and is honest even when it hurts? Who will stand by her principles even if they cost her work? Who is passionate and puts her all into everything?

    That’s me.

  • Why NOT WordPress?

    Why NOT WordPress?

    There’s a website I’ve been running since 1996.

    Yes, I know, I’m an Internet Old.

    1996. That’s 7 years before WordPress was a thing. So it’s not surprising this site was (at one point) moved from ‘something else’ to WordPress. Actually a lot of something-elses over the nearly 30 years of its existence. I moved it over to WP around 2005 (WordPress 1.5) and pretty much left it there for years.

    Now it’s different. Now the site is 100% powered by Hugo.

    Why Did I Stop Using WordPress?

    To understand this decision, you have to keep in mind that the site had been three parts for about 20 years.

    1. The Blog, where announcements were made, etc, powered by WordPress
    2. The Image Gallery, which had … images (about 20 Gigs), powered by netPhotoGraphics
    3. The Wiki/Library, which is the documentation, powered by Hugo

    Well, this year I got a nasty-gram and was forced to shut down the gallery. The simple truth was yes, the gallery included images that legally I didn’t have the right to use. No excuses. But the company involved was kind enough to work out a partial situation. I’m still in the middle of moving what images I can keep into a new home, but while that’s going on, I had a chance to sit down and face reality.

    The gallery, you see, was the biggest feature of the site. Next was the Wiki/Library, and the blog was pretty much just announcements. There was a forum, it was removed ages ago. There was BuddyPress, ditto. People management just isn’t fun.

    There was also the matter of cross linked data. Oh my, did a lot of images appear on the blog and the gallery. I was going to have to purge the old blog posts en masse anyway, so at that point, I asked myself that big question.

    Do I want to move the library to WP, or the blog to Hugo?

    Consider the following:

    1. I was going to have to manually curate nearly 30 years of blog posts (took a few thousand down to about 50)
    2. I already had a running Hugo site and was familiar with it (it has over 1600 files)
    3. If I ported to WP, I would have to rebuild my data setup for how the data is output
    4. Importing blog posts as text only is incredibly easy

    With that in mind, it seemed obvious. Hugo.

    What’s Different with Hugo?

    Obviously I lose the ability to write a blog post and press publish. I have to add a new file, manually link it to my new image, and push to GitHub, where it’s auto-deployed to the site in question. The process for any data is basically this:

    1. Create a branch on my GitHub Repo
    2. Add the new content
    3. Merge the branch into Production

    At that point a GitHub action takes over.

    Beyond that, however, there are some things you take for granted with WP. Like the ease of a mailing list with Jetpack. Now, I did export my Jetpack subscribers and I’m working on a solution there, but yeah, that was a big hit. There’s also the matter of auto-deploying content to socials. But… honestly that’s been pretty much shit-and-miss lately, what with Facebook and Twitter being what they are.

    But all the ‘easy’ stuff? Well Hugo has RSS Feeds, it can process images as it builds (though that will cause your deployments to take longer), it’s open source, and best of all? The output is static HTML.

    Go ahead, try and hack that.

    How Hard Was It?

    Honestly, it took me about 3 days to pick a new theme, apply it, move my basic content over, and start rebuilding the blog. Migrating blog posts took me about 3 weeks. The hardest part was realizing I was going to have to write some complex Hugo Mod code to include my gallery with lightbox code, but I banged that out in an evening.

    There were frustrating moments. The Hugo community is significantly smaller than WordPress (I mean, whose isn’t?) and some of the code is a little on the ‘understood’ level (by which I mean things aren’t always spelled out, they assume you know what they’re talking about). In a way, it’s like using WordPress back in 2006 all over again, and look at where that’s taken me!

    I’m very happy with the result. I picked a ‘fancy’ theme, called Hinode, and it came with Dark Mode built in. I ported over my custom code for recaps (I have a whole star rating system) and started building out topical small galleries where I could.

    If I was a newbie to the web world? This would have been impossible. Then again, a lot of the work I’m doing in WP would be impossible for a newbie. About the only tool I’ve used where I think it’d be easier would be … Maybe MediaWiki? But only because you can build templates from the editor backend.

    Even with Full Site Editing, WordPress would have been a bear and a half.

    Historical Notes

    The ‘Library’ was once on MediaWiki because I had this idea to be a public repository anyone could edit. Only I kept getting attacked by spammers, so I turned off registration. Then I had to apply all sorts of plugins, only MediaWiki didn’t allow you to self-update like WordPress, and I had to write scripts and it was just a pain.

    I rebuilt it all as Hugo about 6 years ago, and I really enjoyed it. GoLang is not something I’m familiar with, and sometimes the language drives me to drink, but so does PHP.

    The Gallery used to be a home-grown SHTML setup, which then moved to a now defunct project, Gallery, and then to ZenPhoto, and finally to NetPhotoGraphics after ZenPhoto decided to be more than just a photo library. NetPhotoGraphics is hella fun to use, and I even built an embed tool for it, so you could paste a link into WP.

    I did that with Hugo as well, and I’ll probably port that back to the new site sooner or later.

    It Is Sad Though

    Basically this site has been a part of my dev growth from day one. I wouldn’t be working in WordPress were it not for this site, and I owe it a lot. Moving to Hugo is the end of an era, and it is a bit sad. But at the same time, I feel like I’m now in even more control over everything, and I’m making a leaner, faster, website every day.

    I have no regrets for the steps I’ve taken on the way, and none about this move. It’s nice to not have to worry about updates all the time. After all, what’s on the site is just HTML.

    I do miss being able to schedule posts though…

  • Plugins: If I Did It…

    Plugins: If I Did It…

    Joe Co. (not their real name) had a bad day. It started when we emailed them this:

    Your plugins have been closed and your account on WordPress.org is suspended indefinitely for egregious guideline violations.

    Normally we send out warning notices to encourage developers to correct behaviour, however in certain cases there are events that cause us to have to take extreme action.

    We received a notice that you had been demanding users edit their reviews and comments in order to receive a refund from your product.

    Your employee fake@example.com said this:

    > Please kindly delete all the comment on Wordpress.org,
    >
    > After that, we will accept your refund request via PayPal

    And then they would provide links to screenshots of what to remove.

    There’s an ugly word for asking people to modify reviews for a reward: bribery.

    However asking users to modify a review and delete comments to get a refund is called EXTORTION […]

    If you’ve been reading for a while, you know how much I hate bribery and extortion.

    Time for Proof

    Joe Co. emailed back

    It’s not a nice day today for our company to receive your email on our
    account suspension, but we understand your concern to protect Org’s plugins
    directory. We respect that!

    However, in case like this, you did receive the feedback from one side and
    I believe that you should take times to reviceved the feedback from our
    company also. And we want you to know that maybe you should protect us and
    our company from UNFAIR COMPETIOR.

    Yes we internally thinks that this is among our Competitor doing
    something to get our Copies of Products and Try to Down us from the
    Directory, here is the reason and proof:

    Now, I can’t list the rest of that for reasons regarding anonymity, but I can summarize the proof:

    1. A PDF of their internal chat client with the person who they felt probably complained (it wasn’t that person)
    2. A PDF where the same person refused to let Joe Co. log into their site (reasonable!)
    3. An admission they handled it badly

    When I opened the first PDF I started laughing.

    Wanna know what it says?

    Important part: But you sent too much spam on our ticket support, our forum and Wordpress.org so we decided to give you a refund under the condition that you delete all your bad comments to avoid your spam review affect to our company's reputation. If you cannot delete all then it's meaningless.

    The kicker? That arrow and red box is from the PDF Joe Co. sent.

    Right there, they are agreeing they told the user that they would only give him a refund if he deleted the reviews. They were not spam, either, the person left one, single, solitary review.

    Who was wrong?

    Both Joe Co. and their user were in the wrong here.

    The user knew damn well they weren’t getting a refund, and in fact I told him he was being silly for complaining that there was no refund when the terms he agreed to say no refunds.

    Joe Co. should have stuck by their guns and not suggested that would give a refund at all.

    Of course, what Joe Co. did after that made it so much worse… Sockpuppets. Sockpuppets everywhere. 100% of their reviews on one plugin were faked. My buddy who cleans that up sobbed into his coffee and I think that was when he wrote a ‘close all’ script.

    Then they made two more accounts and resubmitted plugins and did it all again.

    So while I will grant you that the user was an idiot, Joe Co. was worse.

    Do You Refund?

    Regardless of if you provide refunds or not, the lesson here is “stick to your guns” folks. If the policy is “No Refunds” then suck it up, buttercup. If the policy is to provide a refund within X days, then you do that. If the company has no refund policy, then don’t buy from them, because you will get jerked around.

  • Plugins: It’s the Wild Wild West

    Plugins: It’s the Wild Wild West

    The email subject was really funny to me.

    PLEASE, save from the lawlessness!!!

    They’re guidelines but sure. Yogi Bear here (whose real username implied he was smarter than everyone) went on to complain that developers ignore him and forum mods removed his posts complaining about said developers.

    At WordPress.Org forums there is chaos and lawlessness from some moderators and deception from developers!!!

    I work about month on improving and problem resolving and translation of huge plugin [redacted] but after I send my work to developer – they just start to ignore me and did not fulfill their promises! 

    I create thread at forum about it, but some idiot arbitrariness moderator [redacted] just delete my thread with my asking for a help! 

    It’s like if police will just kill people that turned to them for help!!! It’s a total absurd!!!

    A little hyperbolic, but sure, I get the annoyance.

    All Mods are Bad Mods

    Taking a look, it was clear what happened. Yogi Bear here had made a forum post about “WARNING! Deception from developers!”

    The plugin in question offered free premium versions of their plugin in return for help making translations. In so far as bribery goes, I considered this to be a decent trade. The offer only existed on their website, you had to go find it, and it was a very clear “Work for us and be rewarded.” It didn’t, at the time, violate any guidelines.

    Yogi Bear made a translation and sent it in. Then he spent weeks finding every security bug he could on the plugin and sent that in.

    But after I sent it – developers just stop answer to my letters (already about month) without any explanations and apology.

    […]

    Therefore I WARNING all who want to translate [redacted] or negotiate with developers of [redacted] also known as [redacted] – all your work can be just taken without any promised gratitude!!! Dont work with [redacted] and [redacted] developers – they are deceivers!!!

    Ugh.

    While I will concede that ghosting someone with good intentions is a dick move, there was no contract between Yogi Bear and them, so while they should publicly credit him, they don’t have to. Also they didn’t use most of his fixes (I learned that much later).

    One of the forum Mods replied that you don’t have to submit a translation to them directly (which is true). What that Mod didn’t know was that Yogi Bear was looking for the compensation that comes when your translation via WP.org is accepted.

    Thing is … Yogi Bear’s translation was accepted (and he was rewarded). But still he saw three major issues:

    1. The translated country list, on the English page, was in … English
    2. The plugin used too many similar phrases (“Please confirm” vs “Are you sure?”)
    3. Due to 2, there’s too much reworking to do here

    Issue one was just someone not understanding what he was looking at. The second I agree is annoying, but honesty if that creates number three, then … don’t volunteer.

    The plugin dev replied that they 100% had sent him the reward, but did not agree to his ‘extra’ conditions. This devolved into a “Yes I did!” “No you did not!” shouting match. Yogi Bear had screenshots saying he didn’t get anything (no attachments), Plugin had screenshots with links to where he could download (and his replies).

    After a back and forth, the Mods sighed and closed the thread. Shortly thereafter, Yogi Bear complained to Plugin Review.

    As Expected, It Escalated

    Well.

    Looking into it, it was obvious Yogi Bear had been asked to stop calling people names (everyone was a liar or a cheat). The Mods had warned him, twice, so Plugins pointed out he was the one breaking rules at that point. However! Plugins was happy to listen to this complaint if he had actual evidence of a violation to the forum or plugin guidelines.

    so fucking tide of all of you – idiots, irresponsible scums, liars, lazybones, etc…

    Are you an idiot?

    I repeat – ARE YOU AN IDIOT?

    Because obvious answer is – yes, you are an idiot for sure, I have to explain to you what you can’t understand with your stupid mind…

    1) Where you find that I ignored any requests form moderators?! It’s a total LIE! I don’t receive any requests, just my threads was deleted now by idiot [Mod 1] and previously by the same kind of arrogant idiot [Mod 2]. You cannot blame me that I ignore something because I don’t get any request at all!!!

    2) What do you think – for what purpose WORDPRESS PLUGINS SUPPORT FORUM are exist?! I don’t even talk about super low level of help at those forums by your team and mass closing not resolved questions… but particularly at this situation – It’s most obvious function of especially your forum is to WARN OTHERS about some developer dishonest and deception behavior at their forum part! It’s most obvious action to ask WP.org community to help! It’s most obvious action to ask moderator FOR HELP!

    3) Publishing all this at my personal blog will not give any results. You must smarten up and grow up and take responsibility of such conflicts resolving, of WP users protection, etc – BUT NOT TO brainless delete their asking of help for sure!!!

    4) Just read deleted thread – there are full of EVIDENCE – I help to [plugin] with more than 100!! Bug resolving, some fundamental problems resolving, many suggestions of improvements, etc – it’s a HUGE and respectable work that just was taken by dishonest developers! If it’s a normally for you, if you don’t feel that WP.org PLUGINS SUPPORT FORUM M_U_S_T protect users from dishonest developers and lawlessness moderator – you better just go and kill yourself.

    So to be clear here, none of his complaints were about the plugin (I was kind of hoping he’d share the security issues since I didn’t see anything serious), and all were that the Mods told him to stop being a dick, and he said no. Per usual, this was replied with a reminder that Yogi Bear was emailing the plugin review team and we did not overrule forum moderators unless it was a valid issue with the plugin.

    The issue was Yogi Bear hating the plugin developers.

    Not our circus, not our monkeys (unless the plugin devs retaliated, which they did not — though we did caution them about sharing screenshots of emails in public).

    Yogi Bear shouted that we were idiots, again, and:

    If you are only “plugin bugs fixing team” – you must NOT judge me and my threads at forum.

    1. Plugin REVIEW team, for fuck’s sake!
    2. We weren’t judging, we were saying the Mods did their job and we didn’t override them

    Okay, I was absolutely judging him. And I agreed with the Mods here.

    Yogi Bear was directed to Slack.

    He never went.

    What Happens if The Dev is a Dick?

    That’s a valid question! The answer sucks.

    Let’s say Yogi Bear was right and his translation was accepted (easily verifiable) but he did not get any reward as promised (harder to verify, but assume we could), then … WordPress.org is not the place to rant.

    See, that’s an agreement between Yogi Bear and the plugin developers. It’s a causal, kinda verbal, agreement, and if they don’t meet their end … Er … Well. Sucks? Go to social media?

    Now, if you look back you can see Yogi Bear kind of understood that WP.org wasn’t the place, but he wouldn’t get traction anywhere else. And I really do feel for him there. But if you go to a place, rant, and are told “Hey man, this is not the place where you can just rant about these casual deals gone wrong” and you decide to do it anyway?

    Right.

    If a dev is a dick but didn’t violate guidelines, post about it on your blog, stop using their plugins, and let ’em rot. But that’s really all you can do. A lot of assholes make great plugins, and a lot of them are really not pleasant to work with. Unless they’re breaking the rules of the sites they’re on, you vote with your dollar and nothing more.

    For Yogi Bear here, he did all this to himself.

    1. He 100% got the code he was ‘owed’
    2. The plugin dev had no obligation to work with Yogi Bear on the security issues
    3. He never reported the security issues to WP.Org
    4. He intentionally, purposefully, and willfully ignored the reasonable ask of the Forum Mods to stop being a dick

    In the words of Jeff Probst, I got nothing for you.