diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..cd5a6f3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +root = true + +[*] +indent_style = tab +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false + +[*.yml] +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a028c2e --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# bitplan's skin +bitplan +/vendor +*.swp +/composer.lock diff --git a/.phpcs.xml b/.phpcs.xml new file mode 100644 index 0000000..8db7ad4 --- /dev/null +++ b/.phpcs.xml @@ -0,0 +1,7 @@ + + + + src + + + diff --git a/S5SlideShow.class.php b/S5SlideShow.class.php deleted file mode 100644 index 6b5bf28..0000000 --- a/S5SlideShow.class.php +++ /dev/null @@ -1,742 +0,0 @@ - - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - */ - -/** - * @author Vitaliy Filippov - * @package MediaWiki - * @subpackage Extensions - */ - -if (!defined('MEDIAWIKI')) - die(); - -class S5SlideShow -{ - var $sTitle, $sArticle, $pageContent; - - var $slideParser, $parserOptions; - static $slideno = 0; - - var $slides, $css, $attr; - - /** - * Constructor. If $attr is not an array(), article content - * will be parsed and attributes will be taken from there. - */ - function __construct($sTitle, $sContent = NULL, $attr = NULL) - { - if (!is_object($sTitle)) - { - wfDebug(__CLASS__.": Error! Pass a title object, NOT a title string!\n"); - return false; - } - $this->sArticle = new Article($sTitle); - $this->sTitle = $sTitle; - if ($sContent) - $this->pageContent = $sContent; - else - $this->pageContent = $this->sArticle->getContent(); - $this->attr = array(); - if (is_array($attr)) - $this->setAttributes($attr); - } - - /** - * Parse attribute hash and save them into $this - */ - function setAttributes($attr) - { - global $wgContLang, $wgUser, $egS5SlideHeadingMark, $egS5SlideIncMark, $egS5SlideCenterMark, $egS5DefaultStyle, $egS5Scaled; - // Get attributes from tag content - if (preg_match_all('/(?:^|\n)\s*;\s*([^:\s]*)\s*:\s*([^\n]*)/is', $attr['content'], $m, PREG_SET_ORDER) > 0) - foreach ($m as $set) - $attr[$set[1]] = trim($set[2]); - // Default values - $attr = $attr + array( - 'title' => $this->sTitle->getText(), - 'subtitle' => '', - 'footer' => $this->sTitle->getText(), - 'headingmark' => $egS5SlideHeadingMark, - 'incmark' => $egS5SlideIncMark, - 'centermark' => $egS5SlideCenterMark, - 'style' => $egS5DefaultStyle, - 'font' => '', - // Backwards compatibility: appended CSS - 'addcss' => '', - // Is each slide to be scaled independently? - 'scaled' => $egS5Scaled, - ); - // Boolean value - $attr['scaled'] = $attr['scaled'] == 'true' || $attr['scaled'] == 'yes' || $attr['scaled'] == 1; - // Default author = last revision's author - if (!isset($attr['author'])) - { - $attr['author'] = $wgUser; - if ($this->sArticle){ - try{ - $u = $wgUser; - if ($this->sArticle->mRevision) { - $u = $this->sArticle->getLastNAuthors(1); - $u = $u[0]; - } - if (!is_object($u)) - $u = User::newFromName($u); - if (!is_object($u)) - $u = $wgUser; - $attr['author'] = $u->getRealName(); - } catch (Exception $e) {} - } - } - // Author and date in the subfooter by default - if (!isset($attr['subfooter'])) - { - $attr['subfooter'] = $attr['author']; - if ($attr['subfooter']) - $attr['subfooter'] .= ', '; - $attr['subfooter'] .= $wgContLang->timeanddate($this->sArticle->getTimestamp(), true); - } - else - { - $attr['subfooter'] = str_ireplace( - '{{date}}', - $wgContLang->timeanddate($this->sArticle->getTimestamp(), true), - $attr['subfooter'] - ); - } - $this->attr = $attr + $this->attr; - } - - /** - * Checks heading text for headingmark, incmark, centermark - * Returns NULL or array(title, incremental, centered) - */ - function check_slide_heading($node) - { - $ot = trim($node->nodeValue, "= \n\r\t\v"); - $st = $this->attr['headingmark'] ? preg_replace($this->heading_re, '', $ot) : $ot; - if (!$this->attr['headingmark'] || $st != $ot) - { - $inc = false; - $center = false; - if ($this->inc_re) - { - $t = preg_replace($this->inc_re, '', $st); - if ($t != $st) - { - $inc = true; - $st = $t; - } - } - if ($this->center_re) - { - $t = preg_replace($this->center_re, '', $st); - if ($t != $st) - { - $center = true; - $st = $t; - } - } - return array($st, $inc, $center); - } - return NULL; - } - - /** - * This function transforms section slides into tags - */ - function transform_section_slides($content) - { - wfProfileIn(__METHOD__); - $p = $this->getParser(); - $content = $p->preprocess($content, $this->sTitle, $this->parserOptions); - $p->setOutputType(Parser::OT_WIKI); - $node = $p->getPreprocessor()->preprocessToObj($content); - if (!$node instanceof PPNode_DOM) - { - die("S5SlideShow extension requires DOM support and usage of DOM MediaWiki preprocessor"); - } - $doc = $node->node->ownerDocument; - $all = $node->node->childNodes; - if ($this->attr['headingmark']) - { - $this->heading_re = '/'.str_replace('/', '\\/', preg_quote($this->attr['headingmark'])).'/'; - } - if ($this->attr['incmark']) - { - $this->inc_re = '/'.str_replace('/', '\\/', preg_quote($this->attr['incmark'])).'/'; - } - if ($this->attr['centermark']) - { - $this->center_re = '/'.str_replace('/', '\\/', preg_quote($this->attr['centermark'])).'/'; - } - for ($i = 0; $i < $all->length; $i++) - { - $c = $all->item($i); - if ($c->nodeName == 'h' && ($st = $this->check_slide_heading($c)) !== NULL) - { - /** - * Add slidesATTRSCONTENT - */ - $slide = $doc->createElement('ext'); - $e = $doc->createElement('name'); - $e->nodeValue = 'slides'; - $slide->appendChild($e); - $e = $doc->createElement('attr'); - $v = ' title="'.htmlspecialchars($st[0]).'"'; - if ($st[1]) - $v .= ' incremental="1"'; - if ($st[2]) - $v .= ' center="1"'; - $e->nodeValue = htmlspecialchars($v); - $slide->appendChild($e); - $e = $doc->createElement('inner'); - $slide->appendChild($e); - $e1 = $doc->createElement('close'); - $e1->nodeValue = "\n"; - $slide->appendChild($e1); - $node->node->insertBefore($slide, $c); - $node->node->removeChild($c); - // Move children of $node to $e, up to first - // with name="slide(s)" or heading of same or greater level - for ($j = $i+1; $j < $all->length; ) - { - $d = $all->item($j); - if ($d->nodeName == 'h') - { - break; - } - if ($d->nodeName == 'ext') - { - $name = $d->getElementsByTagName('name'); - if (count($name) != 1) - { - die(__METHOD__.': Internal error, without in DOM text'); - } - if (substr($name->item(0)->nodeValue, 0, 5) == 'slide') - { - break; - } - } - $node->node->removeChild($d); - $e->appendChild($d); - } - } - } - $frame = $p->getPreprocessor()->newFrame(); - $text = $frame->expand($node, PPFrame::RECOVER_ORIG); - $text = $frame->parser->mStripState->unstripBoth($text); - wfProfileOut(__METHOD__); - return $text; - } - - /** - * Extract slides from wiki-text $content - */ - function loadContent($content = NULL) - { - global $wgParser, $wgUser; - if ($content === NULL) - { - $content = $this->pageContent; - } - $this->getParser(); - $p = new Parser; - $p->extS5 = $this; - $p->extS5Hooks = 'parse'; - $p->parse($content, $this->sTitle, $this->parserOptions); - if ($this->attr['headingmark'] !== false) - { - $content = $this->transform_section_slides($content); - } - $this->slides = array(); - $this->css = array(); - $this->parse($content); - foreach ($this->slides as &$slide) - { - $slide['content_html'] = $this->parse($slide['content']); - if ($slide['title']) - { - $slide['title_html'] = $this->parse($slide['title'], true); - } - else - { - $slide['title_html'] = ''; - } - } - return $this->slides; - } - - /** - * Parse slide content using a copy of $wgParser, - * save slides and slide stylesheets into $this and return resulting HTML - */ - function parse($text, $inline = false, $title = NULL) - { - global $wgOut; - if (!$title) - $title = $this->sTitle; - $text = str_replace("__TOC__", '', trim($text)); - $prev = S5SlideShowHooks::$parsingSlide; - S5SlideShowHooks::$parsingSlide = true; - $output = $this->getParser()->parse( - "$text __NOTOC__ __NOEDITSECTION__", $title, - $this->parserOptions, !$inline, false - ); - S5SlideShowHooks::$parsingSlide = $prev; - $wgOut->addParserOutput($output); - return $output->getText(); - } - - // Create parser object for $this->parse() - function getParser() - { - if ($this->slideParser) - return $this->slideParser; - global $wgParser, $wgUser; - $this->parserOptions = ParserOptions::newFromUser($wgUser); - $this->parserOptions->setEditSection(false); - $this->parserOptions->setNumberHeadings(false); - $this->parserOptions->enableLimitReport(false); - // Since $this->parse() is only used in ?action=slide, - // we can use it directly without cloning or creating a new object - // But $wgParser may be a StubObject, so trigger unstub and first call init - $wgParser->parse(" ", $this->sTitle, $this->parserOptions, false, true); - $wgParser->setHook('slideshow', 'S5SlideShow::empty_tag_hook'); - $wgParser->setHook('slide', 'S5SlideShow::empty_tag_hook'); - $wgParser->setHook('slides', array($this, 'slides_parse')); - $wgParser->setHook('slidecss', array($this, 'slidecss_parse')); - $wgParser->mShowToc = false; - return $this->slideParser = $wgParser; - } - - function getHeadItems() - { - // Extract loader scripts and styles from OutputPage::headElement() - global $wgOut; - $wgOut->getContext()->setSkin($skin = new SkinApiClean()); - $s = $wgOut->headElement($skin); - preg_match_all('/]*>.*?<\/script>|'. - ']*rel="stylesheet"[^<>]*>|'. - ']*name="ResourceLoaderDynamicStyles"[^<>]*>/is', $s, $m, PREG_PATTERN_ORDER); - return implode("\n", array_filter($m[0], function($s) { return strpos($s, 'commonPrint') === false; })); - } - - /** - * Generate presentation HTML code - */ - function genSlideFile($printPageSize = false) - { - global $egS5SlideTemplateFile; - global $wgUser, $wgContLang, $wgOut; - - if (!$this->slides) - $this->loadContent(); - - // load template contents - $slide_template = @file_get_contents($egS5SlideTemplateFile); - if (!$slide_template) - return false; - - // build replacement array for slide show template - $replace = array(); - foreach(explode(' ', 'title subtitle author footer subfooter addcss') as $v) - $replace["[$v]"] = $this->parse($this->attr[$v], true); - $replace['[addcss]'] = implode("\n", $this->css); - if ($this->attr['font']) - $replace['[addcss]'] .= "\n.slide, div.header, div.footer { font-family: {$this->attr['font']}; }"; - $replace['[addcss]'] = strip_tags($replace['[addcss]']); - $replace['[addscript]'] = ''; - $replace['[style]'] = $this->attr['style']; - $replace['[styleurl]'] = 'index.php?action=slide&s5skin='.$this->attr['style'].'&s5css=1'; - $replace['[pageid]'] = $this->sArticle->getID(); - $replace['[scaled]'] = $this->attr['scaled'] ? 'true' : 'false'; - $replace['[defaultView]'] = 'slideshow'; - - if ($printPageSize) - { - // Default DPI - $dpi = 96; - $replace['[styleurl]'] .= '&print='.implode('x', $printPageSize = array_map('intval', $printPageSize)); - $replace['[addcss]'] .= '@page {size: '.$printPageSize[0].'mm '.$printPageSize[1]."mm;}\n". - '.body {width: '.($w = floor($printPageSize[0]*$dpi/25.4)). - 'px; height: '.($h = floor($printPageSize[1]*$dpi/25.4))."px;}\n"; - $replace['[addscript]'] .= "var s5PrintPageSize = [ $w, $h ];\n"; - $replace['[defaultView]'] = 'print'; - } - - $slides_html = ''; - $slide0 = " visible"; - if (trim($replace['[author]']) !== '' && trim($replace['[title]']) !== '') - { - $slides_html .= '

'.$replace['[title]']. - '

'. - $replace['[subtitle]'].'

'.$replace['[author]'].'

'; - $slide0 = ''; - } - foreach ($this->slides as $slide) - { - $c = $slide['content_html']; - $t = $slide['title_html']; - // make slide lists incremental if needed - if ($slide['incremental']) - { - $c = str_replace('
    ', '
      ', $c); - $c = str_replace('
        ', '
          ', $c); - } - $c = "
          $c
          "; - if (trim(strip_tags($t))) - { - $center = $slide['center'] ? " notitle" : ""; - $slides_html .= "

          $t

          $c
          \n"; - } - else - $slides_html .= "
          $c
          \n"; - $slide0 = ""; - } - - // substitute values - $replace['[headitems]'] = $this->getHeadItems(); - $replace['[content]'] = $slides_html; - $html = str_replace( - array_keys($replace), - array_values($replace), - $slide_template - ); - $html = $wgContLang->convert($html); - - // output generated content - $wgOut->disable(); - header("Content-Type: text/html; charset=utf-8"); - echo $html; - } - - /** - * Function to replace URLs in S5 skin stylesheet - * $m is the match array coming from preg_replace_callback - */ - static function styleReplaceUrl($skin, $m) - { - $t = Title::newFromText($m[1], NS_FILE); - $f = wfLocalFile($t); - if ($f->exists()) - return 'url('.$f->getFullUrl().')'; - // FIXME remove hardcode extensions/S5SlideShow/ - // Replace images with invalid names with blank.gif - if (preg_match('/[^a-z0-9_\-\.]/is', $m[1])) - return 'url(extensions/S5SlideShow/blank.gif)'; - return "url(extensions/S5SlideShow/$skin/".$m[1].')'; - } - - /** - * Generate CSS stylesheet for a given S5 skin - * TODO cache generated stylesheets and flush the cache after saving style articles - */ - static function genStyle($skin, $print = false) - { - global $wgOut; - $dir = dirname(__FILE__); - $css = ''; - if ($print) - { - S5SlideShowHooks::$styles['print'] = 'print.css'; - } - foreach (S5SlideShowHooks::$styles as $k => $file) - { - $title = Title::newFromText("S5/$skin/$k", NS_MEDIAWIKI); - if ($title->exists()) - { - $a = new Article($title); - $c = $a->getContent(); - } - else - $c = @file_get_contents("$dir/".str_replace('$skin', $skin, $file)); - $c = preg_replace_callback('#url\(([^\)]*)\)#is', create_function('$m', 'return S5SlideShow::styleReplaceUrl("'.$skin.'", $m);'), $c); - $css .= $c; - } - $wgOut->disable(); - header("Content-Type: text/css"); - echo $css; - } - - /** - * - article view mode, backwards compatibility - */ - static function slideshow_legacy($content, $attr, $parser, $frame = NULL) - { - return self::slideshow_view($content, $attr, $parser, $frame, - '
          Warning: legacy <slide>'. - ' parser hook used, change it to <slideshow> please
          ' - ); - } - - /** - * Parse content using an existing parser and cloned options - * without LimitReport, without EditSections - */ - static function clone_options_parse($content, $parser, $inline = false) - { - if (!$parser->mTitle) - { - wfDebug(__METHOD__.": no title object in parser\n"); - return ''; - } - $oldOpt = $parser->mOptions; - if (!isset($parser->extClonedOptions)) - { - if (!$oldOpt) - { - global $wgUser; - $oldOpt = ParserOptions::newFromUser($wgUser); - } - $opt = clone $oldOpt; - $opt->enableLimitReport(false); - $opt->setEditSection(false); - $parser->extClonedOptions = $opt; - } - $html = $parser->parse($content, $parser->mTitle, $parser->extClonedOptions, !$inline, false)->getText(); - $parser->mOptions = $oldOpt; - return $html; - } - - /** - * - article view mode - * displays a link to the slideshow and skin preview - */ - static function slideshow_view($content, $attr, $parser, $frame = NULL, $addmsg = '') - { - global $wgScriptPath, $wgParser, $wgContLang; - if (!$parser->mTitle) - { - wfDebug(__METHOD__.": no title object in parser\n"); - return ''; - } - // Create slideshow object - $attr['content'] = $content; - $slideShow = new S5SlideShow($parser->mTitle, NULL, $attr); - $content = ''; - $article = new Article($parser->mTitle); - foreach (array('title', 'subtitle', 'author', 'footer', 'subfooter') as $key) - { - if (isset($slideShow->attr[$key]) && $slideShow->attr[$key] != '') - { - $value = $slideShow->attr[$key]; - if (mb_strpos($value, "{{date}}") !== false) - { - $value = str_ireplace( - '{{date}}', - $wgContLang->timeanddate($article->getTimestamp(), true), - $value - ); - } - $content .= "\n;" . wfMsg('s5slide-header-' . $key) . ': '. $value; - } - } - // FIXME remove hardcoded '.png', /extensions/S5SlideShow/, "Slide Show" - $url = htmlspecialchars($parser->mTitle->getLocalUrl(array('action' => 'slide'))); - $style_title = Title::newFromText('S5-'.$slideShow->attr['style'].'-preview.png', NS_FILE); - if ($style_title && - ($style_preview = wfLocalFile($style_title)) && - $style_preview->exists()) - { - $style_preview = $style_preview->getTitle()->getPrefixedText(); - $style_preview = self::clone_options_parse("[[$style_preview|240px|link=]]", $wgParser, true); - } - else - { - $style_preview = 'Slide Show'; - } - $inside = self::clone_options_parse($content, $wgParser, true); - $html = - ''. - ''. - ''. - $inside; - if (!empty($slideShow->attr['font'])) - { - $html = '' . $html; - } - $html = '
          ' . $html . '
          '; - return $html; - } - - // - slideshow parse mode - // saves parameters into $this - function slideshow_parse($content, $attr, $parser) - { - $attr['content'] = $content; - $this->setAttributes($attr); - return ''; - } - - // - article view mode - static function slides_view($content, $attr, $parser) - { - if ($attr['split']) - $slides = preg_split('/'.str_replace('/', '\\/', $attr['split']).'/', $content); - else - $slides = array($content); - $html = ''; - $style = ''; - if (!isset($attr['float'])) - $style .= "float: left; "; - if (isset($attr['width'])) - $style .= "width: $attr[width]px; "; - if ($style) - $style = " style='$style'"; - foreach ($slides as $i => $slide) - { - if (isset($attr['title']) && !$i) - { - $slide = "== $attr[title] ==\n".trim($slide); - $st = 'slide withtitle'; - } - else - $st = 'slide'; - $output = self::clone_options_parse(trim($slide), $parser, false); - $html .= '
          '.$output.'
          '; - } - if (!isset($attr['float'])) - $html .= '
          '; - else - $html = "
          $html
          "; - return $html; - } - - // - slideshow parse mode - function slides_parse($content, $attr, $parser) - { - if (isset($attr['split'])) - $slides = preg_split('/'.str_replace('/', '\\/', $attr['split']).'/', $content); - else - $slides = array($content); - foreach ($slides as $c) - { - $this->slides[] = array('content' => trim($c)) + $attr + array( - 'title' => '', - 'incremental' => false, - 'center' => false, - ); - unset($attr['title']); - } - } - - // - article view mode - static function slidecss_view($content, $attr, $parser) - { - // use this CSS only for - if ($attr['view'] == 'true' || $attr['view'] == '1') - $parser->mOutput->addHeadItem(''); - return ''; - } - - // - slideshow parse mode - function slidecss_parse($content, $attr, $parser) - { - $this->css[] = $content; - } - - // stub for tag hooks - static function empty_tag_hook() - { - return ''; - } - - /** - * Check whether $haystack begins or ends with $needle, and if yes, - * remove $needle from it and return true. - */ - static function strCheck(&$haystack, $needle) - { - $needle = mb_strtolower($needle); - if (mb_strtolower(mb_substr($haystack, 0, mb_strlen($needle))) == $needle) - $haystack = trim(mb_substr($haystack, mb_strlen($needle))); - elseif (mb_strtolower(mb_substr($haystack, -mb_strlen($needle))) == $needle) - $haystack = trim(mb_substr($haystack, 0, -mb_strlen($needle))); - else - return false; - return true; - } -} - -// Used to display CSS files instead of non-existing special articles (MediaWiki:S5//) -class S5SkinArticle extends Article -{ - var $s5skin, $s5file; - // Create the object and remember s5skin and s5file - public function __construct($title, $s5skin, $s5file) - { - $this->mPage = $this->newPage( $title ); - $this->mOldId = NULL; - $this->s5skin = $s5skin; - $this->s5file = $s5file; - } - // Get content from the file - public function getContent() - { - if ($this->getID() == 0) - $this->mContent = @file_get_contents($this->s5file); - else - $this->loadContent(); - return $this->mContent; - } - // Show default content from the file - public function showMissingArticle() - { - global $wgOut, $wgRequest, $wgParser; - // Copy-paste from includes/Article.php: - // Show delete and move logs - LogEventsList::showLogExtract( $wgOut, array( 'delete', 'move' ), $this->mTitle->getPrefixedText(), '', - array( 'lim' => 10, - 'conds' => array( "log_action != 'revision'" ), - 'showIfEmpty' => false, - 'msgKey' => array( 'moveddeleted-notice' ) ) - ); - // Show error message - $oldid = $this->getOldID(); - if ($oldid) - { - $text = wfMsgNoTrans( - 'missing-article', $this->mTitle->getPrefixedText(), - wfMsgNoTrans('missingarticle-rev', $oldid) - ); - } - else - $text = $this->getContent(); - if ($wgParser->mTagHooks['source']) - $text = "\n$text\n"; - $text = "
          \n$text\n
          "; - $wgOut->addWikiText($text); - } -} - -class SkinApiClean extends SkinApi -{ - public function setupSkinUserCss(OutputPage $out) - { - SkinTemplate::setupSkinUserCss($out); - } -} diff --git a/S5SlideShow.i18n.php b/S5SlideShow.i18n.php deleted file mode 100644 index 20cf919..0000000 --- a/S5SlideShow.i18n.php +++ /dev/null @@ -1,26 +0,0 @@ - array('1', 'S5SLIDESHOW'), -); - -$messages = array(); - -$messages['en'] = array( - 's5slide-header-title' => 'Title', - 's5slide-header-subtitle' => 'Subtitle', - 's5slide-header-footer' => 'Footer', - 's5slide-header-subfooter' => 'Subfooter', - 's5slide-header-author' => 'Author', -); - -$messages['ru'] = array( - 's5slide-header-title' => 'Заголовок', - 's5slide-header-subtitle' => 'Подзаголовок', - 's5slide-header-footer' => 'Нижний колонтитул', - 's5slide-header-subfooter' => 'Дополнительный нижний колонтитул', - 's5slide-header-author' => 'Автор', -); diff --git a/S5SlideShow.php b/S5SlideShow.php deleted file mode 100644 index 3295a64..0000000 --- a/S5SlideShow.php +++ /dev/null @@ -1,264 +0,0 @@ - - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - */ - -/** - * @author Vitaliy Filippov - * @package MediaWiki - * @subpackage Extensions - */ - -if (!defined('MEDIAWIKI')) - die(); - -$dir = dirname(__FILE__); - -//--- Default configuration ---// - -// Headings with this text will be treated as slide headings -// false = do not treat subsections as slides by default -$egS5SlideHeadingMark = false; - -// All lists on slides with this text in heading will be shown step-by-step -$egS5SlideIncMark = '\(step\)'; - -// Slides with this text in heading will be shown centered -$egS5SlideCenterMark = '\(center\)'; - -// Filesystem path to slideshow template file -$egS5SlideTemplateFile = $dir.'/slide.htm'; - -// In scaled slideshow mode, images are scaled proportionally -// with all other elements. This means you can set image size -// with [[File:xxx.jpg|150px]] - these 150px will also be relative -// to other elements. But, without the following hack, the -// images will be added to slides downsampled, and then, in -// slideshow mode, they will be probably scaled back, which leads -// to reduced quality. - -// If this setting is true, hack into parser when in slideshow -// mode and output original images with HTML width/height set - -// i.e. hand off scaling to the browser - instead of outputting -// downsampled thumbnails. (default = true) -$egS5BrowserScaleHack = true; - -//Default Style for presentations. Useful for not redefining 'default' style -//but change all presentations by default to some "corporate" style. -$egS5DefaultStyle = 'default'; - -//Should Slides be scaled by default? -$egS5Scaled = false; - -//--- End configuration ---// - -/* Extension setup */ - -$wgExtensionMessagesFiles['S5SlideShow'] = $dir.'/S5SlideShow.i18n.php'; -$wgHooks['UnknownAction'][] = 'S5SlideShowHooks::UnknownAction'; -$wgAutoloadClasses['S5SlideShow'] = $dir.'/S5SlideShow.class.php'; -$wgAutoloadClasses['S5SkinArticle'] = $dir.'/S5SlideShow.class.php'; -$wgExtensionFunctions[] = 'S5SlideShowHooks::Setup'; -$wgHooks['ParserFirstCallInit'][] = 'S5SlideShowHooks::ParserFirstCallInit'; -$wgHooks['ArticleFromTitle'][] = 'S5SlideShowHooks::ArticleFromTitle'; -$wgHooks['AlternateEdit'][] = 'S5SlideShowHooks::AlternateEdit'; -$wgHooks['MagicWordwgVariableIDs'][] = 'S5SlideShowHooks::MagicWordwgVariableIDs'; -$wgHooks['ParserGetVariableValueSwitch'][] = 'S5SlideShowHooks::ParserGetVariableValueSwitch'; - -class S5SlideShowHooks -{ - static $styles = array( - 'core.css' => 's5-core.css', - 'base.css' => 's5-base.css', - 'framing.css' => 's5-framing.css', - 'pretty.css' => '$skin/pretty.css', - ); - static $parsingSlide = false; - // Setup parser hooks for S5 - static function ParserFirstCallInit(&$parser) - { - if (!isset($parser->extS5Hooks)) - { - $parser->setHook('slideshow', 'S5SlideShow::slideshow_view'); - $parser->setHook('slide', 'S5SlideShow::slideshow_legacy'); - $parser->setHook('slides', 'S5SlideShow::slides_view'); - $parser->setHook('slidecss', 'S5SlideShow::slidecss_view'); - } - elseif ($parser->extS5Hooks == 'parse') - { - $parser->setHook('slideshow', array($parser->extS5, 'slideshow_parse')); - $parser->setHook('slide', array($parser->extS5, 'slideshow_parse')); - $parser->setHook('slides', 'S5SlideShow::empty_tag_hook'); - $parser->setHook('slidecss', 'S5SlideShow::empty_tag_hook'); - } - elseif ($parser->extS5Hooks == 'parse2') - { - $parser->setHook('slideshow', 'S5SlideShow::empty_tag_hook'); - $parser->setHook('slide', 'S5SlideShow::empty_tag_hook'); - $parser->setHook('slides', array($parser->extS5, 'slides_parse')); - $parser->setHook('slidecss', array($parser->extS5, 'slidecss_parse')); - } - return true; - } - // Setup hook for image scaling hack - static function Setup() - { - global $egS5BrowserScaleHack, $wgHooks; - if ($egS5BrowserScaleHack) - $wgHooks['ImageBeforeProduceHTML'][] = 'S5SlideShowHooks::ImageBeforeProduceHTML'; - } - // Hook that creates {{S5SLIDESHOW}} magic word - static function MagicWordwgVariableIDs(&$mVariablesIDs) - { - $mVariablesIDs[] = 's5slideshow'; - return true; - } - // Hook that evaluates {{S5SLIDESHOW}} magic word - static function ParserGetVariableValueSwitch(&$parser, &$varCache, &$index, &$ret) - { - if ($index == 's5slideshow') - $ret = empty(self::$parsingSlide) ? '' : '1'; - return true; - } - // Render pictures differently in slide show mode - static function ImageBeforeProduceHTML($skin, &$title, &$file, &$frameParams, &$handlerParams, &$time, &$res) - { - global $wgVersion; - if (empty(self::$parsingSlide) || !$file || !$file->exists() || !isset($handlerParams['width'])) - return true; - $fp = &$frameParams; - $hp = &$handlerParams; - $center = false; - if (isset($fp['align']) && $fp['align'] == 'center') - { - $center = true; - $fp['align'] = 'none'; - } - $thumb = $file->getUnscaledThumb(isset($hp['page']) ? array('page' => $hp['page']) : false); - $params = array( - 'alt' => @$fp['alt'], - 'title' => @$fp['title'], - ); - if (version_compare($wgVersion, '1.22', '>=')) - { - $params['override-height'] = ceil($thumb->getHeight() * $hp['width'] / $thumb->getWidth()); - $params['override-width'] = $hp['width']; - } - else - { - $thumb->height = ceil($thumb->height * $hp['width'] / $thumb->width); - $thumb->width = $hp['width']; - } - if (!empty($fp['link-url'])) - $params['custom-url-link'] = $fp['link-url']; - elseif (!empty($fp['link-title'])) - $params['custom-title-link'] = $fp['link-title']; - elseif (!empty($fp['no-link'])) - { - } - else - $params['desc-link'] = true; - $res .= $thumb->toHtml($params); - if (isset($fp['thumbnail'])) - { - $outerWidth = $thumb->getWidth()+2; - $res = "
          ". - "
          $res
          $fp[caption]
          "; - } - if (isset($fp['align']) && $fp['align']) - $res = "
          $res
          "; - if ($center) - $res = "
          $res
          "; - return false; - } - // Hook for ?action=slide - static function UnknownAction($action, $article) - { - global $wgMaxRedirects, $wgRequest; - if ($action == 'slide') - { - $s5skin = trim($wgRequest->getVal('s5skin')); - if (preg_match('/[^\w-]/', $s5skin)) - $s5skin = ''; - $print = $wgRequest->getVal('print'); - if ($print) - { - preg_match_all('/\d+/s', $print, $print, PREG_PATTERN_ORDER); - $print = $print[0]; - } - else - $print = false; - if ($wgRequest->getVal('s5css')) - { - // Get CSS for a given S5 style (from wiki-pages) - S5SlideShow::genStyle($s5skin, $print); - return false; - } - // Check if the article is readable - $title = $article->getTitle(); - for ($r = 0; $r < $wgMaxRedirects && $title->isRedirect(); $r++) - { - if (!$title->userCan('read')) - return true; - $title = $article->followRedirect(); - $article = new Article($title); - } - // Hack for CustIS live preview - // TODO remove support for loading text from session object and - // replace it by support for save-staying-in-edit-mode extension - $content = $wgRequest->getVal('wpTextbox1'); - if (!$content && ($t1 = $wgRequest->getSessionData('wpTextbox1'))) - { - $content = $t1; - $wgRequest->setSessionData('wpTextbox1', NULL); - } - // Generate presentation HTML content - $slideShow = new S5SlideShow($title, $content); - if ($s5skin) - $slideShow->attr['style'] = $s5skin; - $slideShow->genSlideFile($print); - return false; - } - return true; - } - // Used to display CSS files on S5 skin CSS pages when they don't exist - static function ArticleFromTitle(&$title, &$article) - { - if ($title->getNamespace() == NS_MEDIAWIKI && - preg_match('#^S5/([\w-]+)/((core|base|framing|pretty).css)$#s', $title->getText(), $m)) - { - $file = dirname(__FILE__).'/'.str_replace('$skin', $m[1], self::$styles[$m[2]]); - if (file_exists($file)) - { - $article = new S5SkinArticle($title, $m[1], $file); - return false; - } - } - return true; - } - // Used to display CSS files on S5 skin CSS pages in edit mode - static function AlternateEdit($editpage) - { - if ($editpage->mArticle instanceof S5SkinArticle && - !$editpage->mArticle->exists()) - $editpage->mPreloadText = $editpage->mArticle->getContent(); - return true; - } -} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..4f84508 --- /dev/null +++ b/composer.json @@ -0,0 +1,55 @@ +{ + "name": "mediawiki/s5-slide-show", + "extra": { + "installer-name": "S5SlideShow" + }, + "homepage": "https:\/\/www.mediawiki.org\/wiki\/Extension:S5SlideShow", + "license": "GPL-2.0+", + "type": "mediawiki-extension", + "keywords": [ + "extension", + "wiki", + "mediawiki", + "presentation" + ], + "authors": [ + { + "name": "Vitaliy Filippov", + "url": "https://www.mediawiki.org/wiki/User:VitaliyFilippov", + "role": "Developer" + }, + { + "name": "Mark A. Hershberger", + "url": "http://hexmode.com/", + "email": "mah@nichework.com", + "role": "Contributor" + }, + { + "name": "Wolfgang Fahl", + "url": "http://bitplan.com/", + "role": "Contributor" + } + ], + "autoload" : { + "psr-4": { + "MediaWiki\\Extension\\S5SlideShow\\" : "src" + } + }, + "require": { + "php": ">=7.0.0", + "ext-mbstring": "*", + "composer/installers": "1.*,>=1.0.1", + "wikimedia/at-ease": "*" + }, + "require-dev": { + "jakub-onderka/php-parallel-lint": "~1.0", + "mediawiki/mediawiki-codesniffer": "~28.0" + }, + "scripts": { + "test": [ + "parallel-lint . --exclude vendor", + "phpcs -p -s ." + ], + "fix": "phpcbf ." + } +} diff --git a/extension.json b/extension.json new file mode 100644 index 0000000..dc616c7 --- /dev/null +++ b/extension.json @@ -0,0 +1,69 @@ +{ + "name": "S5SlideShow", + "namemsg": "s5slide-name", + "descriptionmsg": "s5slide-desc", + "version": "0.3", + "license-name": "GPL-2.0+", + "type": "parserhook", + "url": "https://www.mediawiki.org/wiki/Extension:S5SlideShow", + "requires": { + "MediaWiki": ">= 1.27.3" + }, + "author": [ + "Vitaliy Filippov", + "[http://www.bitplan.com/index.php/Wolfgang_Fahl Wolfgang Fahl/ProfiWiki]", + "[http://hexmode.com Mark A. Hershberger]" + ], + "Actions": { + "slide": "S5SlideShow\\Action" + }, + "Hooks": { + "ImageBeforeProduceHTML": [ + "S5SlideShow\\S5SlideShowHooks::ImageBeforeProduceHTML" + ], + "ParserFirstCallInit": [ + "S5SlideShow\\S5SlideShowHooks::ParserFirstCallInit" + ], + "ArticleFromTitle": [ + "S5SlideShow\\S5SlideShowHooks::ArticleFromTitle" + ], + "AlternateEdit": [ + "S5SlideShow\\S5SlideShowHooks::AlternateEdit" + ], + "MagicWordwgVariableIDs": [ + "S5SlideShow\\S5SlideShowHooks::MagicWordwgVariableIDs" + ], + "ParserGetVariableValueSwitch": [ + "S5SlideShow\\S5SlideShowHooks::ParserGetVariableValueSwitch" + ] + }, + "SpecialPages": { + "S5SlideShow": "S5SlideShow\\SpecialS5SlideShow" + }, + "MessagesDirs": { + "S5SlideShow": [ + "i18n" + ] + }, + "ExtensionMessagesFiles": { + "S5SlideShowI18n": "src/I18n.php" + }, + "AutoloadClasses": { + "S5SlideShow\\S5SlideShowHooks": "src/S5SlideShowHooks.php", + "S5SlideShow\\SpecialS5SlideShow": "src/Special.php", + "S5SlideShow\\Action": "src/Action.php", + "S5SlideShow\\Render": "src/Render.php", + "S5SlideShow\\Skin": "src/Skin.php" + }, + "config": { + "_prefix": "egS5", + "Scaled": false, + "DefaultStyle": "default", + "BrowserScaleHack": true, + "SlideTemplateFile": "extensions/S5SlideShow/slide.htm", + "SlideCenterMark": "(center)", + "SlideHeadingMark": false, + "SlideIncMark": "(step)" + }, + "manifest_version": 1 +} diff --git a/i18n/de.json b/i18n/de.json new file mode 100644 index 0000000..d0177af --- /dev/null +++ b/i18n/de.json @@ -0,0 +1,17 @@ +{ + "@metadata": { + "authors": [ + "Vitaliy Filippov", + "Wolfgang Fahl" + ] + }, + "s5slide-name": "S5SlideShow", + "s5slide-desc": "S5SlideShow ist eine MediaWiki-Erweiterung, die das einfache, schnelle und bequeme Erstellen von Diashows mit S5 im Kontext von MediaWiki- und Wiki-Seiten ermöglicht.", + "special-s5SlideShow-title": "S5SlideShow configuration", + "special-s5SlideShow-intro": "", + "s5slide-header-title" : "Titel", + "s5slide-header-subtitle" : "Untertitel", + "s5slide-header-footer" : "Fußzeile", + "s5slide-header-subfooter" : "Unterfußzeile", + "s5slide-header-author" : "Autor" +} diff --git a/i18n/en.json b/i18n/en.json new file mode 100644 index 0000000..15669e6 --- /dev/null +++ b/i18n/en.json @@ -0,0 +1,17 @@ +{ + "@metadata": { + "authors": [ + "Vitaliy Filippov", + "Wolfgang Fahl" + ] + }, + "s5slide-name": "S5SlideShow", + "s5slide-desc": "S5SlideShow is a MediaWiki extension allowing simple, fast and convenient creation of slide shows using S5 in the context of MediaWiki and Wiki pages. ", + "special-s5SlideShow-title": "S5SlideShow configuration", + "special-s5SlideShow-intro": "", + "s5slide-header-title" : "Title", + "s5slide-header-subtitle" : "Subtitle", + "s5slide-header-footer" : "Footer", + "s5slide-header-subfooter" : "Subfooter", + "s5slide-header-author" : "Author" +} diff --git a/i18n/fr.json b/i18n/fr.json new file mode 100644 index 0000000..0abe301 --- /dev/null +++ b/i18n/fr.json @@ -0,0 +1,17 @@ +{ + "@metadata": { + "authors": [ + "Vitaliy Filippov", + "Wolfgang Fahl" + ] + }, + "s5slide-name": "S5SlideShow", + "s5slide-desc": "S5SlideShow est une extension MediaWiki permettant la création simple, rapide et pratique de diaporamas utilisant S5 dans le contexte des pages MediaWiki et Wiki.", + "special-s5SlideShow-title": "S5SlideShow configuration", + "special-s5SlideShow-intro": "", + "s5slide-header-title" : "Titre", + "s5slide-header-subtitle" : "Sous-titre", + "s5slide-header-footer" : "Pied de page", + "s5slide-header-subfooter" : "Sous pied de page", + "s5slide-header-author" : "Auteur" +} diff --git a/i18n/ru.json b/i18n/ru.json new file mode 100644 index 0000000..4d0533d --- /dev/null +++ b/i18n/ru.json @@ -0,0 +1,17 @@ +{ + "@metadata": { + "authors": [ + "Vitaliy Filippov", + "Wolfgang Fahl" + ] + }, + "s5slide-name": "S5SlideShow", + "s5slide-desc": "S5SlideShow - это расширение MediaWiki, позволяющее просто, быстро и удобно создавать слайд-шоу с использованием S5 в контексте страниц MediaWiki и Wiki.", + "special-s5SlideShow-title": "S5SlideShow configuration", + "special-s5SlideShow-intro": "", + "s5slide-header-title" : "Заголовок", + "s5slide-header-subtitle" : "Подзаголовок", + "s5slide-header-footer" : "Нижний колонтитул", + "s5slide-header-subfooter" : "Дополнительный нижний колонтитул", + "s5slide-header-author" : "Автор" +} diff --git a/src/Action.php b/src/Action.php new file mode 100644 index 0000000..6b6c30f --- /dev/null +++ b/src/Action.php @@ -0,0 +1,93 @@ + + * Copyright (c) 2020 NicheWork, LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ +namespace S5SlideShow; + +use Action as MWAction; +use Article as MWArticle; + +/** + * @author Vitaliy Filippov + * @author Mark A. Hershberger + * @package MediaWiki + * @subpackage Extensions + */ + +class Action extends MWAction { + + // This action is called 'example_action'. This class will only + // be invoked when the specified action is requested. + public function getName() { + // This should be the same name as used when registering the + // action in $wgActions. + return 'slide'; + } + + // This is the function that is called whenever a page is being + // requested using this action. You should not use globals + // $wgOut, $wgRequest, etc. Instead, use the methods provided by + // the Action class (e.g. $this->getOutput()), instead. + public function show() { + global $wgMaxRedirects; + + $req = $this->getRequest(); + $s5skin = trim( $req->getVal( 's5skin' ) ); + if ( preg_match( '/[^\w-]/', $s5skin ) ) { + $s5skin = ''; + } + $print = $req->getVal( 'print' ); + if ( $print ) { + preg_match_all( '/\d+/s', $print, $print, PREG_PATTERN_ORDER ); + $print = $print[0]; + } else { + $print = false; + } + if ( $req->getVal( 's5css' ) ) { + // Get CSS for a given S5 style (from wiki-pages) + Render::genStyle( $s5skin, $print ); + return false; + } + // Check if the article is readable + $title = $this->getTitle(); + for ( $r = 0; $r < $wgMaxRedirects && $title->isRedirect(); $r++ ) { + if ( !$title->userCan( 'read' ) ) { + return true; + } + $title = WikiPage::newFromID( $title->getArticleID() )->followRedirect(); + $article = new MWArticle( $title ); + } + // Hack for CustIS live preview + // TODO remove support for loading text from session object and + // replace it by support for save-staying-in-edit-mode extension + $content = $req->getVal( 'wpTextbox1' ); + if ( !$content && ( $t1 = $req->getSessionData( 'wpTextbox1' ) ) ) { + $content = $t1; + $req->setSessionData( 'wpTextbox1', null ); + } + // Generate presentation HTML content + $slideShow = new Render( $title, $content ); + if ( $s5skin ) { + $slideShow->attr['style'] = $s5skin; + } + $slideShow->genSlideFile( $print ); + return false; + } +} diff --git a/src/Article.php b/src/Article.php new file mode 100644 index 0000000..904feca --- /dev/null +++ b/src/Article.php @@ -0,0 +1,91 @@ + + * Copyright (c) 2017-2020 Wolfgang Fahl + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ +namespace S5SlideShow; + +use Article as MWArticle; +use LogEventsList; +// more trouble than help +// use Wikimedia\AtEase\AtEase; + +/** + * @author Vitaliy Filippov + * @author Wolfgang Fahl + * @package MediaWiki + * @subpackage Extensions + */ + +// Used to display CSS files instead of non-existing special articles +// (MediaWiki:S5//) +class Article extends MWArticle { + var $s5skin, $s5file; + // Create the object and remember s5skin and s5file + public function __construct( $title, $s5skin, $s5file ) { + $this->mPage = $this->newPage( $title ); + $this->mOldId = null; + $this->s5skin = $s5skin; + $this->s5file = $s5file; + } + + // Get content from the file + public function getContent() { + if ( $this->getID() == 0 ) { + // AtEase::quietCall( 'file_get_contents', + $this->mContent = file_get_contents($this->s5file ); + } else { + $this->loadContent(); + } + return $this->mContent; + } + + // Show default content from the file + public function showMissingArticle() { + global $wgOut, $wgRequest, $wgParser; + // Copy-paste from includes/Article.php: + // Show delete and move logs + LogEventsList::showLogExtract( + $wgOut, [ 'delete', 'move' ], $this->mTitle->getPrefixedText(), '', [ + 'lim' => 10, + 'conds' => [ "log_action != 'revision'" ], + 'showIfEmpty' => false, + 'msgKey' => [ 'moveddeleted-notice' ] + ] + ); + // Show error message + $oldid = $this->getOldID(); + if ( $oldid ) { + $text = wfMessage( + 'missing-article', $this->mTitle->getPrefixedText(), + wfMessage( 'missingarticle-rev', $oldid )->plain() + )->plain(); + } else { + $text = $this->getContent(); + } + if ( $wgParser->mTagHooks['source'] ) { + $text = "\n$text\n"; + } + $text = "
          \n$text\n
          "; + $wgOut->addWikiText( $text ); + } +} diff --git a/src/I18n.php b/src/I18n.php new file mode 100644 index 0000000..1777e0e --- /dev/null +++ b/src/I18n.php @@ -0,0 +1,7 @@ + [ 0, 's5slideshow' ], +]; diff --git a/src/Render.php b/src/Render.php new file mode 100644 index 0000000..91278de --- /dev/null +++ b/src/Render.php @@ -0,0 +1,714 @@ + + * Copyright (c) 2020 Wolfgang Fahl + * Copyright (c) 2020 NicheWork, LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ +namespace S5SlideShow; + +use Article as MWArticle; +use Parser; +use ParserOptions; +use PPFrame; +use Title; +use User; +// more trouble than help +// use Wikimedia\AtEase\AtEase; + +/** + * @author Vitaliy Filippov + * @author Mark A. Hershberger + * @author Wolfgang Fahl + * @package MediaWiki + * @subpackage Extensions + */ + +class Render { + var $sTitle, $sArticle, $pageContent; + + var $slideParser, $parserOptions; + static $slideno = 0; + + var $slides, $css, $attr; + + /** + * Constructor. If $attr is not an array(), article content + * will be parsed and attributes will be taken from there. + */ + function __construct( $sTitle, $sContent = null, $attr = null ) { + if ( !is_object( $sTitle ) ) { + wfDebug( __CLASS__ . ": Error! Pass a title object, NOT a title string!\n" ); + return false; + } + $this->sArticle = new MWArticle( $sTitle ); + $this->sTitle = $sTitle; + if ( $sContent ) { + $this->pageContent = $sContent; + } else { + $this->pageContent = $this->sArticle->getPage()->getContent(); + } + $this->attr = []; + if ( is_array( $attr ) ) { + $this->setAttributes( $attr ); + } + } + + /** + * Parse attribute hash and save them into $this + */ + public function setAttributes( $attr ) { + global $wgContLang, $wgUser, $egS5SlideHeadingMark, $egS5SlideIncMark, + $egS5SlideCenterMark, $egS5DefaultStyle, $egS5Scaled; + // Get attributes from tag content + if ( + preg_match_all( + '/(?:^|\n)\s*;\s*([^:\s]*)\s*:\s*([^\n]*)/is', $attr['content'], $m, + PREG_SET_ORDER + ) > 0 + ) { + foreach ( $m as $set ) { + $attr[$set[1]] = trim( $set[2] ); + } + } + // Default values + $attr = $attr + [ + 'title' => $this->sTitle->getText(), + 'subtitle' => '', + 'footer' => $this->sTitle->getText(), + 'headingmark' => $egS5SlideHeadingMark, + 'incmark' => $egS5SlideIncMark, + 'centermark' => $egS5SlideCenterMark, + 'style' => $egS5DefaultStyle, + 'font' => '', + // Backwards compatibility: appended CSS + 'addcss' => '', + // Is each slide to be scaled independently? + 'scaled' => $egS5Scaled, + ]; + // Boolean value + $attr['scaled'] = strtoupper( $attr['scaled'] ) === 'TRUE' + || strtoupper( $attr['scaled'] ) === 'YES' + || intval( $attr['scaled'] ) === 1; + // Default author = first revision's author + if ( !isset( $attr['author'] ) ) { + $rev = $this->sArticle->getOldestRevision(); + if ( $rev ) { + $attr['author'] = User::newFromId( $rev->getUser() )->getRealName(); + } else { + // Not saved yet + $attr['author'] = $wgUser->getRealName(); + } + } + // Author and date in the subfooter by default + if ( !isset( $attr['subfooter'] ) ) { + $attr['subfooter'] = $attr['author']; + if ( $attr['subfooter'] ) { + $attr['subfooter'] .= ', '; + } + $attr['subfooter'] .= $wgContLang->timeanddate( + $this->sArticle->getTimestamp(), true + ); + } else { + $attr['subfooter'] = str_ireplace( + '{{date}}', + $wgContLang->timeanddate( $this->sArticle->getTimestamp(), true ), + $attr['subfooter'] + ); + } + $this->attr = $attr + $this->attr; + } + + /** + * Checks heading text for headingmark, incmark, centermark + * Returns NULL or array(title, incremental, centered) + */ + public function check_slide_heading( $node ) { + $ot = trim( $node->nodeValue, "= \n\r\t\v" ); + $st = $this->attr['headingmark'] ? preg_replace( $this->heading_re, '', $ot ) : $ot; + if ( !$this->attr['headingmark'] || $st != $ot ) { + $inc = false; + $center = false; + if ( $this->inc_re ) { + $t = preg_replace( $this->inc_re, '', $st ); + if ( $t != $st ) { + $inc = true; + $st = $t; + } + } + if ( $this->center_re ) { + $t = preg_replace( $this->center_re, '', $st ); + if ( $t != $st ) { + $center = true; + $st = $t; + } + } + return [ $st, $inc, $center ]; + } + return null; + } + + /** + * This function transforms section slides into tags + */ + function transform_section_slides( $content ) { + $p = $this->getParser(); + $content = $p->preprocess( $content, $this->sTitle, $this->parserOptions ); + $p->setOutputType( Parser::OT_WIKI ); + $node = $p->preprocessToDom( $content ); + // mediawiki 1.33 + $node= $node->node; + $doc = $node->ownerDocument; + $all = $node->childNodes; + if ( $this->attr['headingmark'] ) { + $this->heading_re = '/' . str_replace( + '/', '\\/', preg_quote( $this->attr['headingmark'] ) + ) . '/'; + } + if ( $this->attr['incmark'] ) { + $this->inc_re = '/' . str_replace( + '/', '\\/', preg_quote( $this->attr['incmark'] ) + ) . '/'; + } + if ( $this->attr['centermark'] ) { + $this->center_re = '/' . str_replace( + '/', '\\/', preg_quote( $this->attr['centermark'] ) + ) . '/'; + } + $allLen=$all->length; + wfDebug(__METHOD__.": checking ".$allLen." document nodes for slide transformation"); + for ( $i = 0; $i < $allLen; $i++ ) { + $c = $all->item( $i ); + if ( isset($c->nodeName) && $c->nodeName == 'h' && ( $st = $this->check_slide_heading( $c ) ) !== null ) { + /** + * Add + * slidesATTRSCONTENT + */ + $slide = $doc->createElement( 'ext' ); + $e = $doc->createElement( 'name' ); + $e->nodeValue = 'slides'; + $slide->appendChild( $e ); + $e = $doc->createElement( 'attr' ); + $v = ' title="' . htmlspecialchars( $st[0] ) . '"'; + if ( $st[1] ) { + $v .= ' incremental="1"'; + } + if ( $st[2] ) { + $v .= ' center="1"'; + } + $e->nodeValue = htmlspecialchars( $v ); + $slide->appendChild( $e ); + $e = $doc->createElement( 'inner' ); + $slide->appendChild( $e ); + $e1 = $doc->createElement( 'close' ); + $e1->nodeValue = "\n"; + $slide->appendChild( $e1 ); + $node->insertBefore( $slide, $c ); + $node->removeChild( $c ); + // Move children of $node to $e, up to first + // with name="slide(s)" or heading of same or greater level + for ( $j = $i + 1; $j < $all->length; ) { + $d = $all->item( $j ); + if ( $d->nodeName == 'h' ) { + break; + } + if ( $d->nodeName == 'ext' ) { + $name = $d->getElementsByTagName( 'name' ); + if ( count( $name ) != 1 ) { + die( + __METHOD__ . ': Internal error, without ' + . 'in DOM text' + ); + } + if ( substr( $name->item( 0 )->nodeValue, 0, 5 ) === 'slide' ) { + break; + } + } + $node->removeChild( $d ); + $e->appendChild( $d ); + } + } + } + $frame = $p->getPreprocessor()->newFrame(); + $text = $frame->expand( $node, PPFrame::RECOVER_ORIG ); + $text = $frame->parser->mStripState->unstripBoth( $text ); + return $text; + } + + /** + * Extract slides from wiki-text $content + */ + function loadContent( $content = null ) { + if ( $content === null ) { + if ( method_exists( $this->pageContent, 'getText' ) ) { + $content = $this->pageContent->getText(); + } else { + // Pre 1.33 compatibility + $content = $this->pageContent->getNativeData(); + } + } + $this->getParser(); + $p = new Parser; + $p->extS5 = $this; + $p->extS5Hooks = 'parse'; + $p->parse( $content, $this->sTitle, $this->parserOptions ); + if ( isset( $this->attr['headingmark'] ) && $this->attr['headingmark'] !== false ) { + $content = $this->transform_section_slides( $content ); + } + $this->slides = []; + $this->css = []; + $this->parse( $content ); + foreach ( $this->slides as &$slide ) { + $slide['content_html'] = $this->parse( $slide['content'] ); + if ( $slide['title'] ) { + $slide['title_html'] = $this->parse( $slide['title'], true ); + } else { + $slide['title_html'] = ''; + } + } + return $this->slides; + } + + /** + * Parse slide content using a copy of $wgParser, + * save slides and slide stylesheets into $this and return resulting HTML + */ + function parse( $text, $inline = false, $title = null ) { + global $wgOut; + if ( !$title ) { + $title = $this->sTitle; + } + $text = str_replace( "__TOC__", '', trim( $text ) ); + $prev = S5SlideShowHooks::$parsingSlide; + S5SlideShowHooks::$parsingSlide = true; + $output = $this->getParser()->parse( + "$text __NOTOC__ __NOEDITSECTION__", $title, + $this->parserOptions, !$inline, false + ); + S5SlideShowHooks::$parsingSlide = $prev; + $wgOut->addParserOutput( $output ); + return $output->getText(); + } + + // Create parser object for $this->parse() + function getParser() { + if ( $this->slideParser ) { + return $this->slideParser; + } + global $wgParser, $wgUser; + $this->parserOptions = ParserOptions::newFromUser( $wgUser ); + // deprecated since 1.31, use ParserOutput::getText() options instead. + //$this->parserOptions->setEditSection( false ); + $this->parserOptions->setNumberHeadings( false ); + $this->parserOptions->enableLimitReport( false ); + // Since $this->parse() is only used in ?action=slide, + // we can use it directly without cloning or creating a new object + // But $wgParser may be a StubObject, so trigger unstub and first call init + $wgParser->parse( " ", $this->sTitle, $this->parserOptions, false, true ); + $wgParser->setHook( 'slideshow', __CLASS__ . '::empty_tag_hook' ); + $wgParser->setHook( 'slide', __CLASS__ . '::empty_tag_hook' ); + $wgParser->setHook( 'slides', [ $this, 'slides_parse' ] ); + $wgParser->setHook( 'slidecss', [ $this, 'slidecss_parse' ] ); + $wgParser->mShowToc = false; + return $this->slideParser = $wgParser; + } + + function getHeadItems() { + // Extract loader scripts and styles from OutputPage::headElement() + global $wgOut; + $skin = new Skin(); + $context=$wgOut->getContext(); + $context->setSkin( $skin ); + $s = $wgOut->headElement( $skin ); + preg_match_all( + '/]*>.*?<\/script>|]*rel="stylesheet"[^<>]*>|' . + ']*name="ResourceLoaderDynamicStyles"[^<>]*>/is', + $s, $m, PREG_PATTERN_ORDER + ); + return implode( + "\n", array_filter( $m[0], + function ( $s ) { + return strpos( $s, 'commonPrint' ) === false; + } ) + ); + } + + /** + * Generate presentation HTML code + */ + function genSlideFile( $printPageSize = false ) { + global $egS5SlideTemplateFile; + global $wgContLang, $wgOut; + + if ( !$this->slides ) { + $this->loadContent(); + } + + $currentDir=getcwd(); + $debugMsg=": trying to load slide template from ".$egS5SlideTemplateFile." relative to ".$currentDir; + wfDebug(__CLASS__.$debugMsg); + + // load template contents + // AtEase::quietCall( 'file_get_contents' + $slide_template = file_get_contents($egS5SlideTemplateFile ); + if ( !$slide_template ) { + return false; + } + + // build replacement array for slide show template + $replace = []; + foreach ( explode( ' ', 'title subtitle author footer subfooter addcss' ) as $v ) { + $replace["[$v]"] = $this->parse( $this->attr[$v], true ); + } + $replace['[addcss]'] = implode( "\n", $this->css ); + if ( $this->attr['font'] ) { + $replace['[addcss]'] .= "\n.slide, div.header, div.footer { " + . "font-family: {$this->attr['font']}; }"; + } + $replace['[addcss]'] = strip_tags( $replace['[addcss]'] ); + $replace['[addscript]'] = ''; + $replace['[style]'] = $this->attr['style']; + $replace['[styleurl]'] = 'index.php?action=slide&s5skin=' + . $this->attr['style'] . '&s5css=1'; + $replace['[pageid]'] = $this->sArticle->getID(); + $replace['[scaled]'] = $this->attr['scaled'] ? 'true' : 'false'; + $replace['[defaultView]'] = 'slideshow'; + + if ( $printPageSize ) { + // Default DPI + $dpi = 96; + $printPageSize = array_map( 'intval', $printPageSize ); + $replace['[styleurl]'] .= '&print=' . implode( 'x', $printPageSize ); + $replace['[addcss]'] .= '@page {size: ' . $printPageSize[0] . 'mm ' + . $printPageSize[1] . "mm;}\n.body {width: " + . ( $w = floor( $printPageSize[0] * $dpi / 25.4 ) ) + . 'px; height: ' + . ( $h = floor( $printPageSize[1] * $dpi / 25.4 ) ) + . "px;}\n"; + $replace['[addscript]'] .= "var s5PrintPageSize = [ $w, $h ];\n"; + $replace['[defaultView]'] = 'print'; + } + + $slides_html = ''; + $slide0 = " visible"; + if ( trim( $replace['[author]'] ) !== '' && trim( $replace['[title]'] ) !== '' ) { + $slides_html .= '

          ' . $replace['[title]'] + . '

          ' + . '

          ' + . $replace['[subtitle]'] . '

          ' . $replace['[author]'] + . '

          '; + $slide0 = ''; + } + foreach ( $this->slides as $slide ) { + $c = $slide['content_html']; + $t = $slide['title_html']; + // make slide lists incremental if needed + if ( $slide['incremental'] ) { + $c = str_replace( '
            ', '
              ', $c ); + $c = str_replace( '
                ', '
                  ', $c ); + } + $c = "
                  $c
                  "; + if ( trim( strip_tags( $t ) ) ) { + $center = $slide['center'] ? " notitle" : ""; + $slides_html .= "
                  " + . "

                  $t

                  $c
                  \n"; + } else { + $slides_html .= "
                  $c
                  \n"; + } + $slide0 = ""; + } + + // substitute values + $replace['[headitems]'] = $this->getHeadItems(); + $replace['[content]'] = $slides_html; + $html = str_replace( + array_keys( $replace ), + array_values( $replace ), + $slide_template + ); + $html = $wgContLang->convert( $html ); + + // output generated content + $wgOut->disable(); + header( "Content-Type: text/html; charset=utf-8" ); + echo $html; + } + + /** + * Function to replace URLs in S5 skin stylesheet + * $m is the match array coming from preg_replace_callback + */ + static function styleReplaceUrl( $skin, $m ) { + $t = Title::newFromText( $m[1], NS_FILE ); + $f = wfLocalFile( $t ); + if ( $f->exists() ) { + return 'url(' . $f->getFullUrl() . ')'; + } + // FIXME remove hardcode extensions/S5SlideShow/ + // Replace images with invalid names with blank.gif + if ( preg_match( '/[^a-z0-9_\-\.]/is', $m[1] ) ) { + return 'url(extensions/S5SlideShow/blank.gif)'; + } + return "url(extensions/S5SlideShow/$skin/" . $m[1] . ')'; + } + + // https://stackoverflow.com/a/834355/1497139 + static function endsWith(string $haystack, string $needle): bool { + return substr($haystack, -strlen($needle))===$needle; + } + + /** + * Generate CSS stylesheet for a given S5 skin + * TODO cache generated stylesheets and flush the cache after saving style articles + */ + static function genStyle( $skin, $print = false ) { + global $wgOut; + // directory of script + $dir = __DIR__; + // since 2020-09 the sources are in src subdirectory + if (Render::endsWith($dir,"src")) { + $dir=dirname($dir); + } + $css = ''; + if ( $print ) { + S5SlideShowHooks::$styles['print'] = 'print.css'; + } + foreach ( S5SlideShowHooks::$styles as $k => $file ) { + // first try whether there is a page for the style + $title = Title::newFromText( "S5/$skin/$k", NS_MEDIAWIKI ); + if ( $title->exists() ) { + $a = new MWArticle( $title ); + $c = $a->getContent(); + } else { + // try getting page from file system + // AtEase::quietCall('file_get_contents' + $skinFile="$dir/" . str_replace( '$skin', $skin, $file); + $c = file_get_contents($skinFile); + } + $c = preg_replace_callback( + '#url\(([^\)]*)\)#is', function( $m ) use ( $skin ) { + return self::styleReplaceUrl($skin, $m); + }, $c ); + $css .= $c; + } + $wgOut->disable(); + header( "Content-Type: text/css" ); + echo $css; + } + + /** + * - article view mode, backwards compatibility + */ + static function slideshow_legacy( $content, $attr, $parser, $frame = null ) { + return self::slideshow_view( + $content, $attr, $parser, $frame, + '
                  Warning: legacy <slide>' . + ' parser hook used, change it to <slideshow> please
                  ' + ); + } + + /** + * Parse content using an existing parser and cloned options + * without LimitReport, without EditSections + */ + static function clone_options_parse( $content, $parser, $inline = false ) { + if ( !$parser->mTitle ) { + wfDebug( __METHOD__ . ": no title object in parser\n" ); + return ''; + } + $oldOpt = $parser->mOptions; + if ( !isset( $parser->extClonedOptions ) ) { + if ( !$oldOpt ) { + global $wgUser; + $oldOpt = ParserOptions::newFromUser( $wgUser ); + } + $opt = clone $oldOpt; + $opt->enableLimitReport( false ); + $opt->setEditSection( false ); + $parser->extClonedOptions = $opt; + } + $html = $parser->parse( + $content, $parser->mTitle, $parser->extClonedOptions, !$inline, false + )->getText(); + $parser->mOptions = $oldOpt; + return $html; + } + + /** + * - article view mode + * displays a link to the slideshow and skin preview + */ + public static function slideshow_view( + $content, $attr, $parser, $frame = null, $addmsg = '' + ) { + global $wgScriptPath, $wgParser, $wgContLang; + if ( !$parser->mTitle ) { + wfDebug( __METHOD__ . ": no title object in parser\n" ); + return ''; + } + // Create slideshow object + $attr['content'] = $content; + $slideShow = new Render( $parser->mTitle, null, $attr ); + $content = ''; + $article = new MWArticle( $parser->mTitle ); + foreach ( [ 'title', 'subtitle', 'author', 'footer', 'subfooter' ] as $key ) { + if ( isset( $slideShow->attr[$key] ) && $slideShow->attr[$key] != '' ) { + $value = $slideShow->attr[$key]; + if ( mb_strpos( $value, "{{date}}" ) !== false ) { + $value = str_ireplace( + '{{date}}', + $wgContLang->timeanddate( $article->getTimestamp(), true ), + $value + ); + } + $content .= "\n;" . wfMessage( 's5slide-header-' . $key ) . ': ' . $value; + } + } + // FIXME remove hardcoded '.png', /extensions/S5SlideShow/, "Slide Show" + $url = htmlspecialchars( $parser->mTitle->getLocalUrl( [ 'action' => 'slide' ] ) ); + $style_title = Title::newFromText( + 'S5-' . $slideShow->attr['style'] . '-preview.png', NS_FILE + ); + if ( + $style_title && ( $style_preview = wfLocalFile( $style_title ) ) && + $style_preview->exists() + ) { + $style_preview = $style_preview->getTitle()->getPrefixedText(); + $style_preview = self::clone_options_parse( + "[[$style_preview|240px|link=]]", $wgParser, true + ); + } else { + $style_preview = ''; + } + $inside = self::clone_options_parse( $content, $wgParser, true ); + $html = '' + . '' + . '' + . $inside; + if ( !empty( $slideShow->attr['font'] ) ) { + $html = '' . $html; + } + $html = '
                  ' . $html . '
                  '; + return $html; + } + + // - slideshow parse mode + // saves parameters into $this + function slideshow_parse( $content, $attr, $parser ) { + $attr['content'] = $content; + $this->setAttributes( $attr ); + return ''; + } + + // - article view mode + static function slides_view( $content, $attr, $parser ) { + if ( !empty( $attr['split'] ) ) { + $slides = preg_split( + '/' . str_replace( '/', '\\/', $attr['split'] ) . '/', $content + ); + } else { + $slides = [ $content ]; + } + $html = ''; + $style = ''; + if ( !isset( $attr['float'] ) ) { + $style .= "float: left; "; + } + if ( isset( $attr['width'] ) ) { + $style .= "width: $attr[width]px; "; + } + if ( $style ) { + $style = " style='$style'"; + } + foreach ( $slides as $i => $slide ) { + if ( isset( $attr['title'] ) && !$i ) { + $slide = "== $attr[title] ==\n" . trim( $slide ); + $st = 'slide withtitle'; + } else { $st = 'slide'; + } + $output = self::clone_options_parse( trim( $slide ), $parser, false ); + $html .= '
                  ' . $output . '
                  '; + } + if ( !isset( $attr['float'] ) ) { + $html .= '
                  '; + + } else { + $html = "
                  $html
                  "; + } + return $html; + } + + // - slideshow parse mode + function slides_parse( $content, $attr, $parser ) { + if ( isset( $attr['split'] ) ) { + $slides = preg_split( + '/' . str_replace( '/', '\\/', $attr['split'] ) . '/', $content + ); + } else { + $slides = [ $content ]; + } + foreach ( $slides as $c ) { + $this->slides[] = [ 'content' => trim( $c ) ] + $attr + [ + 'title' => '', + 'incremental' => false, + 'center' => false, + ]; + unset( $attr['title'] ); + } + } + + // - article view mode + static function slidecss_view( $content, $attr, $parser ) { + // use this CSS only for + if ( + !empty( $attr['view'] ) && ( $attr['view'] == 'true' || $attr['view'] == '1' ) + ) { + $parser->mOutput->addHeadItem( + '' + ); + } + return ''; + } + + // - slideshow parse mode + function slidecss_parse( $content, $attr, $parser ) { + $this->css[] = $content; + } + + // stub for tag hooks + static function empty_tag_hook() { + return ''; + } +} diff --git a/src/S5SlideShowHooks.php b/src/S5SlideShowHooks.php new file mode 100644 index 0000000..7624dac --- /dev/null +++ b/src/S5SlideShowHooks.php @@ -0,0 +1,182 @@ + + * Copyright (c) 2017-2020 Wolfgang Fahl + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ +namespace S5SlideShow; + +use Article as MWArticle; +use WikiPage; +/** + * @author Vitaliy Filippov + * @author Wolfgang Fahl + * @package MediaWiki + * @subpackage Extensions + */ + +class S5SlideShowHooks { + static $styles = [ + 'core.css' => 's5-core.css', + 'base.css' => 's5-base.css', + 'framing.css' => 's5-framing.css', + 'pretty.css' => '$skin/pretty.css', + ]; + static $parsingSlide = false; + + // Setup parser hooks for S5 + static function ParserFirstCallInit( $parser ) { + if ( !isset( $parser->extS5Hooks ) ) { + $parser->setHook( + 'slideshow', [ 'S5SlideShow\Render', 'slideshow_view' ] + ); + $parser->setHook( + 'slide', 'S5SlideShow\Render::slideshow_legacy' + ); + $parser->setHook( + 'slides', 'S5SlideShow\Render::slides_view' + ); + $parser->setHook( + 'slidecss', 'S5SlideShow\Render::slidecss_view' + ); + } elseif ( $parser->extS5Hooks === 'parse' ) { + $parser->setHook( 'slideshow', [ $parser->extS5, 'slideshow_parse' ] ); + $parser->setHook( 'slide', [ $parser->extS5, 'slideshow_parse' ] ); + $parser->setHook( + 'slides', 'S5SlideShow\Render::empty_tag_hook' + ); + $parser->setHook( + 'slidecss', 'S5SlideShow\Render::empty_tag_hook' + ); + } elseif ( $parser->extS5Hooks === 'parse2' ) { + $parser->setHook( + 'slideshow', 'S5SlideShow\Render::empty_tag_hook' + ); + $parser->setHook( + 'slide', 'S5SlideShow\Render::empty_tag_hook' + ); + $parser->setHook( 'slides', [ $parser->extS5, 'slides_parse' ] ); + $parser->setHook( 'slidecss', [ $parser->extS5, 'slidecss_parse' ] ); + } + } + + // Setup hook for image scaling hack + static function Setup() { + // global $wgActions; + // echo "
                  ";var_dump($wgActions);exit;
                  +		// global $egS5BrowserScaleHack, $wgHooks;
                  +		// if ( $egS5BrowserScaleHack ) {
                  +		// 	$wgHooks['ImageBeforeProduceHTML'][]
                  +		// 		= 'MediaWiki\Extensions\S5SlideShow\Hooks::ImageBeforeProduceHTML';
                  +		// }
                  +	}
                  +
                  +	// Hook that creates {{S5SLIDESHOW}} magic word
                  +	static function MagicWordwgVariableIDs( &$mVariablesIDs ) {
                  +		$mVariablesIDs[] = 's5slideshow';
                  +	}
                  +
                  +	// Hook that evaluates {{S5SLIDESHOW}} magic word
                  +	static function ParserGetVariableValueSwitch( $parser, $varCache, $index, &$ret ) {
                  +		if ( $index === 's5slideshow' ) {
                  +			$ret = empty( self::$parsingSlide ) ? '' : '1';
                  +		}
                  +	}
                  +
                  +	// Render pictures differently in slide show mode
                  +	static function ImageBeforeProduceHTML(
                  +		$skin, $title, $file, $frameParams, $handlerParams, $time, &$res
                  +	) {
                  +		global $egS5BrowserScaleHack;
                  +		if ( !$egS5BrowserScaleHack ) {
                  +			return;
                  +		}
                  +
                  +		if (
                  +			empty( self::$parsingSlide ) || !$file || !$file->exists() ||
                  +			!isset( $handlerParams['width'] )
                  +		) {
                  +			return true;
                  +		}
                  +		$fp = &$frameParams;
                  +		$hp = &$handlerParams;
                  +		$center = false;
                  +		if ( isset( $fp['align'] ) && $fp['align'] === 'center' ) {
                  +			$center = true;
                  +			$fp['align'] = 'none';
                  +		}
                  +		$thumb = $file->getUnscaledThumb(
                  +			isset( $hp['page'] ) ? [ 'page' => $hp['page'] ] : false
                  +		);
                  +		$params['alt'] = $fp['alt'] ?? null;
                  +		$params['title'] = $fp['title'] ?? null;
                  +
                  +		$params['override-height']
                  +			= ceil( $thumb->getHeight() * $hp['width'] / $thumb->getWidth() );
                  +		$params['override-width'] = $hp['width'];
                  +		if ( !empty( $fp['link-url'] ) ) {
                  +			$params['custom-url-link'] = $fp['link-url'];
                  +		} elseif ( !empty( $fp['link-title'] ) ) {
                  +			$params['custom-title-link'] = $fp['link-title'];
                  +		} elseif ( !empty( $fp['no-link'] ) ) {
                  +		} else {
                  + 			$params['desc-link'] = true;
                  +		}
                  +		$res .= $thumb->toHtml( $params );
                  +		if ( isset( $fp['thumbnail'] ) ) {
                  +			$outerWidth = $thumb->getWidth() + 2;
                  +			$res = "
                  " + . "
                  $res
                  " + . "
                  $fp[caption]
                  "; + } + if ( isset( $fp['align'] ) && $fp['align'] ) { + $res = "
                  $res
                  "; + } + if ( $center ) { + $res = "
                  $res
                  "; + } + return false; + } + + // Used to display CSS files on S5 skin CSS pages when they don't exist + static function ArticleFromTitle( $title, &$article ) { + if ( $title->getNamespace() === NS_MEDIAWIKI && + preg_match( + '#^S5/([\w-]+)/((core|base|framing|pretty).css)$#s', $title->getText(), $m + ) + ) { + $file = __DIR__ . '/' . str_replace( '$skin', $m[1], self::$styles[$m[2]] ); + if ( file_exists( $file ) ) { + $article = new MWArticle( $title, $m[1], $file ); + return false; + } + } + return true; + } + + // Used to display CSS files on S5 skin CSS pages in edit mode + static function AlternateEdit( $editpage ) { + if ( $editpage->mArticle instanceof MWArticle && !$editpage->mArticle->exists() ) { + $editpage->mPreloadText = $editpage->mArticle->getPage()->getContent(); + } + return true; + } +} diff --git a/src/Skin.php b/src/Skin.php new file mode 100644 index 0000000..0b92981 --- /dev/null +++ b/src/Skin.php @@ -0,0 +1,44 @@ + + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ +namespace S5SlideShow; + +/** + * @author Vitaliy Filippov + * @package MediaWiki + * @subpackage Extensions + */ + +use SkinApi; +use SkinTemplate; + +/** + * see https://doc.wikimedia.org/mediawiki-core/master/php/classSkinApi.html + * + */ +class Skin extends SkinApi { + /** + * set up the skin user css + * @param OutputPage $out + */ + public function setupSkinUserCss( $out ) { + SkinTemplate::setupSkinUserCss( $out ); + } +} diff --git a/src/Special.php b/src/Special.php new file mode 100644 index 0000000..8aadc60 --- /dev/null +++ b/src/Special.php @@ -0,0 +1,51 @@ + + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +/** + * s5SlideShow SpecialPage for S5SlideShow extension + * + * @file + * @ingroup Extensions + */ +namespace S5SlideShow; + +use SpecialPage; + +class SpecialS5SlideShow extends SpecialPage { + public function __construct() { + parent::__construct( 's5SlideShow' ); + } + + /** + * Show the page to the user + * + * @param string $sub The subpage string argument (if any). + */ + public function execute( $sub ) { + $out = $this->getOutput(); + $out->setPageTitle( $this->msg( 'special-s5SlideShow-title' ) ); + $out->addHelpLink( 'S5SlideShow' ); + $out->addWikiMsg( 'special-s5SlideShow-intro' ); + } + + protected function getGroupName() { + return 'other'; + } +} diff --git a/yatil/bullet.gif b/yatil/bullet.gif new file mode 100644 index 0000000..b43de48 Binary files /dev/null and b/yatil/bullet.gif differ