xref: /plugin/combo/syntax/frontmatter.php (revision d5303bc578a280dce47412e1c1d4a1568ca9c663)
1<?php
2/**
3 * Front Matter implementation to add metadata
4 *
5 *
6 * that enhance the metadata dokuwiki system
7 * https://www.dokuwiki.org/metadata
8 * that use the Dublin Core Standard
9 * http://dublincore.org/
10 * by adding the front matter markup specification
11 * https://gerardnico.com/markup/front-matter
12 *
13 * Inspiration
14 * https://github.com/dokufreaks/plugin-meta/blob/master/syntax.php
15 * https://www.dokuwiki.org/plugin:semantic
16 *
17 * See also structured plugin
18 * https://www.dokuwiki.org/plugin:data
19 * https://www.dokuwiki.org/plugin:struct
20 *
21 */
22
23use ComboStrap\Analytics;
24use ComboStrap\LogUtility;
25use ComboStrap\PluginUtility;
26use ComboStrap\Page;
27
28require_once(__DIR__ . '/../class/Analytics.php');
29
30if (!defined('DOKU_INC')) {
31    die();
32}
33
34/**
35 * All DokuWiki plugins to extend the parser/rendering mechanism
36 * need to inherit from this class
37 *
38 * For a list of meta, see also https://ghost.org/docs/publishing/#api-data
39 */
40class syntax_plugin_combo_frontmatter extends DokuWiki_Syntax_Plugin
41{
42    const PARSING_STATE_EMPTY = "empty";
43    const PARSING_STATE_ERROR = "error";
44    const PARSING_STATE_SUCCESSFUL = "successful";
45
46    /**
47     * Syntax Type.
48     *
49     * Needs to return one of the mode types defined in $PARSER_MODES in parser.php
50     * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types
51     *
52     * Return an array of one or more of the mode types {@link $PARSER_MODES} in Parser.php
53     *
54     * baseonly - run only in the base
55     */
56    function getType()
57    {
58        return 'baseonly';
59    }
60
61    /**
62     * @see Doku_Parser_Mode::getSort()
63     * Higher number than the teaser-columns
64     * because the mode with the lowest sort number will win out
65     */
66    function getSort()
67    {
68        return 99;
69    }
70
71    /**
72     * Create a pattern that will called this plugin
73     *
74     * @param string $mode
75     * @see Doku_Parser_Mode::connectTo()
76     */
77    function connectTo($mode)
78    {
79        if ($mode == "base") {
80            // only from the top
81            $this->Lexer->addSpecialPattern('---json.*?---', $mode, PluginUtility::getModeForComponent($this->getPluginComponent()));
82        }
83    }
84
85    /**
86     *
87     * The handle function goal is to parse the matched syntax through the pattern function
88     * and to return the result for use in the renderer
89     * This result is always cached until the page is modified.
90     * @param string $match
91     * @param int $state
92     * @param int $pos
93     * @param Doku_Handler $handler
94     * @return array|bool
95     * @see DokuWiki_Syntax_Plugin::handle()
96     *
97     */
98    function handle($match, $state, $pos, Doku_Handler $handler)
99    {
100
101        if ($state == DOKU_LEXER_SPECIAL) {
102
103            global $ID;
104
105            // strip
106            //   from start `---json` + eol = 8
107            //   from end   `---` + eol = 4
108            $match = substr($match, 7, -3);
109
110            // Empty front matter
111            if (trim($match) == "") {
112                $this->closeParsing();
113                return array("state" => self::PARSING_STATE_EMPTY);
114            }
115
116            // Otherwise you get an object ie $arrayFormat-> syntax
117            $arrayFormat = true;
118            $json = json_decode($match, $arrayFormat);
119
120            // Decodage problem
121            if ($json == null) {
122                return array("state" => self::PARSING_STATE_ERROR);
123            }
124
125            // Trim it
126            $jsonKey = array_map('trim', array_keys($json));
127            // We will get a php warning here because the values may be an array
128            // and trim accept only string
129            $oldLevel = error_reporting(E_ERROR);
130            $jsonValues = array_map('trim', $json);
131            error_reporting($oldLevel);
132            $json = array_combine($jsonKey, $jsonValues);
133
134
135            $notModifiableMeta = [
136                "date",
137                "user",
138                "last_change",
139                "creator",
140                "contributor"
141            ];
142            $result = array();
143            foreach ($json as $key => $value) {
144
145                // Not modifiable metadata
146                if (in_array($key, $notModifiableMeta)) {
147                    LogUtility::msg("Front Matter: The metadata ($key) is a protected metadata and cannot be modified", LogUtility::LVL_MSG_WARNING);
148                    continue;
149                }
150
151                // Description is special
152                if ($key == "description") {
153                    $result["description"] = $value;
154                    p_set_metadata($ID, array("description" => array("abstract" => $value)));
155                    continue;
156                }
157
158                /**
159                 * Pass the title to the metadata
160                 * to advertise that it's in the front-matter
161                 * for the quality rules
162                 */
163                if ($key == Page::TITLE_PROPERTY) {
164                    $result[Page::TITLE_PROPERTY] = $value;
165                }
166
167                /**
168                 * Pass the low quality indicator
169                 * to advertise that it's in the front-matter
170                 */
171                if ($key == Page::LOW_QUALITY_PAGE_INDICATOR) {
172                    $result[Page::LOW_QUALITY_PAGE_INDICATOR] = $value;
173                }
174
175                // Canonical should be lowercase
176                if ($key == Page::CANONICAL_PROPERTY) {
177                    $result[Page::CANONICAL_PROPERTY] = $value;
178                    $value = strtolower($value);
179                }
180
181                // Set the value persistently
182                p_set_metadata($ID, array($key => $value));
183
184            }
185
186            $this->closeParsing($json);
187
188            $result["state"]= self::PARSING_STATE_SUCCESSFUL;
189
190            return $result;
191        }
192
193        return array();
194    }
195
196    /**
197     * Render the output
198     * @param string $format
199     * @param Doku_Renderer $renderer
200     * @param array $data - what the function handle() return'ed
201     * @return boolean - rendered correctly? (however, returned value is not used at the moment)
202     * @see DokuWiki_Syntax_Plugin::render()
203     *
204     *
205     */
206    function render($format, Doku_Renderer $renderer, $data)
207    {
208        // TODO: https://developers.google.com/search/docs/data-types/breadcrumb#breadcrumb-list
209        // News article: https://developers.google.com/search/docs/data-types/article
210        // News article: https://developers.google.com/search/docs/data-types/paywalled-content
211        // What is ?: https://developers.google.com/search/docs/data-types/qapage
212        // How to ?: https://developers.google.com/search/docs/data-types/how-to
213
214        switch ($format) {
215            case 'xhtml':
216                global $ID;
217                /** @var Doku_Renderer_xhtml $renderer */
218                $state = $data["state"];
219                if ($state == self::PARSING_STATE_ERROR) {
220                    LogUtility::msg("Front Matter: The json object for the page ($ID) is not valid", LogUtility::LVL_MSG_ERROR);
221                }
222                break;
223            case Analytics::RENDERER_FORMAT:
224                /** @var renderer_plugin_combo_analytics $renderer */
225                if (array_key_exists("description", $data)) {
226                    $renderer->setMeta("description", $data["description"]);
227                }
228                if (array_key_exists(Page::CANONICAL_PROPERTY, $data)) {
229                    $renderer->setMeta(Page::CANONICAL_PROPERTY, $data[Page::CANONICAL_PROPERTY]);
230                }
231                if (array_key_exists(Page::TITLE_PROPERTY, $data)) {
232                    $renderer->setMeta(Page::TITLE_PROPERTY, $data[Page::TITLE_PROPERTY]);
233                }
234                if (array_key_exists(Page::LOW_QUALITY_PAGE_INDICATOR, $data)) {
235                    $renderer->setMeta(Page::LOW_QUALITY_PAGE_INDICATOR, $data[Page::LOW_QUALITY_PAGE_INDICATOR]);
236                }
237                break;
238
239        }
240        return true;
241    }
242
243    /**
244     *
245     * @param array $json - The Json
246     * Delete the controlled meta that are no more present if they exists
247     * @return bool
248     */
249    public function closeParsing(array $json = array())
250    {
251        global $ID;
252
253        /**
254         * The managed meta with the exception of
255         * the {@link action_plugin_combo_metadescription::DESCRIPTION_META_KEY description}
256         * because it's already managed by dokuwiki in description['abstract']
257         */
258        $managedMeta = [
259            Page::CANONICAL_PROPERTY,
260            action_plugin_combo_metatitle::TITLE_META_KEY,
261            syntax_plugin_combo_disqus::META_DISQUS_IDENTIFIER
262        ];
263        $meta = p_read_metadata($ID);
264        foreach ($managedMeta as $metaKey) {
265            if (!array_key_exists($metaKey, $json)) {
266                if (isset($meta['persistent'][$metaKey])) {
267                    unset($meta['persistent'][$metaKey]);
268                }
269            }
270        }
271        return p_save_metadata($ID, $meta);
272    }
273
274
275}
276
277