tosfos has uploaded a new change for review. https://gerrit.wikimedia.org/r/207327
Change subject: initial commit ...................................................................... initial commit Change-Id: Ia6824b4ada2774609945aebaa83fcd627b4b6461 --- A .gitmodules A .jshintrc A FlickrAPI.hooks.php A FlickrAPI.php A FlickrAPI.sql A FlickrAPICache.php A FlickrAPIUtils.php A i18n/en.json A i18n/qqq.json A modules/phpflickr 10 files changed, 631 insertions(+), 0 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/FlickrAPI refs/changes/27/207327/1 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..4414c1f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "modules/phpflickr"] + path = modules/phpflickr + url = https://github.com/dan-coulter/phpflickr.git diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..4cd914b --- /dev/null +++ b/.jshintrc @@ -0,0 +1,8 @@ +{ + "predef": [ + "mediaWiki", + "jQuery" + ], + "browser": true, + "smarttabs": true +} diff --git a/FlickrAPI.hooks.php b/FlickrAPI.hooks.php new file mode 100644 index 0000000..d4e48e0 --- /dev/null +++ b/FlickrAPI.hooks.php @@ -0,0 +1,205 @@ +<?php + +/** + * Hooks for FlickrAPI extension + * + * @file + * @ingroup Extensions + */ +class FlickrAPIHooks { + + /** + * Get output for the <flickr> tag + * + * @param string $optionsString + * @param array $args + * @param Parser $parser + * @return string + */ + public static function flickrAPITag( $optionsString, array $args, Parser $parser ) { + try { + $output = self::getOutput( $optionsString, $parser ); + } catch ( MWException $e ) { + return self::handleError( $e ); + } + //VALID? + return $output; + } + + /** + * Translate the options string from the user into a useful array + * + * @param string $optionsString + * @return array + */ + private static function extractOptions( $optionsString ) { + $parts = StringUtils::explode( '|', $optionsString ); + + $options = array( 'id' => $parts->current() ); + $parts->next(); + + $validSizes = self::getValidSizes(); + $validTypes = array( 'thumb', 'frame', 'frameless' ); + $validAligns = array( 'right', 'left', 'center', 'none' ); + # Okay now deal with parameters + /** @todo Copied from Flickr extension. Refactor. */ + while ( $parts->valid() ) { + $currentPart = strtolower( trim( $parts->current() ) ); + if ( empty( $options['type'] ) && in_array( $currentPart, $validTypes ) ) { + $options['type'] = $currentPart; + } elseif ( empty( $options['location'] ) && in_array( $currentPart, $validAligns ) ) { + $options['location'] = $currentPart; + } elseif ( empty( $options['size'] ) && array_key_exists( $currentPart, $validSizes ) ) { + $options['size'] = $currentPart; + } elseif ( empty( $options['caption'] ) ) { + $options['caption'] = $currentPart; + } else { + $options['caption'] .= '|' . $currentPart; + } + $parts->next(); + } + + return $options; + } + + /** + * Get the valid sizes available to the user. Some of these may not actually be available + * from the API for this image. + * + * @return array + */ + private static function getValidSizes() { + return array( + 's' => 'Square', + 't' => 'Thumbnail', + 'm' => 'Small', + '-' => 'Medium', + 'b' => 'Large', + ); + } + + /** + * Apply defaults for any parameter that has not been set within the <flickr> tag + * + * @global array $wgFlickrAPIDefaults + * @param array $options + * @param array $info + */ + private static function applyDefaults( array &$options, array $info ) { + global $wgFlickrAPIDefaults; + + if ( empty( $options['type'] ) ) { + $options['type'] = $wgFlickrAPIDefaults['type']; + } + if ( empty( $options['location'] ) ) { + $options['location'] = $wgFlickrAPIDefaults['location']; + } + if ( empty( $options['size'] ) ) { + $options['size'] = $wgFlickrAPIDefaults['size']; + } + if ( empty( $options['caption'] ) ) { + $options['caption'] = $info['photo']['title']['_content']; + } + } + + /** + * Send out an error message + * + * @param MWException $e + * @return string HTML + */ + private static function handleError( MWException $e ) { + return Html::element( 'strong', array( 'class' => array( 'error', 'FlickrAPIError' ) ), + $e->getMessage() ); + } + + /** + * Get an image link for this user input + * + * @global string $wgFlickrAPIKey + * @global string $wgFlickrAPISecret + * @global boolean $wgFlickrAPICache + * @global boolean $wgUseFileCache + * @global string $wgFileCacheDirectory + * @param string $optionsString + * @param Parser $parser + * @return string HTML + * @throws MWException + */ + private static function getOutput( $optionsString, Parser $parser ) { + global $wgFlickrAPIKey, $wgFlickrAPISecret, + $wgFlickrAPICache, $wgUseFileCache, $wgFileCacheDirectory; + + wfProfileIn( __METHOD__ ); + + $options = self::extractOptions( $optionsString ); + + if ( $wgFlickrAPIKey == '' ) { + throw new MWException( + 'Flickr Error ( No API key ): You must set $wgFlickrAPIKey!' ); + } + # Now check we have SOMEthing + if ( !$options['id'] ) { + /** @todo i18n? */ + throw new MWException( 'Flickr Error ( No ID ): Enter at least a PhotoID' ); + } + if ( !is_numeric( $options['id'] ) ) { + throw new MWException( 'Flickr Error ( Not a valid ID ): PhotoID not numeric' ); + } + + $phpFlickr = new phpFlickr( $wgFlickrAPIKey, $wgFlickrAPISecret ); + // Decide which cache to use, if any. + if ( $wgFlickrAPICache ) { + if ( $wgUseFileCache ) { + $phpFlickr->enableCache( 'fs', $wgFileCacheDirectory ); + } else { + $phpFlickr->enableCache( 'custom', + array( 'FlickrAPICache::getCache', 'FlickrAPICache::setCache' ) ); + } + } + + $info = $phpFlickr->photos_getInfo( $options['id'] ); + + self::applyDefaults( $options, $info ); + + $flickrSizes = $phpFlickr->photos_getSizes( $options['id'] ); + if ( !$flickrSizes ) { + throw new MWException( 'Flickr Error ( Photo not found ): PhotoID ' . $options['id'] ); + } + + $linkUrl = $info['photo']['urls']['url']['0']['_content']; + + $frameParams = array( + 'align' => $options['location'], + 'alt' => $options['caption'], + 'caption' => $options['caption'], + 'title' => $options['caption'], + 'link-url' => $linkUrl + ); + + if ( $options['type'] == 'thumb' ) { + $frameParams['thumbnail'] = true; + } elseif ( $options['type'] == 'frame' ) { + $frameParams['framed'] = true; + } + + $validSizes = self::getValidSizes(); + $handlerParams = array(); + foreach ( $flickrSizes as $flickrSize ) { + if ( $flickrSize['label'] === $validSizes[$options['size']] ) { + $handlerParams['width'] = $flickrSize['width']; + $url = $flickrSize['source']; + } + } + + if ( !isset( $url ) ) { + throw new MWException( 'Flickr Error ( Not a valid size ): Not found in this size' ); + } + + $handlerParams['custom-url-link'] = $linkUrl; + + wfProfileOut( __METHOD__ ); + return Html::rawElement( 'div', array( 'class' => 'flickrapi' ), + FlickrAPIUtils::makeImageLink( $parser, $url, $frameParams, $handlerParams ) ); + } +} diff --git a/FlickrAPI.php b/FlickrAPI.php new file mode 100644 index 0000000..8f57278 --- /dev/null +++ b/FlickrAPI.php @@ -0,0 +1,50 @@ +<?php +/** + * FlickrAPI extension + * + * For more info see http://mediawiki.org/wiki/Extension:FlickrAPI + * + * @file + * @ingroup Extensions + * @author Ike Hecht, 2015 + * @license GNU General Public Licence 2.0 or later + */ + +$wgExtensionCredits['parserhook'][] = array( + 'path' => __FILE__, + 'name' => 'FlickrAPI', + 'author' => array( + 'Ike Hecht', + ), + 'version' => '0.1.0', + 'url' => 'https://www.mediawiki.org/wiki/Extension:FlickrAPI', + 'descriptionmsg' => 'flickrapi-desc', +); + +/* Setup */ + +// Register files +$wgAutoloadClasses['FlickrAPIHooks'] = __DIR__ . '/FlickrAPI.hooks.php'; +$wgAutoloadClasses['FlickrAPIUtils'] = __DIR__ . '/FlickrAPIUtils.php'; +$wgAutoloadClasses['FlickrAPICache'] = __DIR__ . '/FlickrAPICache.php'; +/** @todo Spit out better error message if phpflickr module doesn't exist */ +$wgAutoloadClasses['phpFlickr'] = __DIR__ . '/modules/phpflickr/phpFlickr.php'; + +$wgMessagesDirs['FlickrAPI'] = __DIR__ . '/i18n'; + +// Register hooks +$wgHooks['ParserFirstCallInit'][] = function ( &$parser ) { + $parser->setHook( 'flickr', 'FlickrAPIHooks::flickrAPITag' ); + return true; +}; + +$wgHooks['LoadExtensionSchemaUpdates'][] = function ( DatabaseUpdater $updater ) { + $updater->addExtensionTable( FlickrAPICache::TABLE, __DIR__ . '/FlickrAPI.sql', true ); + return true; +}; + +/* Configuration */ +$wgFlickrAPIKey = ''; +$wgFlickrAPISecret = ''; +$wgFlickrAPICache = ( $wgMainCacheType === CACHE_NONE ) ? false : true; +$wgFlickrAPIDefaults = array( 'type' => 'frameless', 'location' => 'right', 'size' => '-' ); diff --git a/FlickrAPI.sql b/FlickrAPI.sql new file mode 100644 index 0000000..9ef4430 --- /dev/null +++ b/FlickrAPI.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS /*_*/FlickrAPI ( + `request` varchar(128) NOT NULL, + `response` mediumtext NOT NULL, + `expiration` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY `request` (`request`) +) /*$wgDBTableOptions*/; diff --git a/FlickrAPICache.php b/FlickrAPICache.php new file mode 100644 index 0000000..ef9a75e --- /dev/null +++ b/FlickrAPICache.php @@ -0,0 +1,54 @@ +<?php + +/** + * Custom db cache for Flickr API calls + * + * @author Ike Hecht + */ +class FlickrAPICache { + const TABLE = 'FlickrAPI'; + + /** + * Get this call from db cache + * + * @param string $reqhash + * @return string|boolean + */ + public static function getCache( $reqhash ) { + $dbr = wfGetDB( DB_SLAVE ); + $conds = array( 'request' => $reqhash, 'CURRENT_TIMESTAMP < expiration' ); + $result = $dbr->select( self::TABLE, 'response', $conds, __METHOD__ ); + + $row = $result->fetchObject(); + if ( $row ) { + return ( $row->response ); + } else { + return false; + } + } + + /** + * Store this call in cache + * + * @param string $reqhash + * @param string $response + * @param integer $cache_expire + * @return boolean + * @throws MWException + */ + public static function setCache( $reqhash, $response, $cache_expire ) { + $dbw = wfGetDB( DB_MASTER ); + $data = array( + 'request' => $reqhash, + 'response' => $response, + 'expiration' => $dbw->encodeExpiry( wfTimestamp( TS_MW, time() + $cache_expire ) ) + ); + #$result = mysqli_query( $this->cache_db, $sql ); + $result = $dbw->upsert( self::TABLE, $data, array( 'request' ), $data, __METHOD__ ); + if ( !$result ) { + throw new MWException( 'Set Cache failed' ); + } + + return $result; + } +} diff --git a/FlickrAPIUtils.php b/FlickrAPIUtils.php new file mode 100644 index 0000000..6047d4b --- /dev/null +++ b/FlickrAPIUtils.php @@ -0,0 +1,289 @@ +<?php + +/** + * Utilities, based on core MediaWiki code from version 1.24.2 + * + * @author Ike Hecht + */ +class FlickrAPIUtils { + + /** + * Scaled down version of Linker::makeImageLink + * + * Given parameters derived from [[Image:Foo|options...]], generate the + * HTML that that syntax inserts in the page. + * + * @param Parser $parser + * @param string $url URL of the image being displayed + * @param array $frameParams Associative array of parameters external to the media handler. + * Boolean parameters are indicated by presence or absence, the value is arbitrary and + * will often be false. + * thumbnail If present, downscale and frame + * manualthumb Image name to use as a thumbnail, instead of automatic scaling + * framed Shows image in original size in a frame + * frameless Downscale but don't frame + * upright If present, tweak default sizes for portrait orientation + * upright_factor Fudge factor for "upright" tweak (default 0.75) + * border If present, show a border around the image + * align Horizontal alignment (left, right, center, none) + * valign Vertical alignment (baseline, sub, super, top, text-top, middle, + * bottom, text-bottom) + * alt Alternate text for image (i.e. alt attribute). Plain text. + * class HTML for image classes. Plain text. + * caption HTML for image caption. + * link-url URL to link to + * link-target Value for the target attribute, only with link-url + * no-link Boolean, suppress description link + * + * @param array $handlerParams Associative array of media handler parameters, to be passed + * to transform(). Typical keys are "width" and "page". + * @param string|bool $time Timestamp of the file, set as false for current + * @param string $query Query params for desc url + * @param int|null $widthOption Used by the parser to remember the user preference thumbnailsize + * @since 1.20 + * @return string HTML for an image, with links, wrappers, etc. + */ + public static function makeImageLink( Parser $parser, $url, $frameParams = array(), + $handlerParams = array(), $time = false, $query = "" ) { + // Shortcuts + $fp = & $frameParams; + $hp = & $handlerParams; + + // Clean up parameters + if ( !isset( $fp['align'] ) ) { + $fp['align'] = ''; + } + if ( !isset( $fp['alt'] ) ) { + $fp['alt'] = ''; + } + if ( !isset( $fp['title'] ) ) { + $fp['title'] = ''; + } + if ( !isset( $fp['class'] ) ) { + $fp['class'] = ''; + } + + $prefix = $postfix = ''; + + if ( 'center' == $fp['align'] ) { + $prefix = '<div class="center">'; + $postfix = '</div>'; + $fp['align'] = 'none'; + } + + if ( isset( $fp['thumbnail'] ) || isset( $fp['manualthumb'] ) || isset( $fp['framed'] ) ) { + # Create a thumbnail. Alignment depends on the writing direction of + # the page content language (right-aligned for LTR languages, + # left-aligned for RTL languages) + # + # If a thumbnail width has not been provided, it is set + # to the default user option as specified in Language*.php + if ( $fp['align'] == '' ) { + $fp['align'] = $parser->getTargetLanguage()->alignEnd(); + } + return $prefix . self::makeThumbLink2( $url, $fp, $hp, $time, $query ) . $postfix; + } + + $params = array( + 'alt' => $fp['alt'], + 'title' => $fp['title'], + 'valign' => isset( $fp['valign'] ) ? $fp['valign'] : false, + 'img-class' => $fp['class'] ); + if ( isset( $fp['border'] ) ) { + $params['img-class'] .= ( $params['img-class'] !== '' ? ' ' : '' ) . 'thumbborder'; + } + $imageLinkparams = self::getImageLinkMTOParams( $fp, $query, $parser ) + $params; + + $s = self::thumbToHtml( $imageLinkparams, $url ); + + if ( $fp['align'] != '' ) { + $s = "<div class=\"float{$fp['align']}\">{$s}</div>"; + } + return str_replace( "\n", ' ', $prefix . $s . $postfix ); + } + + /** + * Scaled down & modified version of Linker::makeThumbLink2 + * + * @param string $url + * @param array $frameParams + * @param array $handlerParams + * @param bool $time + * @param string $query + * @return string + */ + public static function makeThumbLink2( $url, $frameParams = array(), $handlerParams = array(), + $time = false, $query = "" + ) { + # Shortcuts + $fp = & $frameParams; + $hp = & $handlerParams; + + if ( !isset( $fp['align'] ) ) { + $fp['align'] = 'right'; + } + if ( !isset( $fp['alt'] ) ) { + $fp['alt'] = ''; + } + if ( !isset( $fp['title'] ) ) { + $fp['title'] = ''; + } + if ( !isset( $fp['caption'] ) ) { + $fp['caption'] = ''; + } + + if ( empty( $hp['width'] ) ) { + // Reduce width for upright images when parameter 'upright' is used + $hp['width'] = isset( $fp['upright'] ) ? 130 : 180; + } + + $outerWidth = $hp['width'] + 2; + + $s = "<div class=\"thumb t{$fp['align']}\">" + . "<div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">"; + + $params = array( + 'alt' => $fp['alt'], + 'title' => $fp['title'], + 'img-class' => ( isset( $fp['class'] ) && $fp['class'] !== '' ? $fp['class'] . ' ' : '' ) + . 'thumbimage' + ); + $imageLinkparams = self::getImageLinkMTOParams( $fp, $query ) + $params; + $s .= self::thumbToHtml( $imageLinkparams, $url ); + + if ( isset( $fp['framed'] ) ) { + $zoomIcon = ""; + } else { + $zoomIcon = Html::rawElement( 'div', array( 'class' => 'magnify' ), + Html::rawElement( 'a', + array( + 'href' => $hp['custom-url-link'], + 'target' => $hp['custom-target-link'], /* added by this extension */ + 'title' => wfMessage( 'thumbnail-more' )->text() ), "" ) ); + } + $s .= ' <div class="thumbcaption">' . $zoomIcon . $fp['caption'] . "</div></div></div>"; + return str_replace( "\n", ' ', $s ); + } + + /** + * Scaled down version of Linker::getImageLinkMTOParams + * + * Get the link parameters for MediaTransformOutput::toHtml() from given + * frame parameters supplied by the Parser. + * @param array $frameParams The frame parameters + * @param string $query An optional query string to add to description page links + * @param Parser|null $parser + * @return array + */ + private static function getImageLinkMTOParams( $frameParams, $query = '', $parser = null ) { + $mtoParams = array(); + if ( isset( $frameParams['link-url'] ) && $frameParams['link-url'] !== '' ) { + $mtoParams['custom-url-link'] = $frameParams['link-url']; + if ( isset( $frameParams['link-target'] ) ) { + $mtoParams['custom-target-link'] = $frameParams['link-target']; + } + if ( $parser ) { + $extLinkAttrs = $parser->getExternalLinkAttribs( $frameParams['link-url'] ); + foreach ( $extLinkAttrs as $name => $val ) { + // Currently could include 'rel' and 'target' + $mtoParams['parser-extlink-' . $name] = $val; + } + } + } elseif ( isset( $frameParams['link-title'] ) && $frameParams['link-title'] !== '' ) { + $mtoParams['custom-title-link'] = self::normaliseSpecialPage( $frameParams['link-title'] ); + } elseif ( !empty( $frameParams['no-link'] ) ) { + // No link + } else { + $mtoParams['desc-link'] = true; + $mtoParams['desc-query'] = $query; + } + return $mtoParams; + } + + /** + * Scaled down version of ThumbnailImage::toHtml. Not all options are implemented yet. + * + * Return HTML <img ... /> tag for the thumbnail, will include + * width and height attributes and a blank alt text (as required). + * + * @param array $options Associative array of options. Boolean options + * should be indicated with a value of true for true, and false or + * absent for false. + * + * alt HTML alt attribute + * title HTML title attribute + * desc-link Boolean, show a description link + * file-link Boolean, show a file download link + * valign vertical-align property, if the output is an inline element + * img-class Class applied to the \<img\> tag, if there is such a tag + * desc-query String, description link query params + * override-width Override width attribute. Should generally not set + * override-height Override height attribute. Should generally not set + * no-dimensions Boolean, skip width and height attributes (useful if + * set in CSS) + * custom-url-link Custom URL to link to + * custom target-link Value of the target attribute, for custom-target-link + * parser-extlink-* Attributes added by parser for external links: + * parser-extlink-rel: add rel="nofollow" + * parser-extlink-target: link target, but overridden by custom-target-link + * + * For images, desc-link and file-link are implemented as a click-through. For + * sounds and videos, they may be displayed in other ways. + * @param string $url + * @return string + */ + public static function thumbToHtml( $options = array(), $url ) { + $alt = empty( $options['alt'] ) ? '' : $options['alt']; + + $query = empty( $options['desc-query'] ) ? '' : $options['desc-query']; + + if ( !empty( $options['custom-url-link'] ) ) { + $linkAttribs = array( 'href' => $options['custom-url-link'] ); + if ( !empty( $options['title'] ) ) { + $linkAttribs['title'] = $options['title']; + } + if ( !empty( $options['custom-target-link'] ) ) { + $linkAttribs['target'] = $options['custom-target-link']; + } elseif ( !empty( $options['parser-extlink-target'] ) ) { + $linkAttribs['target'] = $options['parser-extlink-target']; + } + if ( !empty( $options['parser-extlink-rel'] ) ) { + $linkAttribs['rel'] = $options['parser-extlink-rel']; + } + } elseif ( !empty( $options['custom-title-link'] ) ) { + // Do nothing - Titles not valid + } elseif ( !empty( $options['desc-link'] ) ) { + // Not implemented + } elseif ( !empty( $options['file-link'] ) ) { + // Not implemented + } else { + $linkAttribs = false; + } + + $attribs = array( + 'alt' => $alt, + 'src' => $url, + ); + + if ( !empty( $options['valign'] ) ) { + $attribs['style'] = "vertical-align: {$options['valign']}"; + } + if ( !empty( $options['img-class'] ) ) { + $attribs['class'] = $options['img-class']; + } + if ( isset( $options['override-height'] ) ) { + $attribs['height'] = $options['override-height']; + } + if ( isset( $options['override-width'] ) ) { + $attribs['width'] = $options['override-width']; + } + + // Copied from linkWrap function + $contents = Xml::element( 'img', $attribs ); + if ( $linkAttribs ) { + return Xml::tags( 'a', $linkAttribs, $contents ); + } else { + return $contents; + } + } +} diff --git a/i18n/en.json b/i18n/en.json new file mode 100644 index 0000000..48f7eb6 --- /dev/null +++ b/i18n/en.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Ike Hecht" + ] + }, + "flickrapi-desc": "Embeds Flickr images" +} \ No newline at end of file diff --git a/i18n/qqq.json b/i18n/qqq.json new file mode 100644 index 0000000..467e3d1 --- /dev/null +++ b/i18n/qqq.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Ike Hecht" + ] + }, + "flickrapi-desc": "{{desc|name=FlickrAPI|url=http://www.mediawiki.org/wiki/Extension:FlickrAPI}}" +} diff --git a/modules/phpflickr b/modules/phpflickr new file mode 160000 index 0000000..bc4f209 --- /dev/null +++ b/modules/phpflickr +Subproject commit bc4f2092b15d347e3d40c19fe0dbff8759fc8e51 -- To view, visit https://gerrit.wikimedia.org/r/207327 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Ia6824b4ada2774609945aebaa83fcd627b4b6461 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/FlickrAPI Gerrit-Branch: master Gerrit-Owner: tosfos <tos...@yahoo.com> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits