diff --git a/commons/res/layout/detail_main_panel.xml b/commons/res/layout/detail_main_panel.xml index dd6f9ae15..2a321f856 100644 --- a/commons/res/layout/detail_main_panel.xml +++ b/commons/res/layout/detail_main_panel.xml @@ -31,6 +31,14 @@ android:text="Description of the media goes here. This can potentially be fairly long, and will need to wrap across multiple lines. We hope it looks nice though." android:id="@+id/mediaDetailDesc" android:layout_gravity="left|start"/> + Download License + CC\u00A0Attribution-ShareAlike\u00A03.0 CC\u00A0Attribution\u00A03.0 CC0 + + + CC BY-SA 3.0 + CC BY-SA 3.0 (Austria) + CC BY-SA 3.0 (Germany) + CC BY-SA 3.0 (Estonia) + CC BY-SA 3.0 (Spain) + CC BY-SA 3.0 (Croatia) + CC BY-SA 3.0 (Luxembourg) + CC BY-SA 3.0 (Netherlands) + CC BY-SA 3.0 (Norway) + CC BY-SA 3.0 (Poland) + CC BY-SA 3.0 (Romania) + CC BY 3.0 + CC Zero + own-pd + CC BY-SA 2.5 + CC BY 2.5 + CC BY-SA 2.0 + CC BY-SA 2.0 + CC BY 2.0 + Free Art License + Public domain (author died over 100 years ago) + Public domain (copyright expired) + Public domain (art) + Public domain (US) + Public domain (US government) + Public domain (NASA) + Public domain (ineligible for copyright) + Attribution + GNU Free Documentation License + Contribute your images. Help Wikipedia articles come to life! Images on Wikipedia come from Wikimedia Commons. Your images help educate people around the world. diff --git a/commons/res/xml/wikimedia_licenses.xml b/commons/res/xml/wikimedia_licenses.xml new file mode 100644 index 000000000..e23bcbfc5 --- /dev/null +++ b/commons/res/xml/wikimedia_licenses.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/commons/src/main/java/org/wikimedia/commons/License.java b/commons/src/main/java/org/wikimedia/commons/License.java new file mode 100644 index 000000000..caa9bc4d6 --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/License.java @@ -0,0 +1,46 @@ +package org.wikimedia.commons; + +public class License { + String key; + String template; + String url; + String name; + + public License(String key, String template, String url, String name) { + if (key == null) { + throw new RuntimeException("License.key must not be null"); + } + if (template == null) { + throw new RuntimeException("License.template must not be null"); + } + this.key = key; + this.template = template; + this.url = url; + this.name = name; + } + + public String getKey() { + return key; + } + + public String getTemplate() { + return template; + } + + public String getName() { + if (name == null) { + // hack + return getKey(); + } else { + return name; + } + } + + public String getUrl(String language) { + if (url == null) { + return null; + } else { + return url.replace("$lang", language); + } + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/LicenseList.java b/commons/src/main/java/org/wikimedia/commons/LicenseList.java new file mode 100644 index 000000000..b9da572b2 --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/LicenseList.java @@ -0,0 +1,75 @@ +package org.wikimedia.commons; + +import android.app.Activity; +import android.content.res.Resources; +import android.util.Log; +import org.xmlpull.v1.XmlPullParser; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class LicenseList { + Map licenses = new HashMap(); + Resources res; + + private static String XMLNS_LICENSE = "https://www.mediawiki.org/wiki/Extension:UploadWizard/xmlns/licenses"; + + public LicenseList(Activity activity) { + res = activity.getResources(); + XmlPullParser parser = res.getXml(R.xml.wikimedia_licenses); + while (Utils.xmlFastForward(parser, XMLNS_LICENSE, "license")) { + String id = parser.getAttributeValue(null, "id"); + String template = parser.getAttributeValue(null, "template"); + String url = parser.getAttributeValue(null, "url"); + String name = nameForTemplate(template); + License license = new License(id, template, url, name); + licenses.put(id, license); + } + + } + + public Set keySet() { + return licenses.keySet(); + } + + public Collection values() { + return licenses.values(); + } + + public License get(String key) { + return licenses.get(key); + } + + public License licenseForTemplate(String template) { + String ucTemplate = Utils.capitalize(template); + for (License license : values()) { + if (ucTemplate.equals(Utils.capitalize(license.getTemplate()))) { + return license; + } + } + return null; + } + + public String nameIdForTemplate(String template) { + // hack :D + // cc-by-sa-3.0 -> cc_by_sa_3_0 + return "license_name_" + template.toLowerCase().replace("-", "_").replace(".", "_"); + } + + private int stringIdByName(String stringId) { + return res.getIdentifier("org.wikimedia.commons:string/" + stringId, null, null); + } + + public String nameForTemplate(String template) { + Log.d("Commons", "LicenseList.nameForTemplate: template: " + template); + String stringId = nameIdForTemplate(template); + Log.d("Commons", "LicenseList.nameForTemplate: stringId: " + stringId); + int nameId = stringIdByName(stringId); + Log.d("Commons", "LicenseList.nameForTemplate: nameId: " + nameId); + String name = res.getString(nameId); + Log.d("Commons", "LicenseList.nameForTemplate: name: " + name); + return name; + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/MediaDataExtractor.java b/commons/src/main/java/org/wikimedia/commons/MediaDataExtractor.java index 8f0c049dd..357025615 100644 --- a/commons/src/main/java/org/wikimedia/commons/MediaDataExtractor.java +++ b/commons/src/main/java/org/wikimedia/commons/MediaDataExtractor.java @@ -33,16 +33,19 @@ public class MediaDataExtractor { private Map descriptions; private String author; private Date date; + private String license; + private LicenseList licenseList; /** * @param filename of the target media object, should include 'File:' prefix */ - public MediaDataExtractor(String filename) { + public MediaDataExtractor(String filename, LicenseList licenseList) { this.filename = filename; categories = new ArrayList(); descriptions = new HashMap(); fetched = false; processed = false; + this.licenseList = licenseList; } /** @@ -114,7 +117,41 @@ public class MediaDataExtractor { descriptions = getMultilingualText(descriptionNode); Node authorNode = findTemplateParameter(templateNode, "author"); - author = Utils.getStringFromDOM(authorNode); + author = getFlatText(authorNode); + } + + /* + Pull up the license data list... + look for the templates in two ways: + * look for 'self' template and check its first parameter + * if none, look for any of the known templates + */ + Log.d("Commons", "MediaDataExtractor searching for license"); + Node selfLicenseNode = findTemplate(doc.getDocumentElement(), "self"); + if (selfLicenseNode != null) { + Node firstNode = findTemplateParameter(selfLicenseNode, 1); + String licenseTemplate = getFlatText(firstNode); + License license = licenseList.licenseForTemplate(licenseTemplate); + if (license == null) { + Log.d("Commons", "MediaDataExtractor found no matching license for self parameter: " + licenseTemplate + "; faking it"); + this.license = licenseTemplate; // hack hack! For non-selectable licenses that are still in the system. + } else { + // fixme: record the self-ness in here too... sigh + // all this needs better server-side metadata + this.license = license.getKey(); + Log.d("Commons", "MediaDataExtractor found self-license " + this.license); + } + } else { + for (License license : licenseList.values()) { + String templateName = license.getTemplate(); + Node template = findTemplate(doc.getDocumentElement(), templateName); + if (template != null) { + // Found! + this.license = license.getKey(); + Log.d("Commons", "MediaDataExtractor found non-self license " + this.license); + break; + } + } } } @@ -201,6 +238,10 @@ public class MediaDataExtractor { throw new IOException("No matching template parameter node found."); } + private String getFlatText(Node parentNode) throws IOException { + return parentNode.getTextContent(); + } + // Extract a dictionary of multilingual texts from a subset of the parse tree. // Texts are wrapped in things like {{en|foo} or {{en|1=foo bar}}. // Text outside those wrappers is stuffed into a 'default' faux language key if present. @@ -246,6 +287,9 @@ public class MediaDataExtractor { media.setCategories(categories); media.setDescriptions(descriptions); + if (license != null) { + media.setLicense(license); + } // add author, date, etc fields } diff --git a/commons/src/main/java/org/wikimedia/commons/Utils.java b/commons/src/main/java/org/wikimedia/commons/Utils.java index 49868d57d..54e5f3013 100644 --- a/commons/src/main/java/org/wikimedia/commons/Utils.java +++ b/commons/src/main/java/org/wikimedia/commons/Utils.java @@ -10,6 +10,8 @@ import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.net.URLCodec; import org.w3c.dom.Node; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; import javax.xml.transform.*; import java.io.*; @@ -194,4 +196,33 @@ public class Utils { String uriStr = CommonsApplication.HOME_URL + urlEncode(underscored); return Uri.parse(uriStr); } + + /** + * Fast-forward an XmlPullParser to the next instance of the given element + * in the input stream (namespaced). + * + * @param parser + * @param namespace + * @param element + * @return true on match, false on failure + */ + public static boolean xmlFastForward(XmlPullParser parser, String namespace, String element) { + try { + while (parser.next() != XmlPullParser.END_DOCUMENT) { + if (parser.getEventType() == XmlPullParser.START_TAG && + parser.getNamespace().equals(namespace) && + parser.getName().equals(element)) { + // We found it! + return true; + } + } + return false; + } catch (XmlPullParserException e) { + e.printStackTrace(); + return false; + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } } diff --git a/commons/src/main/java/org/wikimedia/commons/media/MediaDetailFragment.java b/commons/src/main/java/org/wikimedia/commons/media/MediaDetailFragment.java index e06d441b8..a09638bd3 100644 --- a/commons/src/main/java/org/wikimedia/commons/media/MediaDetailFragment.java +++ b/commons/src/main/java/org/wikimedia/commons/media/MediaDetailFragment.java @@ -59,6 +59,7 @@ public class MediaDetailFragment extends SherlockFragment { private TextView title; private TextView desc; + private TextView license; private ListView listView; private ArrayList categoryNames; private boolean categoriesLoaded = false; @@ -133,6 +134,7 @@ public class MediaDetailFragment extends SherlockFragment { spacer = (MediaDetailSpacer) detailView.findViewById(R.id.mediaDetailSpacer); title = (TextView) detailView.findViewById(R.id.mediaDetailTitle); desc = (TextView) detailView.findViewById(R.id.mediaDetailDesc); + license = (TextView) detailView.findViewById(R.id.mediaDetailLicense); // Enable or disable editing on the title /* @@ -160,10 +162,12 @@ public class MediaDetailFragment extends SherlockFragment { // FIXME: cache this data detailFetchTask = new AsyncTask() { private MediaDataExtractor extractor; + private LicenseList licenseList; @Override protected void onPreExecute() { - extractor = new MediaDataExtractor(media.getFilename()); + licenseList = new LicenseList(getActivity()); + extractor = new MediaDataExtractor(media.getFilename(), licenseList); } @Override @@ -187,6 +191,16 @@ public class MediaDetailFragment extends SherlockFragment { // Fill some fields desc.setText(media.getDescription("en")); + String licenseKey = media.getLicense(); + License licenseObj = licenseList.get(licenseKey); + if (licenseObj == null) { + license.setText(licenseKey); + } else { + license.setText(licenseObj.getName()); + } + Log.d("Commons", "Media license is: " + media.getLicense()); + + categoryNames.removeAll(categoryNames); categoryNames.addAll(media.getCategories()); @@ -231,7 +245,8 @@ public class MediaDetailFragment extends SherlockFragment { } title.setText(media.getDisplayTitle()); - desc.setText(""); + desc.setText(""); // fill in from network... + license.setText(""); // fill in from network... /* title.addTextChangedListener(new TextWatcher() { diff --git a/update-license-info/Makefile b/update-license-info/Makefile new file mode 100644 index 000000000..a234667a5 --- /dev/null +++ b/update-license-info/Makefile @@ -0,0 +1,14 @@ +.FAKE : build update clean install + +build : ../commons/res/xml/wikimedia_licenses.xml + +../commons/res/xml/wikimedia_licenses.xml : licenses.php mediawiki-extensions-UploadWizard + php licenses.php > ../commons/res/xml/wikimedia_licenses.xml + +mediawiki-extensions-UploadWizard : update + +update : + if [ -d mediawiki-extensions-UploadWizard ]; then (cd mediawiki-extensions-UploadWizard && git pull origin master); else git clone https://gerrit.wikimedia.org/r/mediawiki/extensions/UploadWizard mediawiki-extensions-UploadWizard; fi + +clean : + rm -rf mediawiki-extensions-UploadWizard diff --git a/update-license-info/include-stubs.php b/update-license-info/include-stubs.php new file mode 100644 index 000000000..c0a01a0d6 --- /dev/null +++ b/update-license-info/include-stubs.php @@ -0,0 +1,68 @@ + 'English' ); + } +} +$wgMemc = new FakeMemc(); + +class FakeMessage { + function plain() { + return 'stub-message-plain'; + } + function parse() { + return 'stub-message-parsed'; + } +} + +function wfMessage() { + return new FakeMessage(); +} + +/** + * Converts shorthand byte notation to integer form + * + * @param $string String + * @return Integer + */ +function wfShorthandToInteger( $string = '' ) { + $string = trim( $string ); + if ( $string === '' ) { + return -1; + } + $last = $string[strlen( $string ) - 1]; + $val = intval( $string ); + switch ( $last ) { + case 'g': + case 'G': + $val *= 1024; + // break intentionally missing + case 'm': + case 'M': + $val *= 1024; + // break intentionally missing + case 'k': + case 'K': + $val *= 1024; + } + + return $val; +} + +$wgAPIModules = array(); diff --git a/update-license-info/licenses.php b/update-license-info/licenses.php new file mode 100644 index 000000000..badda1a08 --- /dev/null +++ b/update-license-info/licenses.php @@ -0,0 +1,71 @@ + +// 2013-09-30 + +require 'include-stubs.php'; +$config = require "mediawiki-extensions-UploadWizard/UploadWizard.config.php"; +require "mediawiki-extensions-UploadWizard/UploadWizard.i18n.php"; +$licenseList = array(); + +foreach ( $config['licenses'] as $key => $license ) { + // Determine template -> license mappings + if ( isset( $license['templates'] ) ) { + $templates = $license['templates']; + } else { + $templates = array( $key ); + } + + if ( count( $templates ) < 1 ) { + throw new Exception("No templates for $key, this is wrong."); + } + if ( count( $templates ) > 1 ) { + //echo "Skipping multi-template license: $key\n"; + continue; + } + $template = $templates[0]; + if ( preg_match( '/^subst:/i', $template ) ) { + //echo "Skipping subst license: $key\n"; + continue; + } + + $msg = $messages['en'][$license['msg']]; + + $licenseInfo = array( + 'desc' => $msg, + 'template' => $template + ); + if ( isset( $license['url'] ) ) { + $url = $license['url']; + if ( substr( $url, 0, 2 ) == '//' ) { + $url = 'https:' . $url; + } + if ( isset( $license['languageCodePrefix'] ) ) { + $url .= $license['languageCodePrefix'] . '$lang'; + } + $licenseInfo['url'] = $url; + } + $licenseList[$key] = $licenseInfo; +} + +//var_dump( $licenseList ); + +echo "\n"; +echo "\n"; +foreach( $licenseList as $key => $licenseInfo ) { + $encId = htmlspecialchars( $key ); + echo " \n"; + +} +echo "\n"; + \ No newline at end of file