<?php
/*****************************************************
       File: test.admin.menu.php
Plugin name: Test Admin Menus
    Version: 0.0.1
Description: Testing issues with administration menus.
     Author: Kevin Machin.
 Author URI: //darkskipper.com/
*****************************************************/

TestAdminMenu::init ();

///////////////////////////////////////////
// Class: TestAdminMenu
//  Desc: For testing administration menus.
///////////////////////////////////////////

class TestAdminMenu
{
    // Plugin info
    const   TAM_NAME = 'Test Administration Menus',
            TAM_VERS = '0.0.1';

    // Slugs/URIs for menus and items
    const   M_SLUG = 'tam-menu',
            I_SLUG = 'tam-info',
            U_NANO = 'https://nanowrimo.org/';

    private $aErrs; // List of detected errors

    // Constructor
    final private function __construct ()
    {
        // Restrict file access to everything from top directory down
        $sDire = str_replace ('\\', '/', __FILE__);
        $sDire = preg_replace ('#^(([A-Z]:|//[^/]+)?/).*$#i', '$1', $sDire); // Allow drive letter or share name
        ini_set ('open_basedir', $sDire);

        // Empty the errors array
        $this->aErrs = [];
    }
    // End of constructor

    ////////////////////////////////
    // Meth: handErr
    // Desc: Error handler.
    // Retn: Boolean "handled" flag.
    ////////////////////////////////

    final public function handErr ($piCode, $psMess, $psFile, $piLine)
    {
        $aErr = // Store the details for later rendering
        [
            'code' => $piCode,
            'mess' => $psMess,
            'file' => $psFile,
            'line' => $piLine
        ];
        $this->aErrs[] = $aErr;
        return (FALSE); // Not handled, so we log the messages as well
    }
    // End of handErr

    ////////////////////
    // Meth: init
    // Desc: Initialise.
    ////////////////////

    final public static function init ()
    {
        $sKind = __CLASS__;
        $oMain = new $sKind ();
        add_action ('admin_menu',   [$oMain, 'actMenu']);
        add_action ('adminmenu',    [$oMain, 'actTidy']);
        add_filter ('submenu_file', [$oMain, 'filtItem'], 10, 2);
    }
    // End of init

    ///////////////////////////////////////
    // Meth: actMenu
    // Desc: Add some administration menus.
    // Note: Called for 'admin_menu' hook.
    ///////////////////////////////////////

    final public function actMenu ()
    {

        $sMenu = self::M_SLUG;
        $sItem = self::I_SLUG;
        $sSite = self::U_NANO;
        $sCapa = 'read';
        $sHead = 'NaNoWriMo &ndash; Information';
        $mShow = [$this, 'rend'];
        add_menu_page    ($sHead, 'NaNoWriMo', $sCapa, $sMenu, $mShow);
        add_submenu_page ($sMenu, $sHead, 'Info',    $sCapa, $sItem, $mShow);
      //add_submenu_page ($sMenu, $sHead, 'Drive',   $sCapa, 'A:/floppy');
        add_submenu_page ($sMenu, $sHead, 'Website', $sCapa, $sSite);
        remove_submenu_page ($sMenu, $sMenu);
    }
    // End of actMenu

    //////////////////////////////////////////////////////
    // Meth: filtItem
    // Desc: Filter sub-menu items.
    // Retn: Filtered item.
    // Mote: This is called before the menus are rendered,
    //       giving us an opportunity to look for errors.
    //       Called for 'submenu_file' hook.
    //////////////////////////////////////////////////////

    final public function filtItem ($psItem, $psMenu)
    {
        if ($psMenu === self::M_SLUG)
        {
            // Set error handler
            set_error_handler ([$this, 'handErr']);
            //error_reporting (~0);
            trigger_error ('Test error, please ignore.');
        }
        return ($psItem);
    }
    // End of filtItem

    /////////////////////////////////////////////////////////////////
    // Meth: actTidy
    // Desc: Tidy up after menu pages.
    // Note: Called after menu rendering, through action 'adminmenu'.
    /////////////////////////////////////////////////////////////////

    final public function actTidy ()
    {
        restore_error_handler ();
    }
    // End of actTidy

    ////////////////////////////
    // Meth: rend
    // Desc: Render information.
    ////////////////////////////

    final public function rend ()
    {
        // Page markup
        $sForm = <<<_EOT
<h1>
 %s
 <span style="font-weight:normal">&ndash; Version %s</span>
</h1>
<div class="tam">
 <style type="text/css" scoped="scoped">
 div.tam, div.tam p, div.tam code
 {
  font-size: large;
 }
 div.tam div.tac
 {
  text-align:    center;
  background:    #E2EFF3;
  margin:        0 1em;
  border-radius: 1em;
  padding:       1em;
 }
 div.tam div.tac *
 {
  color:      #674732;
 }
 div.tam table, div.tam th, div.tam td
 {
  border:          1px solid;
  border-collapse: collapse;
  padding:         4px;
  text-align:      left;
 }
 div.tam td
 {
  vertical-align: top;
 }
 div.tam td.wrap
 {
  word-wrap: break-word;
 }
 </style>
 <p>
  Below is some sample text.
  The real purpose of this plugin is to demonstrate
  <a title="See below &darr;" href="#tam-errs">warning messages</a>
  generated in the WordPress core administration menu routines.
 </p>
 <div class="tac">
  <h2>
   National Novel Writing Month
  </h2>
  <h3>
   <em>NaNoWriMo</em>
  </h3>
  <p>
   <a title="See the NaNoWriMo website&hellip;" href="%s">National Novel Writing Month</a> is a fun,
   seat-of-your-pants approach to creative writing. 
  </p>
  <p>
   On November 1<sup>st</sup>, participants begin working towards the goal of writing<br />
   a 50,000 word novel by 11:59&nbsp;pm on November 30<sup>th</sup>.
  </p>
  <p>
   Valuing enthusiasm, determination, and a deadline,<br />
   NaNoWriMo is for anyone who has ever thought about writing a novel.
  </p>
 </div>
 <h3>
  Diagnostics
 </h3>
 <p>
  <code>%s = '%s'</code>
 </p>
 <p>
  <code>error_reporting = 0x%X</code>
 </p>
 <h4 id="tam-errs">
  Detected Errors
 </h4>
%s
</div>
_EOT;
        // Format the errors
        $sErrs = NULL;
        $sErrF = <<<_EOT
  <tr>
   <td>%s</td>
   <td class="wrap">%s</td>
   <td title="%s">%s</td>
   <td>%d</td>
  </tr>
_EOT;
        foreach ($this->aErrs as $aErr)
        {
            // Display codes as constant names
            $sCode = $this->errc ($aErr['code']);

            // Zero width non-joiners help with word wrapping for long messages
            $sMess = preg_replace ('#[\\\\/]#', '/&zwnj;', $aErr['mess']);

            // Display short file names -- hover over for full paths
            $sFile = $aErr['file'];
            $sName = basename ($sFile);

            // Each error has a row in a table
            $sErrs .= sprintf ($sErrF, $sCode, $sMess, $sFile, $sName, $aErr['line']);
        }
        if ($sErrs !== NULL)
        {
            // Wrap the rows in a table
            $sErrs = <<<_EOT
 <table class="err">
 <thead>
  <tr>
   <th>Error</th>
   <th>Message</th>
   <th>Source File</th>
   <th>Line</th>
  </tr>
 </thead>
 <tbody>
$sErrs
 </tbody>
 </table>
_EOT;
        }

        // Render the page
        $sHead = self::TAM_NAME;
        $sVers = self::TAM_VERS;
        $sBase = 'open_basedir';
        printf ($sForm, $sHead, $sVers, self::U_NANO,
                $sBase, ini_get ($sBase), error_reporting (), $sErrs);
    }
    // End of rend

    /////////////////////////////////////////////
    // Meth: errc
    // Desc: Convert an error code into a string.
    // Retn: String, e.g. 'E_USER_WARNING'.
    /////////////////////////////////////////////

    final private function errc ($piCode)
    {
        static $aCode =
        [
            'E_NOTICE',
            'E_WARNING',
            'E_ERROR',
            'E_STRICT',
            'E_DEPRECATED',
            'E_USER_NOTICE',
            'E_USER_WARNING',
            'E_USER_ERROR',
            'E_USER_DEPRECATED'
        ];
        $sCode = sprintf ('%08X', $piCode);
        foreach ($aCode as $sCons)
        {
            $iCode = constant ($sCons);
            if ($piCode === $iCode)
            {
                $sCode = $sCons;
                break;
            }
        }
        return ($sCode);
    }
    // End of errc
}
// End of class TestAdminMenu
// End of file test.admin.menu.php