# WordPress plugin recipe

Add a paraphraser shortcode whose requests go through WordPress, never through a browser secret key.

Configure `METAPHRASE_API_KEY` as a private server environment variable. In `wp-config.php`, before the final stop-editing comment, add:

```php
define('METAPHRASE_API_KEY', getenv('METAPHRASE_API_KEY') ?: '');
```

Create `wp-content/plugins/metaphrase-writing/metaphrase-writing.php` with this complete plugin, activate it, and insert `[metaphrase_writer]` into a page:

```php
<?php
/**
 * Plugin Name: MetaPhrase Writing
 * Description: A backend-proxied writing form with a public shortcode.
 * Version: 1.0.0
 */
if (!defined('ABSPATH')) { exit; }
add_shortcode('metaphrase_writer', function () {
    $id = wp_unique_id('metaphrase-');
    $ajax = admin_url('admin-ajax.php');
    $nonce = wp_create_nonce('metaphrase_write');
    ob_start(); ?>
    <div id="<?php echo esc_attr($id); ?>">
      <form><label>Your text<textarea name="text" required style="display:block;width:100%;min-height:160px"></textarea></label><button>Paraphrase</button></form>
      <p role="alert" data-error></p><pre data-output style="white-space:pre-wrap"></pre>
    </div>
    <script>
    (()=>{const root=document.getElementById(<?php echo wp_json_encode($id); ?>),form=root.querySelector('form'),button=form.querySelector('button');
      form.addEventListener('submit',async event=>{event.preventDefault();button.disabled=true;root.querySelector('[data-error]').textContent='';
        try{const data=new FormData(form);data.set('action','metaphrase_write');data.set('nonce',<?php echo wp_json_encode($nonce); ?>);
          const response=await fetch(<?php echo wp_json_encode($ajax); ?>,{method:'POST',body:data});const value=await response.json();
          if(!response.ok||!value.success)throw new Error(value.data?.message??'Please retry.');root.querySelector('[data-output]').textContent=value.data.text;
        }catch(error){root.querySelector('[data-error]').textContent=error.message;}finally{button.disabled=false;}});
    })();
    </script>
    <?php return ob_get_clean();
});
function metaphrase_write_handler() {
    if (!check_ajax_referer('metaphrase_write','nonce',false)) { wp_send_json_error(['message'=>'Refresh the page and retry.'],403); }
    $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
    $limit_key = 'mp_ip_' . hash_hmac('sha256',$ip,wp_salt('nonce'));
    $count = (int) get_transient($limit_key);
    if ($count >= 15) { header('Retry-After: 60'); wp_send_json_error(['message'=>'Please wait a minute.'],429); }
    set_transient($limit_key,$count+1,60);
    if (!defined('METAPHRASE_API_KEY') || !METAPHRASE_API_KEY) { wp_send_json_error(['message'=>'The server API key is not configured.'],503); }
    $text = isset($_POST['text']) && is_string($_POST['text']) ? sanitize_textarea_field(wp_unslash($_POST['text'])) : '';
    if (!$text || strlen($text) > 80000 || preg_match_all('/./us',$text) > 20000) { wp_send_json_error(['message'=>'Enter text within the character limit.'],400); }
    $response = wp_remote_post('https://metaphrase.msalman.com/api/v1/paraphrase',[
      'timeout'=>250,
      'headers'=>['Authorization'=>'Bearer '.METAPHRASE_API_KEY,'Content-Type'=>'application/json'],
      'body'=>wp_json_encode(['text'=>$text,'mode'=>'standard','stream'=>false]),
    ]);
    if (is_wp_error($response)) { wp_send_json_error(['message'=>'The writing service could not be reached.'],502); }
    $status = wp_remote_retrieve_response_code($response);
    $value = json_decode(wp_remote_retrieve_body($response),true);
    if ($status >= 400) { wp_send_json_error(['message'=>$value['error']['message']??'Please retry.','docs_url'=>$value['error']['docs_url']??''], $status); }
    wp_send_json_success(['text'=>$value['result']['text']]);
}
add_action('wp_ajax_metaphrase_write','metaphrase_write_handler');
add_action('wp_ajax_nopriv_metaphrase_write','metaphrase_write_handler');
```

The nonce protects the browser action; the IP throttle is a conservative demo guard. WordPress transients are not an atomic distributed limiter. For high traffic use your site's authenticated access or an atomic shared limiter, and configure WordPress's trusted reverse proxy handling so client IPs cannot be spoofed. If the site uses a strict CSP, move the inline handler into an enqueued script with your site's nonce policy.

For a simpler public box with no WordPress backend integration, use [the widget script](https://metaphrase.msalman.com/docs/widget.md).
