source: spip-zone/_plugins_/pgn4spip/branches/pgn4spip_fonctions.php @ 90061

Last change on this file since 90061 was 90061, checked in by jack@…, 6 years ago

Avant de tout casser on passe le trunk dans "branches"

File size: 17.6 KB
Line 
1<?php
2/**********************************************************************************
3 * @Subject Parse options and generate HTML iframe to run board.html
4 * @package pgn4spip plugin to embed pgn4web Chessboard in a SPIP 2.x or 3.x article
5 * @version 2.61.0
6 * @copyright copyright (c) 2012 Matt Chesstale
7 * @license GNU General Public License version 3
8 * @compatible SPIP 2.x and CFG 1.15 or SPIP 3.x
9 * Penknife or Swiss Army Knife 1.8 if pgn4spip is enabled after Swiss Knife
10 * If you see .pgn header with French double quotes, disable and reenable pgn4spip.
11 * @language PHP for SPIP 2 or 3
12 *
13 * @history:
14 * 2.61.0: Initial version for SPIP 2.1
15 * @credits: Paolo Casaschi
16 * @reference: http://pgn4web.casaschi.net
17 * @tutorial: http://code.google.com/p/pgn4web/wiki/User_Notes_joomla
18 * @Usage
19        In a SPIP article, enter the following tag:
20        [pgn parameter1=value1 prm2=value2 ...] chess games notation in PGN format [/pgn]
21       
22        Tag parameters: see the configuration of the module in CFG for the help
23        layout=horizontal|vertical
24        height=auto|'number'
25        movesDisplay=figurine|text|puzzle|hidden
26        initialGame=first|last|random|'number'
27        initialVariation='number'
28        initialHalfmove=start|end|random|comment|'number'
29        autoplayMode=game|loop|none
30 
31        Example in a SPIP article:
32        Before
33        [pgn autoplayMode=loop initialHalfmove=end] 1. e4 Nf6 2. e5 Nd5 [/pgn]
34        After
35 **********************************************************************************/
36if (!defined("_ECRIRE_INC_VERSION")) return; // No direct access allowed to this file
37
38define('PLUGIN_Name', "pgn4spip");
39define('PATH_Conf', PLUGIN_Name . '/' . PLUGIN_Name . '_conf.php');
40if (!function_exists('ReadCurrentConfiguration')) require _DIR_PLUGINS . PATH_Conf;
41
42define('TAG_pgn', "[pP][gG][nN]"); // pgn or PGN
43define('PATH_board', PLUGIN_Name . "/pgn4web/board.html");
44define('PATH_live', PLUGIN_Name . "/boardLive.html");
45define('indFullPattern', 0);
46define('indPgnOption', 1);
47define('indPgnGame', 2);
48define('NO_iFrame', "your web browser or your host do not support iframes as required to display the chessboard");
49define('DEFAULT_HEIGHT', 60); // 3*2 border + 13*2 padding + 28 buttons without 26*8 squares
50
51define('CLS_CHESSS', "class='chessboard-wrapper'"); // chessboard-wrapper class for the div
52
53// Parse options, prepare HTML content for output, generate the HTML iframe
54// pipeline "pre_propre" is called for the description of the article then its body before "post_propre"
55// @in:         $flux the full article including the [pgn] ... [/pgn] tags
56// @return      $flux with the pgn tags replaced with the HTML iframe to run board.html
57function pgn4spip_prepropre($flux)     
58{
59        global $optValue; // Values of options
60
61        if ($optValue == NULL) // True in SPIP 3 since pgn4spip_options.php is not called
62        {       // Init $optValue with default values of options overriden with the current configuration
63                ReadCurrentConfiguration($optValue);
64        }
65        $regex = "@\[" . TAG_pgn . "(.*?)\](.*?)\[/" . TAG_pgn . "\]@s"; // Expression to search for
66        preg_match_all($regex, $flux, $matches); // find all instances of plugin and put in $matches
67        $countPlugins = count($matches[2] ); // Number of plugins
68        if ($countPlugins > 0) // PGN game(s) found?
69        {
70                NameOptionPrm(/* out */ $optName, $skipPrms, $pgnPrms);
71                for ( $indPlugin=0; $indPlugin < $countPlugins; $indPlugin++ ) // For each game
72                {
73                        $pgnOptionsInput = $matches[indPgnOption][$indPlugin];
74                        $pgnOptions = OptionParser($pgnOptionsInput, $optName);
75                        $pgnSource = ExtendedOptionParser($pgnOptions, $optName, $skipPrms, $pgnPrms, 
76                                                                    /* out */ $isNewPgnSource, $pgnSource); 
77                        $pgnText = SpipSpecific($matches[indPgnGame][$indPlugin], /* out */ $idTxtArea);
78                        $height = $optValue['fh']; // Either "" for automatic evaluation of the height or fh=nbr
79                        $isLive = IsMatchFirst($optValue['md'], 'live');
80                        $optValue['fh'] = FrameHeightEval($pgnText, $isLive, $optValue['hl'], $isNewPgnSource, $optValue['md'],
81                                                                        /* i/o */ $height); // The return value could be not numeric like "b"
82                        $strHtml = GenHtml($pgnText, $idTxtArea, $isLive, $isNewPgnSource, $pgnSource, $height);
83                        $flux = str_replace($matches[indFullPattern][$indPlugin], $strHtml, $flux);
84                        InitOptionValueByDefault($optValue); // Restore values by default defined in PATH_check module
85                }               
86        }
87        return $flux;
88}
89
90// Generate the HTML code of the div including the iframe calling board.html with several options
91// @in:         $pgnText the game in PGN format
92//                      $idTxtArea id of the pgnText
93//                      $isLive true if live broadcast otherwise false
94//                      $isNewPgnSource true if extendedoptions
95//                      $pgnSource special option(s) to indicate the (remote) source of PGN if extendedoptions
96//                      $height the height of the iframe module
97// @return      the HTML iframe to run board.html
98function GenHtml($pgnText, $idTxtArea, $isLive, $isNewPgnSource, $pgnSource, $height)
99{
100        global $cs, $optValue;
101       
102        if ($isLive)
103        {
104                $pathBoard = PATH_live; // Relative path of the HTML script boardLive.html
105                $optValue['md'] = "f"; // (f)igurine in live mode
106        }
107        else
108                $pathBoard = PATH_board; // Relative path of the HTML script board.html
109        if (strlen($cs) == 0) 
110                $csDef = " ";
111        else 
112                $csDef = " style='" . $cs . "' ";       // containerStyle
113       
114        $strHtml  = "<div" . $csDef . CLS_CHESSS . ">";
115    if (!$isNewPgnSource)
116        {
117                $pgnId = "pgn4web_" . $idTxtArea;
118                $strHtml .= "<textarea id='" . $pgnId . "' style='display:none;' rows='40' cols='8'>" . $pgnText . "</textarea>\n";
119        }
120        $strHtml .= "<iframe src='" . _DIR_PLUGINS . $pathBoard . "?";
121        $isFirstOption = true;
122        foreach ($optValue as $optShort => $value)
123        {
124                if (($optShort != "l") && // (l)ayout already encoded by (h)orizontal(L)ayout
125                        (($optShort[0] != "r") || $isLive))
126                {
127                        if (!$isFirstOption) $strHtml .= "&amp;"; // Separator between two parameters
128                        $strHtml .= $optShort . "=" . rawurlencode($value);
129                        $isFirstOption = false;
130                }
131        }
132    if (!$isNewPgnSource) 
133                $strHtml .= "&amp;pi=" . rawurlencode($pgnId);
134               
135    $strHtml .= $pgnSource . "'\n";
136        $strHtml .= "frameborder='0' width='100%' height='" . $height . "' ";
137        $strHtml .= "scrolling='no' marginheight='0' marginwidth='0'>" . NO_iFrame . "</iframe>";
138        $strHtml .= "</div>";
139
140        return $strHtml;
141}
142
143// @return      true if the option matches with the value or the first character of the value
144// @in:         @option the value of the option
145//                      @value  the value for comparison
146function IsMatchFirst($option, $value)
147{
148        if ($option == $value)
149                return true;
150        if ($option == $value[0])
151                return true;
152        return false;
153}
154
155// Parse the options inside the [pgn prm1=value1 prm2=value2 ...] tag
156// Update the values in $optValue, the array of options
157// @in:         $pgnOptionsInput the specified options in the pgn tag
158//                      $optName array of long names of options, an alternative to the short names
159// @return      the pgn options without HTML tags
160function OptionParser($pgnOptionsInput, $optName)
161{
162        global $optValue;
163       
164        $pgnOptions = preg_replace('@(pgnData=|pd=)<a href="([^"]+)".*</a>@i', "$1$2", $pgnOptionsInput); // Fix Swiss Knife MailCrypt
165        $pgnOptions = preg_replace("@<.*?>@", " ", $pgnOptions); // Remove HTML tags
166        foreach ($optValue as $optShort => $value)
167        {
168                if (preg_match("#(^|\s)(" . $optName[$optShort] . "|" . $optShort . ")=(.*?)(\s|$)#si", $pgnOptions, $thisOption))
169                {
170                        $valueNew = $thisOption[3];
171                        if (IsOptionValueOk($optShort, $valueNew, $optValue[$optShort]))
172                                $optValue[$optShort] = $valueNew;
173                }
174        }       
175        // Rules between options
176        if (IsMatchFirst($optValue['l'], "vertical")) // vertical (l)ayout?
177                $optValue['hl'] = "f"; // (f)alse: vertical
178        elseif (IsMatchFirst($optValue['l'], "horizontal")) // horizontal (l)ayout?
179                $optValue['hl'] = "t"; // (t)rue:  horizontal
180        if (preg_match("#(^|\s)(height|h)=(.*?)(\s|$)#si", $pgnOptions, $thisOption))
181                $height = $thisOption[3];               
182        if (IsMatchFirst($optValue['md'], "puzzle"))
183                $optValue['hd'] = "v"; // headerDisplay <- (v)ariations
184        return $pgnOptions;
185}
186
187// Compatibility with the SPIP plugin Swiss Army Knife a.k.a PenKnife
188// @in:         $pgnText eventually with left curly inverted commas ou French double quotes << guillemets >>
189// @return      $pgnText with only '"' instead of &laquo; &raquo; &ldquo; &rdquo; generated by Swiss Knife
190//                      Tool "Curly inverted commas"
191// @see         http://www.degraeve.com/reference/specialcharacters.php
192function CompatibleSwissKnife($pgnText)
193{
194        $pgnText = str_replace('&laquo;&nbsp;', '"', $pgnText); // Left angle quote + " " to double quote
195        $pgnText = str_replace('&ldquo;', '"', $pgnText);               // Left double quote
196        $pgnText = str_replace('&nbsp;&raquo;', '"', $pgnText); // " " + Right angle quote
197        $pgnText = str_replace('&rdquo;', '"', $pgnText);               // Right double quote
198       
199        $pgnText = str_replace('&#8217;', "'", $pgnText);               // Left single quote to single quote
200        $pgnText = str_replace('&lsquo;', "'", $pgnText);               // Left single quote
201        $pgnText = str_replace('&rsquo;', "'", $pgnText);               // Right single quote
202        return $pgnText;
203}
204
205// PGN comments are not processed as italic by SPIP. No empty line and HTML tags
206// @in:         $pgnText eventually with HTML tags, emtpy lines, PGN comments between {...}, quotes by Swiss Knife
207// @out:        $idTxtArea hexa identifier based on crc of the pgn text before replacing {...}
208// @return      $pgnText without HTML tags, empty lines, quotes by Swiss Knife but protected PGN comments
209function SpipSpecific($pgnText, &$idTxtArea)
210{
211        $pgnText = trim(preg_replace("@<.*?>@", " ", $pgnText)); // Remove HTML tags in the PGN game
212        // No empty line processed as <p>...</p> by other SPIP plugins
213        $pgnText = preg_replace("@(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n\']+@", "\r\n", $pgnText);
214        $pgnText = CompatibleSwissKnife($pgnText);
215        $idTxtArea = dechex(crc32($pgnText));
216        $pgnText = str_replace("{", "&#123;", $pgnText); // Replace "{PGN comment}" with
217        $pgnText = str_replace("}", "&#125;", $pgnText); // "&#123;PGN comment&#125;" to avoid SPIP italic
218        return $pgnText;
219}
220
221// Is there a new source for the PGN?
222// @in:         the pgn options without HTML tags
223//                      $optName array of long names of options, an alternative to the short names
224//                      $skipPrms other options (extendedoptions, height) to be skiped
225//                      $pgnPrms pgn options to indicate alternative (remote) source
226// @out:        $isNewPgnSource true if extendedoptions
227// @return      $pgnSource special option(s) to indicate the (remote) (URL) source of PGN if extendedoptions
228function ExtendedOptionParser($pgnOptions, $optName, $skipPrms, $pgnPrms, &$isNewPgnSource, &$pgnSource)
229{
230        global $extOpt;
231
232    $isNewPgnSource = false;
233    $pgnSource = trim($extOpt); 
234        if ($pgnSource != '')
235        {
236                $pgnSource = preg_replace('/^\s+/', '', $pgnSource);
237                $pgnSource = preg_replace('/\s+$/', '', $pgnSource);
238                $pgnSource = preg_replace('/&/', ' ', $pgnSource);
239        }
240        foreach ($skipPrms as $optShort => $optLong)
241        {
242                if (preg_match("#(^|\s)(" . $optLong . "|" . $optShort . ")=(.*?)(\s|$)#si", $pgnOptions, $thisOption))
243                {
244                        $valueNew = $thisOption[3];
245                        switch ($optShort)
246                        {                                               
247                        case "eo":      if (IsMatchFirst($valueNew, 'true')) // Extended options?
248                                                        $pgnSource .= ' ' . $pgnOptions;               
249                                                break;                                         
250                        }
251                }
252        }       
253        if (strlen($pgnSource) > 0) 
254        {
255        foreach ($optName as $optShort => $optLong)
256                $pgnSource = preg_replace('#(^|\s+)' . $optShort . '|' . $optLong . '=\S*#si', '', $pgnSource);
257
258        foreach ($skipPrms as $optShort => $optLong)
259                $pgnSource = preg_replace('#(^|\s+)' . $optShort . '|' . $optLong . '=\S*#si', '', $pgnSource);
260
261        foreach ($pgnPrms as $optShort => $optLong)
262                {
263            if (preg_match('#(^|\s+)' . $optShort . '|' . $optLong . '=\S*#si', $pgnSource))
264                        {
265                $isNewPgnSource = true;
266                break;
267            }
268        }
269        if (strlen($pgnSource) > 0)
270                $pgnSource = ' ' . $pgnSource;
271                       
272                $pgnSource = rtrim($pgnSource); // No need to add "&amp;" at the right of the last parameter
273        $pgnSource = preg_replace('#\s+#si', '&amp;', $pgnSource);
274        }       
275        return $pgnSource;
276}
277
278// Evaluate the frame height of the iframe module
279// @in:         $pgnText PGN game
280//                      $isLive true if $md="live"
281//                      $hl horizontalLayout "t" or "f"
282//                      $isNewPgnSource true if extendedOptions then +34 for the height
283//                      $md movesDisplay not hidden -> +300 for the height
284// @return      $height = fh, the (f)rame(H)eight of the module
285//                      "b" for (b)oard for vertical scrolling of the game if horizontalLayout
286function FrameHeightEval($pgnText, $isLive, $hl, $isNewPgnSource, $md, /* i/o */ &$height)
287{
288        global $optValue;
289
290    if (!is_numeric($height))
291        {
292                $ss = $optValue['ss'];
293                if (!is_numeric($ss))
294                        $ss = DEFAULT_ss; // default size for squareSize
295                $ss = intval($ss);
296        $height = DEFAULT_HEIGHT + $ss * 8;
297                if ($isLive)
298                { // GameSelector + eventDetails + playersDetails + statusDetails + padding
299                        $height += 23 + 18 + 17 + 17 + 10;
300                        if ($hl == "f") // Vertical layout?
301                                $height += 150; // Room for the PGN moves under statusDetails
302                }
303                else
304                {
305                        // guessing if one game or multiple games are supplied
306                        $multiGamesRegexp = '/\s*\[\s*\w+\s*"[^"]*"\s*\]\s*[^\s\[\]]+[\s\S]*\[\s*\w+\s*"[^"]*"\s*\]\s*/';
307                        if ($isNewPgnSource || (preg_match($multiGamesRegexp, $pgnText) > 0)) { $height += 34; }
308                        if ($hl == "t") // Horizontal layout?
309                                return "b"; // (b)oard required for vertical scrolling bar
310                       
311                        $height += 75; // header
312                        if (!IsMatchFirst($md, 'hidden')) { $height += 300; } // moves
313                }
314    }
315        return $height;
316}
317
318// Associate to each short name of options a long name that can be used as paramater of the [pgn prm=value] tag
319// @out:        $optName array of long names of options in alphabetic order, an alternative to the short names
320//                      $skipPrms other options (extendedoptions, height) to be skiped
321//                      $pgnPrms pgn options to indicate alternative (remote) source
322// @return      nothing
323function NameOptionPrm(&$optName, &$skipPrms, &$pgnPrms)
324{
325    // the [value by default] is in square brackets in first position in the list after //
326    $optName = array( // of long names of options, an alternative to the short names
327    'am'    => 'autoplayMode',           // [n]one |(g)ame | (l)oop
328    'bbch'  => 'boardBorderColor',       // [E0E0E0] was 000000
329    'bd'    => 'buttonsDisplay',         // [c]ustom, (h)idden, (s)tandard
330    'bch'   => 'backgroundColorHex',     // [F6F6F6] was FFFFFF, (t)ransparent to use the parent's background color
331    'bsch'  => 'boardShadowColor',       // [t]ransparent no shadow | (b)order | nbr
332    'cbch'  => 'controlBackgroundColor', // [F0F0F0] was standard buttons
333    'cd'    => 'commentsDisplay',        // [n]ewline, (i)nline,(h)idden
334    'ctch'  => 'controlTextColor',       // [696969] was 000000
335    'd'     => 'delay',                  // [3000] was 1000 in ms
336    'dch'   => 'darkColor',              // [E0E0E0] was C6CEC3
337    'fcch'  => 'fontCommentsColor',      // [000080]
338    'fcs'   => 'fontCommentsSize',       // [m]oves <- fontMovesSize
339    'fh'    => 'frameHeight',            // [""], (p)age, (b)oard, nbr, overriding textHeight. See FrameHeightEval()
340    'fhch'  => 'fontHeaderColor',        // [000000]
341    'fhs'   => 'fontHeaderSize',         // [14] was 16
342    'fmch'  => 'fontMovesColor',         // [000000]
343    'fms'   => 'fontMovesSize',          // [14] was 16
344    'fp'    => 'framePadding',           // [13] was 0
345    'fw'    => 'frameWidth',             // [p]age, (b)oard, nbr, overriding textHeight
346    'hch'   => 'highlightColor',         // [ABABAB] was DAF4D7
347    'hd'    => 'headerDisplay',          // [j]ustified | (h)idden | (c)entered | (l)ive | (v)ariations
348    'hl'    => 'horizontalLayout',       // [t]rue layout=[h]orizontal, (f)alse layout=(v)ertical
349    'hm'    => 'highlightMode',          // [b]order, (s)quare, (n)one
350    'hmch'  => 'highlightMovesColor',    // [E0E0E0] was DAF4D7 | (b)ackground for no highligh
351    'ig'    => 'initialGame',            // [f]irst | (l)ast | (r)andom
352    'ih'    => 'initialHalfmove',        // [s]tart | (e)nd | (r)andom | (c)omment | (v)ariation | nbr
353    'iv'    => 'initialVariation',       // [0]
354    'l'     => 'layout',                 // [h]orizontal, (v)ertical. See OptionParser()
355    'lch'   => 'lightColor',             // [F6F6F6] was EFF4EC. suffix 'h' stands for hex
356    'md'    => 'movesDisplay',           // [f]igurine | (t)ext | (p)uzzle | (h)idden | (l)ive
357    'pf'    => 'pieceFont',              // [d]efault based on pieceSize | (a)lpha | (m)erida | (u)scf | (s)vgchess
358    'ps'    => 'pieceSize',              // [d]efault <- squareSize
359    'rd'    => 'refreshDemo',            // [f]alse | (t)rue if md="t"
360    'rm'    => 'refreshMinutes',         // [1] minute if md="t"
361    'ss'    => 'squareSize',             // [26] was 28
362                                         // textHeight: nbr, optional if frameHeight
363    'tm'    => 'textMargin'              // [13] was 0. Set left/right margin width of the  textual section, header and/or moves text
364                                         // textWidth: nbr, optional if frameWidth
365                    );
366    $skipPrms = array( // of other options (extendedOptions, height) to be skiped
367    'eo'    => 'extendedOptions',        // [f]alse, (t)rue
368    'h'     => 'height'                  // [auto] = DEFAULT_HEIGHT. Height of the module
369                    );
370    $pgnPrms = array( // of pgn options to indicate alternative (remote) source
371    'fs'    => 'fenString',              // Initial position in format FEN
372    'pd'    => 'pgnData',                // URL of the PGN file
373    'pe'    => 'pgnEncoded',             // PGN game encoded
374    'pi'    => 'pgnId',                  // id of the pgnText textarea
375    'pt'    => 'pgnText'                 // PGN game
376                    );
377}
378?>
Note: See TracBrowser for help on using the repository browser.