xref: /plugin/combo/syntax/frontmatter.php (revision d5303bc578a280dce47412e1c1d4a1568ca9c663)
1007225e5Sgerardnico<?php
2007225e5Sgerardnico/**
3007225e5Sgerardnico * Front Matter implementation to add metadata
4007225e5Sgerardnico *
5007225e5Sgerardnico *
6007225e5Sgerardnico * that enhance the metadata dokuwiki system
7007225e5Sgerardnico * https://www.dokuwiki.org/metadata
8007225e5Sgerardnico * that use the Dublin Core Standard
9007225e5Sgerardnico * http://dublincore.org/
10007225e5Sgerardnico * by adding the front matter markup specification
11007225e5Sgerardnico * https://gerardnico.com/markup/front-matter
12007225e5Sgerardnico *
13007225e5Sgerardnico * Inspiration
14007225e5Sgerardnico * https://github.com/dokufreaks/plugin-meta/blob/master/syntax.php
15007225e5Sgerardnico * https://www.dokuwiki.org/plugin:semantic
16007225e5Sgerardnico *
17007225e5Sgerardnico * See also structured plugin
18007225e5Sgerardnico * https://www.dokuwiki.org/plugin:data
19007225e5Sgerardnico * https://www.dokuwiki.org/plugin:struct
20007225e5Sgerardnico *
21007225e5Sgerardnico */
22007225e5Sgerardnico
23007225e5Sgerardnicouse ComboStrap\Analytics;
24007225e5Sgerardnicouse ComboStrap\LogUtility;
25007225e5Sgerardnicouse ComboStrap\PluginUtility;
2671f916b9Sgerardnicouse ComboStrap\Page;
27007225e5Sgerardnico
28007225e5Sgerardnicorequire_once(__DIR__ . '/../class/Analytics.php');
29007225e5Sgerardnico
30007225e5Sgerardnicoif (!defined('DOKU_INC')) {
31007225e5Sgerardnico    die();
32007225e5Sgerardnico}
33007225e5Sgerardnico
34007225e5Sgerardnico/**
35007225e5Sgerardnico * All DokuWiki plugins to extend the parser/rendering mechanism
36007225e5Sgerardnico * need to inherit from this class
37*d5303bc5Sgerardnico *
38*d5303bc5Sgerardnico * For a list of meta, see also https://ghost.org/docs/publishing/#api-data
39007225e5Sgerardnico */
40007225e5Sgerardnicoclass syntax_plugin_combo_frontmatter extends DokuWiki_Syntax_Plugin
41007225e5Sgerardnico{
42007225e5Sgerardnico    const PARSING_STATE_EMPTY = "empty";
43007225e5Sgerardnico    const PARSING_STATE_ERROR = "error";
44007225e5Sgerardnico    const PARSING_STATE_SUCCESSFUL = "successful";
45007225e5Sgerardnico
46007225e5Sgerardnico    /**
47007225e5Sgerardnico     * Syntax Type.
48007225e5Sgerardnico     *
49007225e5Sgerardnico     * Needs to return one of the mode types defined in $PARSER_MODES in parser.php
50007225e5Sgerardnico     * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types
51007225e5Sgerardnico     *
52007225e5Sgerardnico     * Return an array of one or more of the mode types {@link $PARSER_MODES} in Parser.php
53007225e5Sgerardnico     *
54007225e5Sgerardnico     * baseonly - run only in the base
55007225e5Sgerardnico     */
56007225e5Sgerardnico    function getType()
57007225e5Sgerardnico    {
58007225e5Sgerardnico        return 'baseonly';
59007225e5Sgerardnico    }
60007225e5Sgerardnico
61007225e5Sgerardnico    /**
62007225e5Sgerardnico     * @see Doku_Parser_Mode::getSort()
63007225e5Sgerardnico     * Higher number than the teaser-columns
64007225e5Sgerardnico     * because the mode with the lowest sort number will win out
65007225e5Sgerardnico     */
66007225e5Sgerardnico    function getSort()
67007225e5Sgerardnico    {
68007225e5Sgerardnico        return 99;
69007225e5Sgerardnico    }
70007225e5Sgerardnico
71007225e5Sgerardnico    /**
72007225e5Sgerardnico     * Create a pattern that will called this plugin
73007225e5Sgerardnico     *
74007225e5Sgerardnico     * @param string $mode
75007225e5Sgerardnico     * @see Doku_Parser_Mode::connectTo()
76007225e5Sgerardnico     */
77007225e5Sgerardnico    function connectTo($mode)
78007225e5Sgerardnico    {
79007225e5Sgerardnico        if ($mode == "base") {
80007225e5Sgerardnico            // only from the top
81007225e5Sgerardnico            $this->Lexer->addSpecialPattern('---json.*?---', $mode, PluginUtility::getModeForComponent($this->getPluginComponent()));
82007225e5Sgerardnico        }
83007225e5Sgerardnico    }
84007225e5Sgerardnico
85007225e5Sgerardnico    /**
86007225e5Sgerardnico     *
87007225e5Sgerardnico     * The handle function goal is to parse the matched syntax through the pattern function
88007225e5Sgerardnico     * and to return the result for use in the renderer
89007225e5Sgerardnico     * This result is always cached until the page is modified.
90007225e5Sgerardnico     * @param string $match
91007225e5Sgerardnico     * @param int $state
92007225e5Sgerardnico     * @param int $pos
93007225e5Sgerardnico     * @param Doku_Handler $handler
94007225e5Sgerardnico     * @return array|bool
95007225e5Sgerardnico     * @see DokuWiki_Syntax_Plugin::handle()
96007225e5Sgerardnico     *
97007225e5Sgerardnico     */
98007225e5Sgerardnico    function handle($match, $state, $pos, Doku_Handler $handler)
99007225e5Sgerardnico    {
100007225e5Sgerardnico
101007225e5Sgerardnico        if ($state == DOKU_LEXER_SPECIAL) {
102007225e5Sgerardnico
103007225e5Sgerardnico            global $ID;
104007225e5Sgerardnico
105007225e5Sgerardnico            // strip
106007225e5Sgerardnico            //   from start `---json` + eol = 8
107007225e5Sgerardnico            //   from end   `---` + eol = 4
108007225e5Sgerardnico            $match = substr($match, 7, -3);
109007225e5Sgerardnico
110007225e5Sgerardnico            // Empty front matter
111007225e5Sgerardnico            if (trim($match) == "") {
112007225e5Sgerardnico                $this->closeParsing();
113007225e5Sgerardnico                return array("state" => self::PARSING_STATE_EMPTY);
114007225e5Sgerardnico            }
115007225e5Sgerardnico
116007225e5Sgerardnico            // Otherwise you get an object ie $arrayFormat-> syntax
117007225e5Sgerardnico            $arrayFormat = true;
118007225e5Sgerardnico            $json = json_decode($match, $arrayFormat);
119007225e5Sgerardnico
120007225e5Sgerardnico            // Decodage problem
121007225e5Sgerardnico            if ($json == null) {
122007225e5Sgerardnico                return array("state" => self::PARSING_STATE_ERROR);
123007225e5Sgerardnico            }
124007225e5Sgerardnico
125007225e5Sgerardnico            // Trim it
126007225e5Sgerardnico            $jsonKey = array_map('trim', array_keys($json));
127007225e5Sgerardnico            // We will get a php warning here because the values may be an array
128007225e5Sgerardnico            // and trim accept only string
129007225e5Sgerardnico            $oldLevel = error_reporting(E_ERROR);
130007225e5Sgerardnico            $jsonValues = array_map('trim', $json);
131007225e5Sgerardnico            error_reporting($oldLevel);
132007225e5Sgerardnico            $json = array_combine($jsonKey, $jsonValues);
133007225e5Sgerardnico
134007225e5Sgerardnico
135007225e5Sgerardnico            $notModifiableMeta = [
136007225e5Sgerardnico                "date",
137007225e5Sgerardnico                "user",
138007225e5Sgerardnico                "last_change",
139007225e5Sgerardnico                "creator",
140007225e5Sgerardnico                "contributor"
141007225e5Sgerardnico            ];
142007225e5Sgerardnico            $result = array();
143007225e5Sgerardnico            foreach ($json as $key => $value) {
144007225e5Sgerardnico
145007225e5Sgerardnico                // Not modifiable metadata
146007225e5Sgerardnico                if (in_array($key, $notModifiableMeta)) {
147007225e5Sgerardnico                    LogUtility::msg("Front Matter: The metadata ($key) is a protected metadata and cannot be modified", LogUtility::LVL_MSG_WARNING);
148007225e5Sgerardnico                    continue;
149007225e5Sgerardnico                }
150007225e5Sgerardnico
151007225e5Sgerardnico                // Description is special
152007225e5Sgerardnico                if ($key == "description") {
153007225e5Sgerardnico                    $result["description"] = $value;
154007225e5Sgerardnico                    p_set_metadata($ID, array("description" => array("abstract" => $value)));
155007225e5Sgerardnico                    continue;
156007225e5Sgerardnico                }
157007225e5Sgerardnico
158f3748b38Sgerardnico                /**
1599b9e6d1fSgerardnico                 * Pass the title to the metadata
160f3748b38Sgerardnico                 * to advertise that it's in the front-matter
161f3748b38Sgerardnico                 * for the quality rules
162f3748b38Sgerardnico                 */
163f3748b38Sgerardnico                if ($key == Page::TITLE_PROPERTY) {
164f3748b38Sgerardnico                    $result[Page::TITLE_PROPERTY] = $value;
165f3748b38Sgerardnico                }
166f3748b38Sgerardnico
1679b9e6d1fSgerardnico                /**
1689b9e6d1fSgerardnico                 * Pass the low quality indicator
1699b9e6d1fSgerardnico                 * to advertise that it's in the front-matter
1709b9e6d1fSgerardnico                 */
1719b9e6d1fSgerardnico                if ($key == Page::LOW_QUALITY_PAGE_INDICATOR) {
1729b9e6d1fSgerardnico                    $result[Page::LOW_QUALITY_PAGE_INDICATOR] = $value;
1739b9e6d1fSgerardnico                }
1749b9e6d1fSgerardnico
175007225e5Sgerardnico                // Canonical should be lowercase
17671f916b9Sgerardnico                if ($key == Page::CANONICAL_PROPERTY) {
17771f916b9Sgerardnico                    $result[Page::CANONICAL_PROPERTY] = $value;
178007225e5Sgerardnico                    $value = strtolower($value);
179007225e5Sgerardnico                }
180007225e5Sgerardnico
181007225e5Sgerardnico                // Set the value persistently
182007225e5Sgerardnico                p_set_metadata($ID, array($key => $value));
183007225e5Sgerardnico
184007225e5Sgerardnico            }
185007225e5Sgerardnico
186007225e5Sgerardnico            $this->closeParsing($json);
187007225e5Sgerardnico
188007225e5Sgerardnico            $result["state"]= self::PARSING_STATE_SUCCESSFUL;
189007225e5Sgerardnico
190007225e5Sgerardnico            return $result;
191007225e5Sgerardnico        }
192007225e5Sgerardnico
193007225e5Sgerardnico        return array();
194007225e5Sgerardnico    }
195007225e5Sgerardnico
196007225e5Sgerardnico    /**
197007225e5Sgerardnico     * Render the output
198007225e5Sgerardnico     * @param string $format
199007225e5Sgerardnico     * @param Doku_Renderer $renderer
200007225e5Sgerardnico     * @param array $data - what the function handle() return'ed
201007225e5Sgerardnico     * @return boolean - rendered correctly? (however, returned value is not used at the moment)
202007225e5Sgerardnico     * @see DokuWiki_Syntax_Plugin::render()
203007225e5Sgerardnico     *
204007225e5Sgerardnico     *
205007225e5Sgerardnico     */
206007225e5Sgerardnico    function render($format, Doku_Renderer $renderer, $data)
207007225e5Sgerardnico    {
208007225e5Sgerardnico        // TODO: https://developers.google.com/search/docs/data-types/breadcrumb#breadcrumb-list
209007225e5Sgerardnico        // News article: https://developers.google.com/search/docs/data-types/article
210007225e5Sgerardnico        // News article: https://developers.google.com/search/docs/data-types/paywalled-content
211007225e5Sgerardnico        // What is ?: https://developers.google.com/search/docs/data-types/qapage
212007225e5Sgerardnico        // How to ?: https://developers.google.com/search/docs/data-types/how-to
213007225e5Sgerardnico
214007225e5Sgerardnico        switch ($format) {
215007225e5Sgerardnico            case 'xhtml':
216007225e5Sgerardnico                global $ID;
217007225e5Sgerardnico                /** @var Doku_Renderer_xhtml $renderer */
218007225e5Sgerardnico                $state = $data["state"];
219007225e5Sgerardnico                if ($state == self::PARSING_STATE_ERROR) {
220007225e5Sgerardnico                    LogUtility::msg("Front Matter: The json object for the page ($ID) is not valid", LogUtility::LVL_MSG_ERROR);
221007225e5Sgerardnico                }
222007225e5Sgerardnico                break;
223007225e5Sgerardnico            case Analytics::RENDERER_FORMAT:
224007225e5Sgerardnico                /** @var renderer_plugin_combo_analytics $renderer */
225007225e5Sgerardnico                if (array_key_exists("description", $data)) {
226007225e5Sgerardnico                    $renderer->setMeta("description", $data["description"]);
227007225e5Sgerardnico                }
22871f916b9Sgerardnico                if (array_key_exists(Page::CANONICAL_PROPERTY, $data)) {
22971f916b9Sgerardnico                    $renderer->setMeta(Page::CANONICAL_PROPERTY, $data[Page::CANONICAL_PROPERTY]);
230007225e5Sgerardnico                }
231f3748b38Sgerardnico                if (array_key_exists(Page::TITLE_PROPERTY, $data)) {
232f3748b38Sgerardnico                    $renderer->setMeta(Page::TITLE_PROPERTY, $data[Page::TITLE_PROPERTY]);
233f3748b38Sgerardnico                }
2349b9e6d1fSgerardnico                if (array_key_exists(Page::LOW_QUALITY_PAGE_INDICATOR, $data)) {
2359b9e6d1fSgerardnico                    $renderer->setMeta(Page::LOW_QUALITY_PAGE_INDICATOR, $data[Page::LOW_QUALITY_PAGE_INDICATOR]);
2369b9e6d1fSgerardnico                }
237007225e5Sgerardnico                break;
238007225e5Sgerardnico
239007225e5Sgerardnico        }
240007225e5Sgerardnico        return true;
241007225e5Sgerardnico    }
242007225e5Sgerardnico
243007225e5Sgerardnico    /**
244007225e5Sgerardnico     *
245007225e5Sgerardnico     * @param array $json - The Json
246007225e5Sgerardnico     * Delete the controlled meta that are no more present if they exists
247007225e5Sgerardnico     * @return bool
248007225e5Sgerardnico     */
249007225e5Sgerardnico    public function closeParsing(array $json = array())
250007225e5Sgerardnico    {
251007225e5Sgerardnico        global $ID;
252007225e5Sgerardnico
253007225e5Sgerardnico        /**
254007225e5Sgerardnico         * The managed meta with the exception of
255007225e5Sgerardnico         * the {@link action_plugin_combo_metadescription::DESCRIPTION_META_KEY description}
256007225e5Sgerardnico         * because it's already managed by dokuwiki in description['abstract']
257007225e5Sgerardnico         */
258007225e5Sgerardnico        $managedMeta = [
25971f916b9Sgerardnico            Page::CANONICAL_PROPERTY,
260007225e5Sgerardnico            action_plugin_combo_metatitle::TITLE_META_KEY,
261007225e5Sgerardnico            syntax_plugin_combo_disqus::META_DISQUS_IDENTIFIER
262007225e5Sgerardnico        ];
263007225e5Sgerardnico        $meta = p_read_metadata($ID);
264007225e5Sgerardnico        foreach ($managedMeta as $metaKey) {
265007225e5Sgerardnico            if (!array_key_exists($metaKey, $json)) {
266007225e5Sgerardnico                if (isset($meta['persistent'][$metaKey])) {
267007225e5Sgerardnico                    unset($meta['persistent'][$metaKey]);
268007225e5Sgerardnico                }
269007225e5Sgerardnico            }
270007225e5Sgerardnico        }
271007225e5Sgerardnico        return p_save_metadata($ID, $meta);
272007225e5Sgerardnico    }
273007225e5Sgerardnico
274007225e5Sgerardnico
275007225e5Sgerardnico}
276007225e5Sgerardnico
277