├── screenshot.png ├── phpunit.xml ├── lib ├── wp-pluggable.php ├── okfn-base.php ├── okfn-annot-content-policy.php ├── okfn-annot-factory.php ├── okfn-utils.php ├── okfn-annot-settings.php └── okfn-annot-injector.php ├── templates ├── annotator-instance.js └── settings.html ├── Guardfile ├── test ├── test-bootstrap.php ├── lib │ ├── test-helpers.php │ └── okfn-test-case.php ├── okfn-utilsTest.php ├── okfn-annot-settingsTest.php ├── okfn-annot-factoryTest.php └── okfn-annot-injectorTest.php ├── okfn-annotator.php ├── readme.txt ├── vendor ├── javascripts │ ├── json2.min.js │ └── annotator │ │ └── annotator-full.min.js ├── Mustache.php └── stylesheets │ └── annotator.min.css ├── deploy.sh └── README.md /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openannotation/annotator-wordpress/HEAD/screenshot.png -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | test/okfn-utilsTest.php 5 | test/okfn-annot-settingsTest.php 6 | test/okfn-annot-injectorTest.php 7 | test/okfn-annot-factoryTest.php 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /lib/wp-pluggable.php: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /templates/annotator-instance.js: -------------------------------------------------------------------------------- 1 | jQuery(function ($) { 2 | var element= $('{{annotator_content}}'); 3 | 4 | if (element) { 5 | element.annotator() 6 | .annotator('setupPlugins', null, { 7 | Store: { 8 | annotationData: {uri: '{{uri}}'}, 9 | loadFromSearch: {uri: '{{uri}}', limit: 200} 10 | } 11 | }); 12 | } else { 13 | throw new Error("OkfnAnnotator: Unable to find a DOM element for selector '{{annotator_content}}'; cannot instantiate the Annotator"); 14 | } 15 | 16 | }); 17 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # A sample Guardfile 2 | # More info at https://github.com/guard/guard#readme 3 | # 4 | # Runs the phpunit test suite whenever a .php file in lib/ or test changes 5 | # 6 | 7 | require 'guard/guard' 8 | 9 | module ::Guard 10 | class PhpUnit < ::Guard::Guard 11 | def start 12 | Dir.chdir(File.dirname(File.expand_path(__FILE__))) 13 | end 14 | def run_on_change(paths) 15 | message = `phpunit` 16 | ::Guard::Notifier.notify(message, :title => 'PHPUnit') 17 | end 18 | def run_all 19 | true 20 | end 21 | end 22 | end 23 | 24 | 25 | 26 | guard 'phpunit', :cli => '--testdox ' do 27 | watch %r{^lib/(.+)\.php} 28 | watch %r{^test/(.+)\.php} 29 | watch %r{^phpunit.xml$} 30 | end 31 | -------------------------------------------------------------------------------- /test/test-bootstrap.php: -------------------------------------------------------------------------------- 1 | 27 | -------------------------------------------------------------------------------- /test/lib/test-helpers.php: -------------------------------------------------------------------------------- 1 | current_user; 39 | } 40 | 41 | ?> 42 | -------------------------------------------------------------------------------- /lib/okfn-base.php: -------------------------------------------------------------------------------- 1 | objectify_conf(); 11 | } 12 | 13 | /* 14 | * Returns a read only copy of the internal config object. 15 | * 16 | * returns an object. 17 | * 18 | */ 19 | public function get_conf() { 20 | return $this->conf; 21 | } 22 | 23 | 24 | 25 | /* 26 | * Turns the conf attribute from an array into an object. 27 | * 28 | * This is just a tiny bit of syntactical sugar which allows to 29 | * write $this->conf->bar rather than $this->conf['bar'] 30 | * 31 | * (PHP does not allow to assign an object litteral to an 32 | * attribute defined in the class declaration). 33 | * 34 | * 35 | */ 36 | private function objectify_conf(){ 37 | if ($this->conf) { 38 | $this->conf = OkfnUtils::Objectify($this->conf); 39 | } 40 | } 41 | 42 | 43 | } 44 | ?> 45 | -------------------------------------------------------------------------------- /test/okfn-utilsTest.php: -------------------------------------------------------------------------------- 1 | 1, 7 | 'bar' => 2, 8 | 'my-input-foo' => 3, 9 | 'my-input-bar' => 4 10 | ), true); 11 | 12 | $this->assertEquals($filtered, array( 13 | 'foo' => 3, 14 | 'bar' => 4 15 | )); 16 | } 17 | 18 | function test_objectify() { 19 | $object = OkfnUtils::objectify(array( 20 | 'a' => array( 21 | 'a1' => array( 22 | 'a1a' => 'hi!' 23 | ) 24 | ), 25 | 'b' 26 | )); 27 | $this->assertEquals($object->a->a1->a1a,'hi!'); 28 | } 29 | 30 | function test_unque_id() { 31 | for ($index = 0; $index < 3; $index++) { 32 | $ids[]=(int) OkfnUtils::unique_id($prefix=''); 33 | } 34 | 35 | $this->assertGreaterThan($ids[1],$ids[2]); 36 | $this->assertGreaterThan($ids[0],$ids[1]); 37 | } 38 | } 39 | ?> 40 | -------------------------------------------------------------------------------- /test/lib/okfn-test-case.php: -------------------------------------------------------------------------------- 1 | getMock($class, 21 | $methods, 22 | $constructor_params, 23 | $mock_classname, 24 | $call_constructor 25 | ); 26 | } 27 | 28 | } 29 | ?> 30 | -------------------------------------------------------------------------------- /okfn-annotator.php: -------------------------------------------------------------------------------- 1 | Annotator widget (by the Open Knowledge Foundation). 17 | Version: 0.4 18 | Author: Open Knowledge Foundation 19 | Author URI: http://okfn.org/projects/annotator/ 20 | License: GPLv2 or later 21 | */ 22 | 23 | 24 | foreach(array( 25 | 'lib/wp-pluggable', 26 | 'vendor/Mustache', 27 | 'lib/okfn-utils', 28 | 'lib/okfn-base', 29 | 'lib/okfn-annot-settings', 30 | 'lib/okfn-annot-content-policy', 31 | 'lib/okfn-annot-injector', 32 | 'lib/okfn-annot-factory', 33 | ) as $lib) require_once("${lib}.php"); 34 | 35 | $settings = new OkfnAnnotSettings; 36 | 37 | if (!is_admin()) { 38 | $factory = new OkfnAnnotFactory($settings); 39 | $content_policy = new OkfnAnnotContentPolicy($settings); 40 | $injector = new OkfnAnnotInjector($factory, $content_policy); 41 | $injector->inject(); 42 | } 43 | 44 | ?> 45 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === Annotator === 2 | Contributors: a-fiore 3 | Tags: annotation, okf, annotate.it 4 | Requires at least: 3.2.0 5 | Tested up to: 3.2.0 6 | Stable tag: 0.4 7 | 8 | Adds inline annotations to Wordpress using the Open Knowledge 9 | Foundation's Annotator tool. 10 | 11 | == Description == 12 | 13 | 14 | ## Features 15 | 16 | - Automatically includes the Annotator JavaScript code and its third party 17 | party dependencies (i.e. `jquery.js`, and `json2.js`) into the currently active theme (no HTML editing needed). 18 | - Offers the option to configure the annotable content area from the Wordpress settings page. 19 | - Provides blog administrators with the option to decide whether to display only authenticated users' annotations or also annotations made by anonymous users. 20 | - Provides a rudimentary regular expressions' based mechanism for configuring in what pages/blog sections the Annotator widget should be included. 21 | 22 | 23 | == Installation == 24 | 25 | 1. Upload the 'okfn-annotator' folder and place it into into `/wp-content/plugins/`. 26 | 2. Activate the plugin through the 'Plugins' menu in WordPress. 27 | 3. Finally, sign up at [AnnotateIt](http://annotateit.org) and 28 | fill your _Account ID_ and _Auth Token_ details in the Plugin settings 29 | form. 30 | -------------------------------------------------------------------------------- /lib/okfn-annot-content-policy.php: -------------------------------------------------------------------------------- 1 | settings = $settings; 24 | $pattern = stripslashes($settings->get_option('url_pattern')); 25 | $this->url_pattern = empty($pattern) ? '.*' : $pattern; 26 | } 27 | 28 | /* 29 | * Checks if a URL is annotable. 30 | * 31 | * url - a URL string; defaults to current url (optional). 32 | * 33 | * returns whether or not the annotator should be injected in this url. 34 | * 35 | */ 36 | function url_is_annotatable($url=null) { 37 | $url or $url = OkfnUtils::current_url(); 38 | $match= preg_match("/" . $this->url_pattern . "/", $url); 39 | return $match; 40 | } 41 | 42 | } 43 | ?> 44 | -------------------------------------------------------------------------------- /test/okfn-annot-settingsTest.php: -------------------------------------------------------------------------------- 1 | mockHelper('OkfnAnnotSettings',$this->methods_to_mock); 12 | $mock->expects($this->once()) 13 | ->method('register_menu'); 14 | 15 | $mock->__construct(); 16 | $mock->process_request(); 17 | 18 | } 19 | 20 | public function testShouldRenderTheSettingsForm() { 21 | $request_params=array('okfn-annot-wrong-submit-button' => true); 22 | $mock = $this->mockHelper('OkfnAnnotSettings',$this->methods_to_mock); 23 | 24 | 25 | $mock->expects($this->once()) 26 | ->method('render_settings_form'); 27 | 28 | $mock->expects($this->never()) 29 | ->method('process_settings_form'); 30 | 31 | $mock->process_request(); 32 | } 33 | 34 | 35 | public function testShouldProcessTheFormIfTheRightSubmitButtonIsSentOverWithTheRequest() { 36 | $request_params = array('okfn-annotsettings-submit' => true); //correct submit token 37 | 38 | $mock = $this->mockHelper('OkfnAnnotSettings',$this->methods_to_mock); 39 | 40 | $mock->expects($this->never()) 41 | ->method('render_settings_form'); 42 | 43 | $mock->expects($this->once()) 44 | ->method('process_settings_form') 45 | ->with($request_params); 46 | 47 | $mock->process_request($request_params); 48 | } 49 | 50 | protected function tearDown() { 51 | } 52 | } 53 | ?> 54 | -------------------------------------------------------------------------------- /test/okfn-annot-factoryTest.php: -------------------------------------------------------------------------------- 1 | setting' object. 12 | * 13 | */ 14 | 15 | 16 | function settings_get_option($option) { 17 | global $okfn_fixtures; 18 | return (string) @$okfn_fixtures->settings->$option; 19 | } 20 | 21 | 22 | 23 | 24 | class OkfnAnnotFactoryTest extends OkfnTestCase { 25 | 26 | function setUp() { 27 | //prepare test fixture 28 | global $okfn_fixtures; 29 | 30 | $okfn_fixtures->settings = (object) array( 31 | 'annotator_content' => '.a-selector', 32 | ); 33 | 34 | $okfn_fixtures->current_user = (object) array( 35 | 'ID' => '0', 36 | 'nickname' => '', 37 | ); 38 | 39 | $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; 40 | $_SERVER['HTTP_HOST'] = 'example.com'; 41 | $_SERVER['REQUEST_URI'] = '/hello-test'; 42 | 43 | 44 | //mock the settings object 45 | $this->settings_mock = $this->mockHelper('OkfnAnnotSettings',array('get_option'),array(), false); 46 | 47 | //stub the #get_options method so that it will return values from our fixtures 48 | $this->settings_mock->expects($this->any()) 49 | ->method('get_option') 50 | ->will($this->returnCallback('settings_get_option')); 51 | 52 | } 53 | 54 | 55 | function testCreateSnippet() { 56 | $factory_mock = $this->getMock('OkfnAnnotFactory', array('render_template'), array($this->settings_mock)); 57 | $factory_mock->expects($this->once())->method('render_template'); 58 | 59 | $factory_mock->create_snippet(); 60 | } 61 | } 62 | ?> 63 | -------------------------------------------------------------------------------- /lib/okfn-annot-factory.php: -------------------------------------------------------------------------------- 1 | settings = $settings; 27 | $this->annotator_content = $settings->get_option('annotator_content'); 28 | 29 | //todo: add load from search 30 | $this->uri = $this->get_current_uri(); 31 | } 32 | 33 | 34 | /* 35 | * 36 | * Fetches the URI of the currently visited blog page 37 | * 38 | * returns a uri string 39 | * 40 | */ 41 | 42 | private function get_current_uri(){ 43 | return OkfnUtils::current_url(); 44 | } 45 | 46 | /* 47 | * Wrapper method calling the Mustache template engine. 48 | * 49 | * (Needed mostly for testing and mocking...) 50 | * 51 | * 52 | * 53 | */ 54 | 55 | function render_template($template_vars, $template='annotator-instance.js') { 56 | $template = OkfnUtils::get_template($template); 57 | $mustache = new Mustache; 58 | return $mustache->render($template,$template_vars); 59 | } 60 | 61 | 62 | private function prepare_variables(){ 63 | $template_vars = array( 64 | 'annotator_content' => $this->annotator_content, 65 | 'uri' => $this->uri, 66 | ); 67 | 68 | return $template_vars; 69 | } 70 | 71 | /* 72 | * Generates a code snippet containing a customised Javascript instantiation 73 | * code for the Annotator. 74 | * 75 | * Returns a string or null if no instance is meant to be created. 76 | * 77 | */ 78 | function create_snippet() { 79 | return $this->render_template($this->prepare_variables()); 80 | } 81 | } 82 | ?> 83 | -------------------------------------------------------------------------------- /templates/settings.html: -------------------------------------------------------------------------------- 1 |
2 |

3 |

Annotator

4 | 5 | {{#form_submitted}} 6 |
7 |

Settings saved.

8 |
9 | {{/form_submitted}} 10 | 11 |
12 | 13 |

Embedding options

14 | 15 | 16 | 17 | 22 | 25 | 26 | 27 | 28 | 34 | 37 | 38 | 39 |
18 | 21 | 23 | 24 |
29 | 33 | 35 | 36 |
40 | 41 |
URL pattern examples
42 | 48 |

49 | 50 | 51 |

52 |
53 |
54 | 64 | -------------------------------------------------------------------------------- /vendor/javascripts/json2.min.js: -------------------------------------------------------------------------------- 1 | var JSON;JSON||(JSON={}); 2 | (function(){function k(a){return a<10?"0"+a:a}function o(a){p.lastIndex=0;return p.test(a)?'"'+a.replace(p,function(a){var c=r[a];return typeof c==="string"?c:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function l(a,j){var c,d,h,m,g=e,f,b=j[a];b&&typeof b==="object"&&typeof b.toJSON==="function"&&(b=b.toJSON(a));typeof i==="function"&&(b=i.call(j,a,b));switch(typeof b){case "string":return o(b);case "number":return isFinite(b)?String(b):"null";case "boolean":case "null":return String(b);case "object":if(!b)return"null"; 3 | e+=n;f=[];if(Object.prototype.toString.apply(b)==="[object Array]"){m=b.length;for(c=0;c= 5.\*.\* 18 | 19 | ## Install 20 | 21 | Just `git clone` this project into the `wp-content/plugins/` directory, then 22 | activate the plugin through the Wordpress administration panel accessible at `http:///wp-admin/plugins.php`. 23 | 24 | You will also need to sign up at [AnnotateIt](http://annotateit.org) and 25 | fill your _Account ID_ and _Auth Token_ details in the Plugin settings 26 | form. 27 | 28 | 29 | ## Demo 30 | 31 | A blog post showing the plugin in action can be found [here](http://wp-annotator.andreafiore.me/2011/05/26/hello-world/). The plugin settings for this specific installation are depicted in the screenshot below: 32 | 33 | wp-annotator settings screenshot 34 | 35 | 36 | ### Bugs and feature requests 37 | 38 | You are welcome to submit bug reports as well as ideas and feature 39 | requests using the [GitHub issue tracker](https://github.com/okfn/annotator-wordpress/issues). 40 | 41 | ### Running the unit tests 42 | 43 | The plugin comes with a fairly decent test suite. To run the tests you will need [PEAR](http://pear.php.net/), the PHP package manager. 44 | Once installed PEAR, you will also need to install the latest version of the PHPUnit package. This can be done by issuing the following commands: 45 | 46 | pear channel-discover pear.phpunit.de 47 | pear channel-discover components.ez.no 48 | pear channel-discover pear.symfony-project.com 49 | pear install phpunit/PHPUnit 50 | 51 | The test suite has to be run from within the project root 52 | directory by executing the phpunit command with no options or 53 | arguments. 54 | 55 | phpunit 56 | 57 | The test suite can also be configured to run automatically whenever the library code changes. This is desirable while developing, as it allows to spot 58 | regressions sooner rather than later, and to keep tests up to date. In order 59 | to automate the execution of the tests you will need to install and run 60 | [guard](https://github.com/guard/guard), a Ruby tool that allows to bind custom 61 | commands to file system events. The 'Guardfile' provided here contains a few useful configuration directives that allow doing this with PHPUnit tests. 62 | 63 | -------------------------------------------------------------------------------- /lib/okfn-utils.php: -------------------------------------------------------------------------------- 1 | array( 70 | * 'a1' => array( 71 | * 'a1a' => 'hi!' 72 | * ) 73 | * ), 74 | * 'b' 75 | * )); 76 | * 77 | * echo $object->a->a1->a1a //=> hi! 78 | * 79 | * returns a multi-dimensional object 80 | */ 81 | 82 | static function objectify($array) { 83 | 84 | if (is_array($array)) { 85 | return (object) array_map(array(__CLASS__, __FUNCTION__), $array); 86 | } else { 87 | return $array; 88 | } 89 | } 90 | 91 | /* 92 | * Generates a unique identifier by combining a prefix and an autoincremental integer 93 | * 94 | * prefix - a string to be prepended to the numeric id (optional). 95 | * 96 | * returns the unique identifer string. 97 | * 98 | */ 99 | static function unique_id($prefix='id_') { 100 | static $id; 101 | $id = isset($id) ? $id + 1 : 0; 102 | return "{$prefix}{$id}"; 103 | } 104 | 105 | /* 106 | * Gets the current page URL. 107 | * 108 | * 109 | * Returns a URL string. 110 | * 111 | */ 112 | 113 | static function current_url() { 114 | return 'http://' . preg_replace('/\/$/', '', $_SERVER['HTTP_HOST']) . 115 | $_SERVER['REQUEST_URI']; 116 | } 117 | 118 | } 119 | 120 | ?> 121 | -------------------------------------------------------------------------------- /test/okfn-annot-injectorTest.php: -------------------------------------------------------------------------------- 1 | constructor_arguments = array( 13 | $this->mockHelper('OkfnAnnotSettings',array(),array(),false), 14 | $this->mockHelper('OkfnAnnotContentPolicy',array('url_is_annotatable'),array(),false) 15 | ); 16 | } 17 | 18 | 19 | public function testAllAssetPathsShouldBeCorrect(){ 20 | $injector = new OkfnAnnotInjector(new stdClass, new stdClass); 21 | $conf = $injector->get_conf(); 22 | 23 | foreach(array('javascripts', 'stylesheets') as $asset_type) { 24 | foreach($conf->$asset_type as $asset) { 25 | $this->assertFileExists(dirname(dirname(__FILE__)) ."/{$conf->assets_basepath}{$asset->file}"); 26 | } 27 | } 28 | } 29 | 30 | public function testShouldLoadJavascriptDependencies() { 31 | list($settings_mock, $content_policy_mock) = $this->constructor_arguments; 32 | $injector_mock = $this->mockHelper('OkfnAnnotInjector', $this->methods_to_mock, $this->constructor_arguments); 33 | 34 | $content_policy_mock->expects($this->any()) 35 | ->method('url_is_annotatable') 36 | ->will($this->returnValue(true)); 37 | 38 | $injector_mock->expects($this->once()) 39 | ->method('load_javascript_dependencies'); 40 | 41 | $injector_mock->inject(); 42 | } 43 | 44 | 45 | public function testShouldLoadCorrectlyBothJavascriptAndStylesheetAssets() { 46 | list($settings_mock, $content_policy_mock) = $this->constructor_arguments; 47 | $injector_mock = $this->mockHelper('OkfnAnnotInjector',$this->methods_to_mock, $this->constructor_arguments); 48 | 49 | 50 | $content_policy_mock->expects($this->any()) 51 | ->method('url_is_annotatable') 52 | ->will($this->returnValue(true)); 53 | 54 | 55 | 56 | /* 57 | * NOTE: The value of the at() index does not refer the value of ->method(), but to stack of all the different 58 | * method calls done by the mocked object (which sucks...). 59 | * 60 | */ 61 | $injector_mock->expects($this->at(0)) 62 | ->method('wp_enqueue_asset') 63 | ->with('annotator.css', 64 | $this->anything(), 65 | $this->anything(), 66 | $this->anything(), 67 | false, 68 | 'stylesheets' 69 | ); 70 | 71 | 72 | $injector_mock->expects($this->at(2)) 73 | ->method('wp_enqueue_asset') 74 | ->with('jquery.js', 75 | $this->anything(), 76 | $this->anything(), 77 | $this->anything(), 78 | false, 79 | 'javascripts' 80 | ); 81 | 82 | $injector_mock->expects($this->at(3)) 83 | ->method('wp_enqueue_asset') 84 | ->with('annotator-full.js', 85 | $this->anything(), 86 | $this->anything(), 87 | $this->anything(), 88 | false, 89 | 'javascripts' 90 | ); 91 | 92 | 93 | $injector_mock->inject(); 94 | } 95 | 96 | public function testShouldRespectTheUserDefinedPolicy(){ 97 | list($settings_mock, $content_policy_mock) = $this->constructor_arguments; 98 | $mock_methods = array_merge($this->methods_to_mock, array('load_stylesheets','load_javascripts')); 99 | 100 | $injector_mock = $this->mockHelper('OkfnAnnotInjector',$mock_methods, $this->constructor_arguments); 101 | 102 | 103 | $content_policy_mock->expects($this->any()) 104 | ->method('url_is_annotatable') 105 | ->will($this->returnValue(false)); 106 | 107 | foreach($mock_methods as $method) { 108 | $injector_mock->expects($this->never()) 109 | ->method($method); 110 | } 111 | } 112 | 113 | } 114 | ?> 115 | -------------------------------------------------------------------------------- /lib/okfn-annot-settings.php: -------------------------------------------------------------------------------- 1 | 'okfn-annot', 10 | 'settings_page_title' => 'Annotator settings', 11 | 'menu_item_title' => 'Annotator', 12 | 'menu_item_identifier' => 'okfn-annotator', 13 | 'submit_parameter_name' => 'okfn-annotsettings-submit', 14 | 15 | //options exposed to the user and stored in wordpress settings 16 | 'default_options' => array( 17 | 'annotator_content' => '.entry-content', 18 | 'url_pattern' => '.*', 19 | ) 20 | ); 21 | 22 | function __construct(){ 23 | parent::__construct(); 24 | add_action('admin_menu', array($this,'register_menu')); 25 | } 26 | 27 | /* 28 | * Wrapper for calling wordpress internal 'get_option'. Its sole function is to 29 | * automatically add the plugin prefix defined in '$conf->forminput_prefix'. 30 | * 31 | * option - the name of the option to be retrieved 32 | * useprefix - whether or not the plugin prefix should be prepended; defaults to true (optional). 33 | * 34 | * 35 | * returns an option value 36 | */ 37 | 38 | function get_option($option,$prefix=true) { 39 | if ($prefix) { 40 | $option_name = $this->conf->forminput_prefix . '-' . $option; 41 | } 42 | 43 | return get_option($option_name); 44 | } 45 | 46 | /* 47 | * Registers the Annotator plugin settings page and its menu item 48 | * using wordpress special function 'add_options_page' 49 | * 50 | * returns nothing 51 | * 52 | */ 53 | 54 | function register_menu() { 55 | add_options_page( 56 | $this->conf->settings_page_title, 57 | $this->conf->menu_item_title, 58 | 'manage_options', //wordpress permission level 59 | $this->conf->menu_item_identifier, 60 | array($this,'process_request') //callback popoulating the settings page content 61 | ); 62 | } 63 | 64 | /* 65 | * Checks for the presence of the form submit tocken 66 | * 67 | * returns a boolean 68 | */ 69 | 70 | function form_is_submitted($params) { 71 | return isset($params[ $this->conf->submit_parameter_name ]); 72 | } 73 | 74 | /* 75 | * Sets the plugin options using the form input fields sent through the form 76 | * 77 | * 78 | * params - the request paramters 79 | * 80 | * returns nothing. 81 | * 82 | */ 83 | 84 | function process_settings_form($params){ 85 | $prefix = $this->conf->forminput_prefix; 86 | $options=OkfnUtils::filter_by_regexp("/^{$prefix}-/", $params); 87 | 88 | foreach($options as $option => $value) { 89 | print_r($option, $value); 90 | update_option($option, $value); 91 | } 92 | 93 | $this->render_settings_form($is_submit=true); 94 | } 95 | 96 | /* 97 | * 98 | * Renders the HTML for the Annotator settings options page. 99 | * 100 | * is_submit - boolean flag indicating whether data has been submitted 101 | * 102 | * 103 | * returns a rendered form template 104 | * 105 | */ 106 | 107 | function render_settings_form($is_submit=false) { 108 | 109 | $prefix = $this->conf->forminput_prefix; 110 | 111 | $options = array( 112 | 'prefix' => $prefix, 113 | 'action' => $_SERVER['REQUEST_URI'], 114 | 'submit_name' => $this->conf->submit_parameter_name, 115 | 'submit_value' => __('Save settings'), 116 | 'form_submitted' => $is_submit, 117 | ); 118 | 119 | foreach($this->conf->default_options as $optname => $value) { 120 | $stored_value = get_option("{$prefix}-{$optname}"); 121 | $options[$optname] = empty($stored_value) ? $value : $stored_value ; 122 | } 123 | 124 | //unescape backslashes automatically added by php for string sanitation 125 | $options['url_pattern'] = stripslashes($options['url_pattern']); 126 | 127 | $mustache = new Mustache; 128 | $template = OkfnUtils::get_template('settings.html'); 129 | print $mustache->render($template, $options); 130 | } 131 | 132 | /* 133 | * Public: 134 | * 135 | * Logical switch determining whether to render or to process the setting form. 136 | * 137 | * 138 | * params - the request parameters (defaults to $_POST if not provided). 139 | * 140 | * 141 | * returns nothing 142 | */ 143 | 144 | function process_request($params=array()) { 145 | 146 | if (empty($params)) { 147 | $params = $_POST; 148 | } 149 | 150 | 151 | ( $this->form_is_submitted($params)) ? 152 | $this->process_settings_form($params) : 153 | $this->render_settings_form() ; 154 | } 155 | 156 | } 157 | ?> 158 | -------------------------------------------------------------------------------- /lib/okfn-annot-injector.php: -------------------------------------------------------------------------------- 1 | '/vendor/', 14 | 15 | // In the sake of simplicity for now we only support the full minified version 16 | // of the annotator (A more fine grained control through the settings can be implemented later). 17 | 18 | 'javascripts' => array( 19 | array( 20 | 'file' => 'javascripts/jquery.min.js' 21 | ), 22 | array( 23 | 'file' => 'javascripts/annotator/annotator-full.min.js', 24 | 'dependencies' => array('json2','jquery'), 25 | ), 26 | ), 27 | 28 | 'stylesheets' => array( 29 | array( 30 | 'file' => 'stylesheets/annotator.min.css' 31 | ) 32 | ) 33 | ); 34 | private $factory; 35 | private $content_policy; 36 | 37 | function __construct($factory,$content_policy){ 38 | parent::__construct(); 39 | 40 | $this->factory = $factory; 41 | $this->content_policy = $content_policy; 42 | } 43 | 44 | /* 45 | * Enqueues the Annotator's dependencies (i.e. JSON2, and jQuery). 46 | * 47 | * returns nothing 48 | * 49 | */ 50 | function load_javascript_dependencies(){ 51 | wp_enqueue_script('json2'); 52 | //deregister the javascript version used by wordpress 53 | wp_deregister_script('jquery'); 54 | } 55 | 56 | /* 57 | * Public: 58 | * 59 | * Enqueues the Annotator javascript files 60 | * 61 | * returns nothing 62 | * 63 | */ 64 | public function load_javascripts() { 65 | $this->load_javascript_dependencies(); 66 | $this->load_assets('javascripts'); 67 | } 68 | 69 | /* 70 | * Public: 71 | * 72 | * Enqueues the Annotator stylesheet/s 73 | * 74 | * returns nothing 75 | * 76 | */ 77 | function load_stylesheets() { 78 | $this->load_assets('stylesheets'); 79 | } 80 | 81 | 82 | /* 83 | * Ensures that libraries are registered with the '.{js|css}' and not the '.min.{js|css}' prefix 84 | * 85 | * path - a relative path to an asset 86 | * 87 | * returns the asset id (stripped of the '.min' fragment ). 88 | * 89 | */ 90 | function asset_id($path) { 91 | return preg_replace('/(\.min)\.(js|css)$/','.$2', basename($path) ); 92 | } 93 | 94 | 95 | /* 96 | * Wrapper for wp_enqueue_{style|script} 97 | * 98 | * This has been implemented only for mocking/testing purposes. 99 | * 100 | * asset_id - The asset filename (without the path and the .min prefix). 101 | * asset_src - The asset url (relative to the plugin directory). 102 | * dependencies - Array of asset filenames specifying other assets that should be loaded first. 103 | * version_number - A version number to be passed to the library; defaults to Wordpress version number, (optional). 104 | * in_footer - Wether or not to load the library in the document footer rather than the header. 105 | * javascripts_or_stylesheets - Whether to load a javascript or a stylesheet asset 106 | * 107 | * returns nothing 108 | */ 109 | function wp_enqueue_asset($asset_id, $asset_src, $dependencies, $version_number, $in_footer, $javascripts_or_stylesheets) { 110 | 111 | $loader_function = ($javascripts_or_stylesheets === 'javascripts') ? 112 | 'wp_enqueue_script' : 113 | 'wp_enqueue_style'; 114 | 115 | call_user_func_array($loader_function, array($asset_id, 116 | plugins_url($asset_src , dirname(__FILE__)), 117 | $dependencies, $version_number, $in_footer 118 | )); 119 | } 120 | 121 | /* 122 | * Registers the annotator assets (as specified in $conf) and enqueues them using 123 | * the wordpress loading functions 124 | * 125 | * javascripts_or_stylesheets - Whether it should load javascripts or stylesheets. 126 | * 127 | * returns nothing 128 | */ 129 | 130 | private function load_assets($javascripts_or_stylesheets) { 131 | foreach($this->conf->$javascripts_or_stylesheets as $asset) { 132 | $in_footer = isset($asset->in_footer) && $asset->in_footer; 133 | $this->wp_enqueue_asset( 134 | $this->asset_id($asset->file), 135 | "{$this->conf->assets_basepath}{$asset->file}", 136 | isset($asset->dependencies) ? $asset->dependencies : array(), 137 | false, 138 | $in_footer, 139 | $javascripts_or_stylesheets 140 | ); 141 | 142 | } 143 | } 144 | 145 | /* 146 | * 147 | * Prints the instantiation snippet produced by the OkfnAnnotFactory object 148 | * 149 | * returns nothing 150 | */ 151 | function print_snippet() { 152 | $snippet = $this->factory->create_snippet(); 153 | if ($snippet) { 154 | print implode("\n",array('')); 155 | } 156 | } 157 | 158 | function inject() { 159 | if ($this->content_policy->url_is_annotatable()) { 160 | add_action('wp_print_styles', array($this,'load_stylesheets')); 161 | add_action('wp_print_scripts', array($this,'load_javascripts')); 162 | add_action('wp_footer', array($this,'print_snippet')); 163 | } 164 | } 165 | } 166 | ?> 167 | -------------------------------------------------------------------------------- /vendor/Mustache.php: -------------------------------------------------------------------------------- 1 | false, 24 | MustacheException::UNCLOSED_SECTION => true, 25 | MustacheException::UNEXPECTED_CLOSE_SECTION => true, 26 | MustacheException::UNKNOWN_PARTIAL => false, 27 | MustacheException::UNKNOWN_PRAGMA => true, 28 | ); 29 | 30 | // Override charset passed to htmlentities() and htmlspecialchars(). Defaults to UTF-8. 31 | protected $_charset = 'UTF-8'; 32 | 33 | /** 34 | * Pragmas are macro-like directives that, when invoked, change the behavior or 35 | * syntax of Mustache. 36 | * 37 | * They should be considered extremely experimental. Most likely their implementation 38 | * will change in the future. 39 | */ 40 | 41 | /** 42 | * The {{%DOT-NOTATION}} pragma allows context traversal via dots. Given the following context: 43 | * 44 | * $context = array('foo' => array('bar' => array('baz' => 'qux'))); 45 | * 46 | * One could access nested properties using dot notation: 47 | * 48 | * {{%DOT-NOTATION}}{{foo.bar.baz}} 49 | * 50 | * Which would render as `qux`. 51 | */ 52 | const PRAGMA_DOT_NOTATION = 'DOT-NOTATION'; 53 | 54 | /** 55 | * The {{%IMPLICIT-ITERATOR}} pragma allows access to non-associative array data in an 56 | * iterable section: 57 | * 58 | * $context = array('items' => array('foo', 'bar', 'baz')); 59 | * 60 | * With this template: 61 | * 62 | * {{%IMPLICIT-ITERATOR}}{{#items}}{{.}}{{/items}} 63 | * 64 | * Would render as `foobarbaz`. 65 | * 66 | * {{%IMPLICIT-ITERATOR}} accepts an optional 'iterator' argument which allows implicit 67 | * iterator tags other than {{.}} ... 68 | * 69 | * {{%IMPLICIT-ITERATOR iterator=i}}{{#items}}{{i}}{{/items}} 70 | */ 71 | const PRAGMA_IMPLICIT_ITERATOR = 'IMPLICIT-ITERATOR'; 72 | 73 | /** 74 | * The {{%UNESCAPED}} pragma swaps the meaning of the {{normal}} and {{{unescaped}}} 75 | * Mustache tags. That is, once this pragma is activated the {{normal}} tag will not be 76 | * escaped while the {{{unescaped}}} tag will be escaped. 77 | * 78 | * Pragmas apply only to the current template. Partials, even those included after the 79 | * {{%UNESCAPED}} call, will need their own pragma declaration. 80 | * 81 | * This may be useful in non-HTML Mustache situations. 82 | */ 83 | const PRAGMA_UNESCAPED = 'UNESCAPED'; 84 | 85 | /** 86 | * Constants used for section and tag RegEx 87 | */ 88 | const SECTION_TYPES = '\^#\/'; 89 | const TAG_TYPES = '#\^\/=!<>\\{&'; 90 | 91 | public $_otag = '{{'; 92 | public $_ctag = '}}'; 93 | 94 | protected $_tagRegEx; 95 | 96 | protected $_template = ''; 97 | protected $_context = array(); 98 | protected $_partials = array(); 99 | protected $_pragmas = array(); 100 | 101 | protected $_pragmasImplemented = array( 102 | self::PRAGMA_DOT_NOTATION, 103 | self::PRAGMA_IMPLICIT_ITERATOR, 104 | self::PRAGMA_UNESCAPED 105 | ); 106 | 107 | protected $_localPragmas = array(); 108 | 109 | /** 110 | * Mustache class constructor. 111 | * 112 | * This method accepts a $template string and a $view object. Optionally, pass an associative 113 | * array of partials as well. 114 | * 115 | * @access public 116 | * @param string $template (default: null) 117 | * @param mixed $view (default: null) 118 | * @param array $partials (default: null) 119 | * @return void 120 | */ 121 | public function __construct($template = null, $view = null, $partials = null) { 122 | if ($template !== null) $this->_template = $template; 123 | if ($partials !== null) $this->_partials = $partials; 124 | if ($view !== null) $this->_context = array($view); 125 | } 126 | 127 | /** 128 | * Mustache class clone method. 129 | * 130 | * A cloned Mustache instance should have pragmas, delimeters and root context 131 | * reset to default values. 132 | * 133 | * @access public 134 | * @return void 135 | */ 136 | public function __clone() { 137 | $this->_otag = '{{'; 138 | $this->_ctag = '}}'; 139 | $this->_localPragmas = array(); 140 | 141 | if ($keys = array_keys($this->_context)) { 142 | $last = array_pop($keys); 143 | if ($this->_context[$last] instanceof Mustache) { 144 | $this->_context[$last] =& $this; 145 | } 146 | } 147 | } 148 | 149 | /** 150 | * Render the given template and view object. 151 | * 152 | * Defaults to the template and view passed to the class constructor unless a new one is provided. 153 | * Optionally, pass an associative array of partials as well. 154 | * 155 | * @access public 156 | * @param string $template (default: null) 157 | * @param mixed $view (default: null) 158 | * @param array $partials (default: null) 159 | * @return string Rendered Mustache template. 160 | */ 161 | public function render($template = null, $view = null, $partials = null) { 162 | if ($template === null) $template = $this->_template; 163 | if ($partials !== null) $this->_partials = $partials; 164 | 165 | if ($view) { 166 | $this->_context = array($view); 167 | } else if (empty($this->_context)) { 168 | $this->_context = array($this); 169 | } 170 | 171 | $template = $this->_renderPragmas($template); 172 | return $this->_renderTemplate($template, $this->_context); 173 | } 174 | 175 | /** 176 | * Wrap the render() function for string conversion. 177 | * 178 | * @access public 179 | * @return string 180 | */ 181 | public function __toString() { 182 | // PHP doesn't like exceptions in __toString. 183 | // catch any exceptions and convert them to strings. 184 | try { 185 | $result = $this->render(); 186 | return $result; 187 | } catch (Exception $e) { 188 | return "Error rendering mustache: " . $e->getMessage(); 189 | } 190 | } 191 | 192 | /** 193 | * Internal render function, used for recursive calls. 194 | * 195 | * @access protected 196 | * @param string $template 197 | * @return string Rendered Mustache template. 198 | */ 199 | protected function _renderTemplate($template) { 200 | $template = $this->_renderSections($template); 201 | return $this->_renderTags($template); 202 | } 203 | 204 | /** 205 | * Render boolean, enumerable and inverted sections. 206 | * 207 | * @access protected 208 | * @param string $template 209 | * @return string 210 | */ 211 | protected function _renderSections($template) { 212 | while ($section_data = $this->_findSection($template)) { 213 | list($section, $offset, $type, $tag_name, $content) = $section_data; 214 | 215 | $replace = ''; 216 | $val = $this->_getVariable($tag_name); 217 | switch($type) { 218 | // inverted section 219 | case '^': 220 | if (empty($val)) { 221 | $replace .= $content; 222 | } 223 | break; 224 | 225 | // regular section 226 | case '#': 227 | if ($this->_varIsIterable($val)) { 228 | if ($this->_hasPragma(self::PRAGMA_IMPLICIT_ITERATOR)) { 229 | if ($opt = $this->_getPragmaOptions(self::PRAGMA_IMPLICIT_ITERATOR)) { 230 | $iterator = $opt['iterator']; 231 | } else { 232 | $iterator = '.'; 233 | } 234 | } else { 235 | $iterator = false; 236 | } 237 | 238 | foreach ($val as $local_context) { 239 | 240 | if ($iterator) { 241 | $iterator_context = array($iterator => $local_context); 242 | $this->_pushContext($iterator_context); 243 | } else { 244 | $this->_pushContext($local_context); 245 | } 246 | $replace .= $this->_renderTemplate($content); 247 | $this->_popContext(); 248 | } 249 | } else if ($val) { 250 | if (is_array($val) || is_object($val)) { 251 | $this->_pushContext($val); 252 | $replace .= $this->_renderTemplate($content); 253 | $this->_popContext(); 254 | } else { 255 | $replace .= $content; 256 | } 257 | } 258 | break; 259 | } 260 | 261 | $template = substr_replace($template, $replace, $offset, strlen($section)); 262 | } 263 | 264 | return $template; 265 | } 266 | 267 | /** 268 | * Prepare a section RegEx string for the given opening/closing tags. 269 | * 270 | * @access protected 271 | * @param string $otag 272 | * @param string $ctag 273 | * @return string 274 | */ 275 | protected function _prepareSectionRegEx($otag, $ctag) { 276 | return sprintf( 277 | '/(?:(?<=\\n)[ \\t]*)?%s(?P[%s])(?P.+?)%s\\n?/s', 278 | preg_quote($otag, '/'), 279 | self::SECTION_TYPES, 280 | preg_quote($ctag, '/') 281 | ); 282 | } 283 | 284 | /** 285 | * Extract a section from $template. 286 | * 287 | * This is a helper function to find sections needed by _renderSections. 288 | * 289 | * @access protected 290 | * @param string $template 291 | * @return array $section, $offset, $type, $tag_name and $content 292 | */ 293 | protected function _findSection($template) { 294 | $regEx = $this->_prepareSectionRegEx($this->_otag, $this->_ctag); 295 | 296 | $section_start = null; 297 | $section_type = null; 298 | $content_start = null; 299 | 300 | $search_offset = 0; 301 | 302 | $section_stack = array(); 303 | $matches = array(); 304 | while (preg_match($regEx, $template, $matches, PREG_OFFSET_CAPTURE, $search_offset)) { 305 | 306 | $match = $matches[0][0]; 307 | $offset = $matches[0][1]; 308 | $type = $matches['type'][0]; 309 | $tag_name = trim($matches['tag_name'][0]); 310 | 311 | $search_offset = $offset + strlen($match); 312 | 313 | switch ($type) { 314 | case '^': 315 | case '#': 316 | if (empty($section_stack)) { 317 | $section_start = $offset; 318 | $section_type = $type; 319 | $content_start = $search_offset; 320 | } 321 | array_push($section_stack, $tag_name); 322 | break; 323 | case '/': 324 | if (empty($section_stack) || ($tag_name !== array_pop($section_stack))) { 325 | if ($this->_throwsException(MustacheException::UNEXPECTED_CLOSE_SECTION)) { 326 | throw new MustacheException('Unexpected close section: ' . $tag_name, MustacheException::UNEXPECTED_CLOSE_SECTION); 327 | } 328 | } 329 | 330 | if (empty($section_stack)) { 331 | $section = substr($template, $section_start, $search_offset - $section_start); 332 | $content = substr($template, $content_start, $offset - $content_start); 333 | 334 | return array($section, $section_start, $section_type, $tag_name, $content); 335 | } 336 | break; 337 | } 338 | } 339 | 340 | if (!empty($section_stack)) { 341 | if ($this->_throwsException(MustacheException::UNCLOSED_SECTION)) { 342 | throw new MustacheException('Unclosed section: ' . $section_stack[0], MustacheException::UNCLOSED_SECTION); 343 | } 344 | } 345 | } 346 | 347 | /** 348 | * Prepare a pragma RegEx for the given opening/closing tags. 349 | * 350 | * @access protected 351 | * @param string $otag 352 | * @param string $ctag 353 | * @return string 354 | */ 355 | protected function _preparePragmaRegEx($otag, $ctag) { 356 | return sprintf( 357 | '/%s%%\\s*(?P[\\w_-]+)(?P(?: [\\w]+=[\\w]+)*)\\s*%s\\n?/s', 358 | preg_quote($otag, '/'), 359 | preg_quote($ctag, '/') 360 | ); 361 | } 362 | 363 | /** 364 | * Initialize pragmas and remove all pragma tags. 365 | * 366 | * @access protected 367 | * @param string $template 368 | * @return string 369 | */ 370 | protected function _renderPragmas($template) { 371 | $this->_localPragmas = $this->_pragmas; 372 | 373 | // no pragmas 374 | if (strpos($template, $this->_otag . '%') === false) { 375 | return $template; 376 | } 377 | 378 | $regEx = $this->_preparePragmaRegEx($this->_otag, $this->_ctag); 379 | return preg_replace_callback($regEx, array($this, '_renderPragma'), $template); 380 | } 381 | 382 | /** 383 | * A preg_replace helper to remove {{%PRAGMA}} tags and enable requested pragma. 384 | * 385 | * @access protected 386 | * @param mixed $matches 387 | * @return void 388 | * @throws MustacheException unknown pragma 389 | */ 390 | protected function _renderPragma($matches) { 391 | $pragma = $matches[0]; 392 | $pragma_name = $matches['pragma_name']; 393 | $options_string = $matches['options_string']; 394 | 395 | if (!in_array($pragma_name, $this->_pragmasImplemented)) { 396 | throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA); 397 | } 398 | 399 | $options = array(); 400 | foreach (explode(' ', trim($options_string)) as $o) { 401 | if ($p = trim($o)) { 402 | $p = explode('=', $p); 403 | $options[$p[0]] = $p[1]; 404 | } 405 | } 406 | 407 | if (empty($options)) { 408 | $this->_localPragmas[$pragma_name] = true; 409 | } else { 410 | $this->_localPragmas[$pragma_name] = $options; 411 | } 412 | 413 | return ''; 414 | } 415 | 416 | /** 417 | * Check whether this Mustache has a specific pragma. 418 | * 419 | * @access protected 420 | * @param string $pragma_name 421 | * @return bool 422 | */ 423 | protected function _hasPragma($pragma_name) { 424 | if (array_key_exists($pragma_name, $this->_localPragmas) && $this->_localPragmas[$pragma_name]) { 425 | return true; 426 | } else { 427 | return false; 428 | } 429 | } 430 | 431 | /** 432 | * Return pragma options, if any. 433 | * 434 | * @access protected 435 | * @param string $pragma_name 436 | * @return mixed 437 | * @throws MustacheException Unknown pragma 438 | */ 439 | protected function _getPragmaOptions($pragma_name) { 440 | if (!$this->_hasPragma($pragma_name)) { 441 | throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA); 442 | } 443 | 444 | return (is_array($this->_localPragmas[$pragma_name])) ? $this->_localPragmas[$pragma_name] : array(); 445 | } 446 | 447 | /** 448 | * Check whether this Mustache instance throws a given exception. 449 | * 450 | * Expects exceptions to be MustacheException error codes (i.e. class constants). 451 | * 452 | * @access protected 453 | * @param mixed $exception 454 | * @return void 455 | */ 456 | protected function _throwsException($exception) { 457 | return (isset($this->_throwsExceptions[$exception]) && $this->_throwsExceptions[$exception]); 458 | } 459 | 460 | /** 461 | * Prepare a tag RegEx for the given opening/closing tags. 462 | * 463 | * @access protected 464 | * @param string $otag 465 | * @param string $ctag 466 | * @return string 467 | */ 468 | protected function _prepareTagRegEx($otag, $ctag) { 469 | return sprintf( 470 | '/(?P(?<=\\n)[ \\t]*)?%s(?P[%s]?)(?P.+?)(?:\\2|})?%s(?:\\s*(?=\\n))?/s', 471 | preg_quote($otag, '/'), 472 | self::TAG_TYPES, 473 | preg_quote($ctag, '/') 474 | ); 475 | } 476 | 477 | /** 478 | * Loop through and render individual Mustache tags. 479 | * 480 | * @access protected 481 | * @param string $template 482 | * @return void 483 | */ 484 | protected function _renderTags($template) { 485 | if (strpos($template, $this->_otag) === false) { 486 | return $template; 487 | } 488 | 489 | $otag_orig = $this->_otag; 490 | $ctag_orig = $this->_ctag; 491 | 492 | $this->_tagRegEx = $this->_prepareTagRegEx($this->_otag, $this->_ctag); 493 | 494 | $html = ''; 495 | $matches = array(); 496 | while (preg_match($this->_tagRegEx, $template, $matches, PREG_OFFSET_CAPTURE)) { 497 | $tag = $matches[0][0]; 498 | $offset = $matches[0][1]; 499 | $modifier = $matches['type'][0]; 500 | $tag_name = trim($matches['tag_name'][0]); 501 | 502 | if (isset($matches['whitespace']) && $matches['whitespace'][1] > -1) { 503 | $whitespace = $matches['whitespace'][0]; 504 | } else { 505 | $whitespace = null; 506 | } 507 | 508 | $html .= substr($template, 0, $offset); 509 | 510 | $next_offset = $offset + strlen($tag); 511 | if ((substr($html, -1) == "\n") && (substr($template, $next_offset, 1) == "\n")) { 512 | $next_offset++; 513 | } 514 | $template = substr($template, $next_offset); 515 | 516 | $html .= $this->_renderTag($modifier, $tag_name, $whitespace); 517 | } 518 | 519 | $this->_otag = $otag_orig; 520 | $this->_ctag = $ctag_orig; 521 | 522 | return $html . $template; 523 | } 524 | 525 | /** 526 | * Render the named tag, given the specified modifier. 527 | * 528 | * Accepted modifiers are `=` (change delimiter), `!` (comment), `>` (partial) 529 | * `{` or `&` (don't escape output), or none (render escaped output). 530 | * 531 | * @access protected 532 | * @param string $modifier 533 | * @param string $tag_name 534 | * @throws MustacheException Unmatched section tag encountered. 535 | * @return string 536 | */ 537 | protected function _renderTag($modifier, $tag_name, $whitespace) { 538 | switch ($modifier) { 539 | case '=': 540 | return $this->_changeDelimiter($tag_name); 541 | break; 542 | case '!': 543 | return $this->_renderComment($tag_name); 544 | break; 545 | case '>': 546 | case '<': 547 | return $this->_renderPartial($tag_name, $whitespace); 548 | break; 549 | case '{': 550 | // strip the trailing } ... 551 | if ($tag_name[(strlen($tag_name) - 1)] == '}') { 552 | $tag_name = substr($tag_name, 0, -1); 553 | } 554 | case '&': 555 | if ($this->_hasPragma(self::PRAGMA_UNESCAPED)) { 556 | return $this->_renderEscaped($tag_name); 557 | } else { 558 | return $this->_renderUnescaped($tag_name); 559 | } 560 | break; 561 | case '#': 562 | case '^': 563 | case '/': 564 | // remove any leftovers from _renderSections 565 | return ''; 566 | break; 567 | } 568 | 569 | if ($this->_hasPragma(self::PRAGMA_UNESCAPED)) { 570 | return $this->_renderUnescaped($modifier . $tag_name); 571 | } else { 572 | return $this->_renderEscaped($modifier . $tag_name); 573 | } 574 | } 575 | 576 | /** 577 | * Escape and return the requested tag. 578 | * 579 | * @access protected 580 | * @param string $tag_name 581 | * @return string 582 | */ 583 | protected function _renderEscaped($tag_name) { 584 | return htmlentities($this->_getVariable($tag_name), ENT_COMPAT, $this->_charset); 585 | } 586 | 587 | /** 588 | * Render a comment (i.e. return an empty string). 589 | * 590 | * @access protected 591 | * @param string $tag_name 592 | * @return string 593 | */ 594 | protected function _renderComment($tag_name) { 595 | return ''; 596 | } 597 | 598 | /** 599 | * Return the requested tag unescaped. 600 | * 601 | * @access protected 602 | * @param string $tag_name 603 | * @return string 604 | */ 605 | protected function _renderUnescaped($tag_name) { 606 | return $this->_getVariable($tag_name); 607 | } 608 | 609 | /** 610 | * Render the requested partial. 611 | * 612 | * @access protected 613 | * @param string $tag_name 614 | * @return string 615 | */ 616 | protected function _renderPartial($tag_name, $whitespace = '') { 617 | $view = clone($this); 618 | 619 | return $whitespace . preg_replace('/\n(?!$)/s', "\n" . $whitespace, $view->render($this->_getPartial($tag_name))); 620 | } 621 | 622 | /** 623 | * Change the Mustache tag delimiter. This method also replaces this object's current 624 | * tag RegEx with one using the new delimiters. 625 | * 626 | * @access protected 627 | * @param string $tag_name 628 | * @return string 629 | */ 630 | protected function _changeDelimiter($tag_name) { 631 | list($otag, $ctag) = explode(' ', $tag_name); 632 | $this->_otag = $otag; 633 | $this->_ctag = $ctag; 634 | 635 | $this->_tagRegEx = $this->_prepareTagRegEx($this->_otag, $this->_ctag); 636 | 637 | return ''; 638 | } 639 | 640 | /** 641 | * Push a local context onto the stack. 642 | * 643 | * @access protected 644 | * @param array &$local_context 645 | * @return void 646 | */ 647 | protected function _pushContext(&$local_context) { 648 | $new = array(); 649 | $new[] =& $local_context; 650 | foreach (array_keys($this->_context) as $key) { 651 | $new[] =& $this->_context[$key]; 652 | } 653 | $this->_context = $new; 654 | } 655 | 656 | /** 657 | * Remove the latest context from the stack. 658 | * 659 | * @access protected 660 | * @return void 661 | */ 662 | protected function _popContext() { 663 | $new = array(); 664 | 665 | $keys = array_keys($this->_context); 666 | array_shift($keys); 667 | foreach ($keys as $key) { 668 | $new[] =& $this->_context[$key]; 669 | } 670 | $this->_context = $new; 671 | } 672 | 673 | /** 674 | * Get a variable from the context array. 675 | * 676 | * If the view is an array, returns the value with array key $tag_name. 677 | * If the view is an object, this will check for a public member variable 678 | * named $tag_name. If none is available, this method will execute and return 679 | * any class method named $tag_name. Failing all of the above, this method will 680 | * return an empty string. 681 | * 682 | * @access protected 683 | * @param string $tag_name 684 | * @throws MustacheException Unknown variable name. 685 | * @return string 686 | */ 687 | protected function _getVariable($tag_name) { 688 | if ($tag_name != '.' && strpos($tag_name, '.') !== false && $this->_hasPragma(self::PRAGMA_DOT_NOTATION)) { 689 | $chunks = explode('.', $tag_name); 690 | $first = array_shift($chunks); 691 | 692 | $ret = $this->_findVariableInContext($first, $this->_context); 693 | while ($next = array_shift($chunks)) { 694 | // Slice off a chunk of context for dot notation traversal. 695 | $c = array($ret); 696 | $ret = $this->_findVariableInContext($next, $c); 697 | } 698 | return $ret; 699 | } else { 700 | return $this->_findVariableInContext($tag_name, $this->_context); 701 | } 702 | } 703 | 704 | /** 705 | * Get a variable from the context array. Internal helper used by getVariable() to abstract 706 | * variable traversal for dot notation. 707 | * 708 | * @access protected 709 | * @param string $tag_name 710 | * @param array $context 711 | * @throws MustacheException Unknown variable name. 712 | * @return string 713 | */ 714 | protected function _findVariableInContext($tag_name, $context) { 715 | foreach ($context as $view) { 716 | if (is_object($view)) { 717 | if (method_exists($view, $tag_name)) { 718 | return $view->$tag_name(); 719 | } else if (isset($view->$tag_name)) { 720 | return $view->$tag_name; 721 | } 722 | } else if (is_array($view) && array_key_exists($tag_name, $view)) { 723 | return $view[$tag_name]; 724 | } 725 | } 726 | 727 | if ($this->_throwsException(MustacheException::UNKNOWN_VARIABLE)) { 728 | throw new MustacheException("Unknown variable: " . $tag_name, MustacheException::UNKNOWN_VARIABLE); 729 | } else { 730 | return ''; 731 | } 732 | } 733 | 734 | /** 735 | * Retrieve the partial corresponding to the requested tag name. 736 | * 737 | * Silently fails (i.e. returns '') when the requested partial is not found. 738 | * 739 | * @access protected 740 | * @param string $tag_name 741 | * @throws MustacheException Unknown partial name. 742 | * @return string 743 | */ 744 | protected function _getPartial($tag_name) { 745 | if (is_array($this->_partials) && isset($this->_partials[$tag_name])) { 746 | return $this->_partials[$tag_name]; 747 | } 748 | 749 | if ($this->_throwsException(MustacheException::UNKNOWN_PARTIAL)) { 750 | throw new MustacheException('Unknown partial: ' . $tag_name, MustacheException::UNKNOWN_PARTIAL); 751 | } else { 752 | return ''; 753 | } 754 | } 755 | 756 | /** 757 | * Check whether the given $var should be iterated (i.e. in a section context). 758 | * 759 | * @access protected 760 | * @param mixed $var 761 | * @return bool 762 | */ 763 | protected function _varIsIterable($var) { 764 | return $var instanceof Traversable || (is_array($var) && !array_diff_key($var, array_keys(array_keys($var)))); 765 | } 766 | } 767 | 768 | 769 | /** 770 | * MustacheException class. 771 | * 772 | * @extends Exception 773 | */ 774 | class MustacheException extends Exception { 775 | 776 | // An UNKNOWN_VARIABLE exception is thrown when a {{variable}} is not found 777 | // in the current context. 778 | const UNKNOWN_VARIABLE = 0; 779 | 780 | // An UNCLOSED_SECTION exception is thrown when a {{#section}} is not closed. 781 | const UNCLOSED_SECTION = 1; 782 | 783 | // An UNEXPECTED_CLOSE_SECTION exception is thrown when {{/section}} appears 784 | // without a corresponding {{#section}} or {{^section}}. 785 | const UNEXPECTED_CLOSE_SECTION = 2; 786 | 787 | // An UNKNOWN_PARTIAL exception is thrown whenever a {{>partial}} tag appears 788 | // with no associated partial. 789 | const UNKNOWN_PARTIAL = 3; 790 | 791 | // An UNKNOWN_PRAGMA exception is thrown whenever a {{%PRAGMA}} tag appears 792 | // which can't be handled by this Mustache instance. 793 | const UNKNOWN_PRAGMA = 4; 794 | 795 | } -------------------------------------------------------------------------------- /vendor/stylesheets/annotator.min.css: -------------------------------------------------------------------------------- 1 | .annotator-notice,.annotator-filter *,.annotator-widget *{font-family:"Helvetica Neue",Arial,Helvetica,sans-serif;font-weight:normal;text-align:left;margin:0;padding:0;background:0;-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none;-moz-box-shadow:none;-webkit-box-shadow:none;-o-box-shadow:none;box-shadow:none;color:#909090}.annotator-adder{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAAAwCAYAAAD+WvNWAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDowMzgwMTE3NDA3MjA2ODExODRCQUU5RDY0RTkyQTJDNiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDowOUY5RUFERDYwOEIxMUUxOTQ1RDkyQzU2OTNEMDZENCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDowOUY5RUFEQzYwOEIxMUUxOTQ1RDkyQzU2OTNEMDZENCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjA1ODAxMTc0MDcyMDY4MTE5MTA5OUIyNDhFRUQ1QkM4IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjAzODAxMTc0MDcyMDY4MTE4NEJBRTlENjRFOTJBMkM2Ii8+IDwvcmRmOkRlc2NyaXB0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');background-repeat:no-repeat}.annotator-resize,.annotator-widget::after,.annotator-editor a::after,.annotator-viewer .annotator-controls button,.annotator-viewer .annotator-controls a,.annotator-filter .annotator-filter-navigation button::after,.annotator-filter .annotator-filter-property .annotator-filter-clear{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAEiCAYAAAD0w4JOAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RDY0MTMzNTM2QUQzMTFFMUE2REJERDgwQTM3Njg5NTUiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RDY0MTMzNTQ2QUQzMTFFMUE2REJERDgwQTM3Njg5NTUiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo2ODkwQjlFQzZBRDExMUUxQTZEQkREODBBMzc2ODk1NSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpENjQxMzM1MjZBRDMxMUUxQTZEQkREODBBMzc2ODk1NSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PkijPpwAABBRSURBVHja7JsJVBRX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');background-repeat:no-repeat}.annotator-hl{background:rgba(255,255,10,0.3)}.annotator-wrapper{position:relative}.annotator-adder,.annotator-outer,.annotator-notice{z-index:9999}.annotator-adder,.annotator-outer,.annotator-widget,.annotator-notice{position:absolute;font-size:10px;line-height:1}.annotator-hide{display:none;visibility:hidden}.annotator-adder{margin-top:-48px;margin-left:-24px;width:48px;height:48px;background-position:left top}.annotator-adder:hover{background-position:center top}.annotator-adder:active{background-position:center right}.annotator-adder button{display:block;width:36px;height:41px;margin:0 auto;border:0;background:0;text-indent:-999em;cursor:pointer}.annotator-outer{width:0;height:0}.annotator-widget{margin:0;padding:0;bottom:15px;left:-18px;min-width:265px;background-color:rgba(251,251,251,0.98);border:1px solid rgba(122,122,122,0.6);-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;-webkit-box-shadow:0 5px 15px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 15px rgba(0,0,0,0.2);-o-box-shadow:0 5px 15px rgba(0,0,0,0.2);box-shadow:0 5px 15px rgba(0,0,0,0.2)}.annotator-invert-x .annotator-widget{left:auto;right:-18px}.annotator-invert-y .annotator-widget{bottom:auto;top:8px}.annotator-widget strong{font-weight:bold}.annotator-widget .annotator-listing,.annotator-widget .annotator-item{padding:0;margin:0;list-style:none}.annotator-widget::after{content:"";display:block;width:18px;height:10px;background-position:0 0;position:absolute;bottom:-10px;left:8px}.annotator-invert-x .annotator-widget::after{left:auto;right:8px}.annotator-invert-y .annotator-widget::after{background-position:0 -15px;bottom:auto;top:-9px}.annotator-widget .annotator-item,.annotator-editor .annotator-item input,.annotator-editor .annotator-item textarea{position:relative;font-size:12px}.annotator-viewer .annotator-item{border-top:2px solid rgba(122,122,122,0.2)}.annotator-widget .annotator-item:first-child{border-top:0}.annotator-editor .annotator-item,.annotator-viewer div{border-top:1px solid rgba(133,133,133,0.11)}.annotator-viewer div{padding:6px 6px}.annotator-viewer .annotator-item ol,.annotator-viewer .annotator-item ul{padding:4px 16px}.annotator-viewer div:first-of-type,.annotator-editor .annotator-item:first-child textarea{padding-top:12px;padding-bottom:12px;color:#3c3c3c;font-size:13px;font-style:italic;line-height:1.3;border-top:0}.annotator-viewer .annotator-controls{position:relative;top:5px;right:5px;padding-left:5px;opacity:0;-webkit-transition:opacity .2s ease-in;-moz-transition:opacity .2s ease-in;-o-transition:opacity .2s ease-in;transition:opacity .2s ease-in;float:right}.annotator-viewer li:hover .annotator-controls,.annotator-viewer li .annotator-controls.annotator-visible{opacity:1}.annotator-viewer .annotator-controls button,.annotator-viewer .annotator-controls a{cursor:pointer;display:inline-block;width:13px;height:13px;margin-left:2px;border:0;opacity:.2;text-indent:-900em;background-color:transparent;outline:0}.annotator-viewer .annotator-controls button:hover,.annotator-viewer .annotator-controls button:focus,.annotator-viewer .annotator-controls a:hover,.annotator-viewer .annotator-controls a:focus{opacity:.9}.annotator-viewer .annotator-controls button:active,.annotator-viewer .annotator-controls a:active{opacity:1}.annotator-viewer .annotator-controls button[disabled]{display:none}.annotator-viewer .annotator-controls .annotator-edit{background-position:0 -60px}.annotator-viewer .annotator-controls .annotator-delete{background-position:0 -75px}.annotator-viewer .annotator-controls .annotator-link{background-position:0 -270px}.annotator-editor .annotator-item{position:relative}.annotator-editor .annotator-item label{top:0;display:inline;cursor:pointer;font-size:12px}.annotator-editor .annotator-item input,.annotator-editor .annotator-item textarea{display:block;min-width:100%;padding:10px 8px;border:0;margin:0;color:#3c3c3c;background:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;resize:none}.annotator-editor .annotator-item textarea::-webkit-scrollbar{height:8px;width:8px}.annotator-editor .annotator-item textarea::-webkit-scrollbar-track-piece{margin:13px 0 3px;background-color:#e5e5e5;-webkit-border-radius:4px}.annotator-editor .annotator-item textarea::-webkit-scrollbar-thumb:vertical{height:25px;background-color:#ccc;-webkit-border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.1)}.annotator-editor .annotator-item textarea::-webkit-scrollbar-thumb:horizontal{width:25px;background-color:#ccc;-webkit-border-radius:4px}.annotator-editor .annotator-item:first-child textarea{min-height:5.5em;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;-o-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.annotator-editor .annotator-item input:focus,.annotator-editor .annotator-item textarea:focus{background-color:#f3f3f3;outline:0}.annotator-editor .annotator-item input[type=radio],.annotator-editor .annotator-item input[type=checkbox]{width:auto;min-width:0;padding:0;display:inline;margin:0 4px 0 0;cursor:pointer}.annotator-editor .annotator-checkbox{padding:8px 6px}.annotator-filter,.annotator-filter .annotator-filter-navigation button,.annotator-editor .annotator-controls{text-align:right;padding:3px;border-top:1px solid #d4d4d4;background-color:#d4d4d4;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),color-stop(0.6,#dcdcdc),to(#d2d2d2));background-image:-moz-linear-gradient(-90deg,#f5f5f5,#dcdcdc 60%,#d2d2d2);background-image:-webkit-linear-gradient(-90deg,#f5f5f5,#dcdcdc 60%,#d2d2d2);background-image:linear-gradient(-90deg,#f5f5f5,#dcdcdc 60%,#d2d2d2);-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,0.7),inset -1px 0 0 rgba(255,255,255,0.7),inset 0 1px 0 rgba(255,255,255,0.7);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,0.7),inset -1px 0 0 rgba(255,255,255,0.7),inset 0 1px 0 rgba(255,255,255,0.7);-o-box-shadow:inset 1px 0 0 rgba(255,255,255,0.7),inset -1px 0 0 rgba(255,255,255,0.7),inset 0 1px 0 rgba(255,255,255,0.7);box-shadow:inset 1px 0 0 rgba(255,255,255,0.7),inset -1px 0 0 rgba(255,255,255,0.7),inset 0 1px 0 rgba(255,255,255,0.7);-webkit-border-radius:0 0 5px 5px;-moz-border-radius:0 0 5px 5px;-o-border-radius:0 0 5px 5px;border-radius:0 0 5px 5px}.annotator-invert-y .annotator-controls{border-top:0;border-bottom:1px solid #b4b4b4;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;-o-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.annotator-editor a,.annotator-filter .annotator-filter-property label{position:relative;display:inline-block;padding:0 6px 0 22px;color:#363636;text-shadow:0 1px 0 rgba(255,255,255,0.75);text-decoration:none;line-height:24px;font-size:12px;font-weight:bold;border:1px solid #a2a2a2;background-color:#d4d4d4;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),color-stop(0.5,#d2d2d2),color-stop(0.5,#bebebe),to(#d2d2d2));background-image:-moz-linear-gradient(-90deg,#f5f5f5,#d2d2d2 50%,#bebebe 50%,#d2d2d2);background-image:-webkit-linear-gradient(-90deg,#f5f5f5,#d2d2d2 50%,#bebebe 50%,#d2d2d2);background-image:linear-gradient(-90deg,#f5f5f5,#d2d2d2 50%,#bebebe 50%,#d2d2d2);-webkit-box-shadow:inset 0 0 5px rgba(255,255,255,0.2),inset 0 0 1px rgba(255,255,255,0.8);-moz-box-shadow:inset 0 0 5px rgba(255,255,255,0.2),inset 0 0 1px rgba(255,255,255,0.8);-o-box-shadow:inset 0 0 5px rgba(255,255,255,0.2),inset 0 0 1px rgba(255,255,255,0.8);box-shadow:inset 0 0 5px rgba(255,255,255,0.2),inset 0 0 1px rgba(255,255,255,0.8);-webkit-border-radius:5px;-moz-border-radius:5px;-o-border-radius:5px;border-radius:5px}.annotator-editor a::after{position:absolute;top:50%;left:5px;display:block;content:"";width:15px;height:15px;margin-top:-7px;background-position:0 -90px}.annotator-editor a:hover,.annotator-editor a:focus,.annotator-editor a.annotator-focus,.annotator-filter .annotator-filter-active label,.annotator-filter .annotator-filter-navigation button:hover{outline:0;border-color:#435aa0;background-color:#3865f9;background-image:-webkit-gradient(linear,left top,left bottom,from(#7691fb),color-stop(0.5,#5075fb),color-stop(0.5,#3865f9),to(#3665fa));background-image:-moz-linear-gradient(-90deg,#7691fb,#5075fb 50%,#3865f9 50%,#3665fa);background-image:-webkit-linear-gradient(-90deg,#7691fb,#5075fb 50%,#3865f9 50%,#3665fa);background-image:linear-gradient(-90deg,#7691fb,#5075fb 50%,#3865f9 50%,#3665fa);color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.42)}.annotator-editor a:hover::after,.annotator-editor a:focus::after{margin-top:-8px;background-position:0 -105px}.annotator-editor a:active,.annotator-filter .annotator-filter-navigation button:active{border-color:#700c49;background-color:#d12e8e;background-image:-webkit-gradient(linear,left top,left bottom,from(#fc7cca),color-stop(0.5,#e85db2),color-stop(0.5,#d12e8e),to(#ff009c));background-image:-moz-linear-gradient(-90deg,#fc7cca,#e85db2 50%,#d12e8e 50%,#ff009c);background-image:-webkit-linear-gradient(-90deg,#fc7cca,#e85db2 50%,#d12e8e 50%,#ff009c);background-image:linear-gradient(-90deg,#fc7cca,#e85db2 50%,#d12e8e 50%,#ff009c)}.annotator-editor a.annotator-save::after{background-position:0 -120px}.annotator-editor a.annotator-save:hover::after,.annotator-editor a.annotator-save:focus::after,.annotator-editor a.annotator-save.annotator-focus::after{margin-top:-8px;background-position:0 -135px}.annotator-editor .annotator-widget::after{background-position:0 -30px}.annotator-editor.annotator-invert-y .annotator-widget .annotator-controls{background-color:#f2f2f2}.annotator-editor.annotator-invert-y .annotator-widget::after{background-position:0 -45px;height:11px}.annotator-resize{position:absolute;top:0;right:0;width:12px;height:12px;background-position:2px -150px}.annotator-invert-x .annotator-resize{right:auto;left:0;background-position:0 -195px}.annotator-invert-y .annotator-resize{top:auto;bottom:0;background-position:2px -165px}.annotator-invert-y.annotator-invert-x .annotator-resize{background-position:0 -180px}.annotator-notice{color:#fff;position:absolute;position:fixed;top:-54px;left:0;width:100%;font-size:14px;line-height:50px;text-align:center;background:black;background:rgba(0,0,0,0.9);border-bottom:4px solid #d4d4d4;-webkit-transition:top .4s ease-out;-moz-transition:top .4s ease-out;-o-transition:top .4s ease-out;transition:top .4s ease-out}.ie6 .annotator-notice{position:absolute}.annotator-notice-success{border-color:#3665f9}.annotator-notice-error{border-color:#ff7e00}.annotator-notice p{margin:0}.annotator-notice a{color:#fff}.annotator-notice-show{top:0}.annotator-tags{margin-bottom:-2px}.annotator-tags .annotator-tag{display:inline-block;padding:0 8px;margin-bottom:2px;line-height:1.6;font-weight:bold;background-color:#e6e6e6;-webkit-border-radius:8px;-moz-border-radius:8px;-o-border-radius:8px;border-radius:8px}.annotator-filter{z-index:9998;position:fixed;top:0;right:0;left:0;text-align:left;line-height:0;border:0;border-bottom:1px solid #878787;padding-left:10px;padding-right:10px;-webkit-border-radius:0;-moz-border-radius:0;-o-border-radius:0;border-radius:0;-webkit-box-shadow:inset 0 -1px 0 rgba(255,255,255,0.3);-moz-box-shadow:inset 0 -1px 0 rgba(255,255,255,0.3);-o-box-shadow:inset 0 -1px 0 rgba(255,255,255,0.3);box-shadow:inset 0 -1px 0 rgba(255,255,255,0.3)}.annotator-filter strong{font-size:12px;font-weight:bold;color:#3c3c3c;text-shadow:0 1px 0 rgba(255,255,255,0.7);position:relative;top:-9px}.annotator-filter .annotator-filter-property,.annotator-filter .annotator-filter-navigation{position:relative;display:inline-block;overflow:hidden;line-height:10px;padding:2px 0;margin-right:8px}.annotator-filter .annotator-filter-property label,.annotator-filter .annotator-filter-navigation button{text-align:left;display:block;float:left;line-height:20px;-webkit-border-radius:10px 0 0 10px;-moz-border-radius:10px 0 0 10px;-o-border-radius:10px 0 0 10px;border-radius:10px 0 0 10px}.annotator-filter .annotator-filter-property label{padding-left:8px}.annotator-filter .annotator-filter-property input{display:block;float:right;-webkit-appearance:none;background-color:#fff;border:1px solid #878787;border-left:none;padding:2px 4px;line-height:16px;min-height:16px;font-size:12px;width:150px;color:#333;background-color:#f8f8f8;-webkit-border-radius:0 10px 10px 0;-moz-border-radius:0 10px 10px 0;-o-border-radius:0 10px 10px 0;border-radius:0 10px 10px 0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.2);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.2);-o-box-shadow:inset 0 1px 1px rgba(0,0,0,0.2);box-shadow:inset 0 1px 1px rgba(0,0,0,0.2)}.annotator-filter .annotator-filter-property input:focus{outline:0;background-color:#fff}.annotator-filter .annotator-filter-clear{position:absolute;right:3px;top:6px;border:0;text-indent:-900em;width:15px;height:15px;background-position:0 -90px;opacity:.4}.annotator-filter .annotator-filter-clear:hover,.annotator-filter .annotator-filter-clear:focus{opacity:.8}.annotator-filter .annotator-filter-clear:active{opacity:1}.annotator-filter .annotator-filter-navigation button{border:1px solid #a2a2a2;padding:0;text-indent:-900px;width:20px;min-height:22px;-webkit-box-shadow:inset 0 0 5px rgba(255,255,255,0.2),inset 0 0 1px rgba(255,255,255,0.8);-moz-box-shadow:inset 0 0 5px rgba(255,255,255,0.2),inset 0 0 1px rgba(255,255,255,0.8);-o-box-shadow:inset 0 0 5px rgba(255,255,255,0.2),inset 0 0 1px rgba(255,255,255,0.8);box-shadow:inset 0 0 5px rgba(255,255,255,0.2),inset 0 0 1px rgba(255,255,255,0.8)}.annotator-filter .annotator-filter-navigation button,.annotator-filter .annotator-filter-navigation button:hover,.annotator-filter .annotator-filter-navigation button:focus{color:transparent}.annotator-filter .annotator-filter-navigation button::after{position:absolute;top:8px;left:8px;content:"";display:block;width:9px;height:9px;background-position:0 -210px}.annotator-filter .annotator-filter-navigation button:hover::after{background-position:0 -225px}.annotator-filter .annotator-filter-navigation .annotator-filter-next{-webkit-border-radius:0 10px 10px 0;-moz-border-radius:0 10px 10px 0;-o-border-radius:0 10px 10px 0;border-radius:0 10px 10px 0;border-left:none}.annotator-filter .annotator-filter-navigation .annotator-filter-next::after{left:auto;right:7px;background-position:0 -240px}.annotator-filter .annotator-filter-navigation .annotator-filter-next:hover::after{background-position:0 -255px}.annotator-hl-active{background:rgba(255,255,10,0.8)}.annotator-hl-filtered{background-color:transparent} -------------------------------------------------------------------------------- /vendor/javascripts/annotator/annotator-full.min.js: -------------------------------------------------------------------------------- 1 | ((function(){var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v=Array.prototype.slice,w=Object.prototype.hasOwnProperty,x=function(a,b){return function(){return a.apply(b,arguments)}},y=function(a,b){function d(){this.constructor=a}for(var c in b)w.call(b,c)&&(a[c]=b[c]);return d.prototype=b.prototype,a.prototype=new d,a.__super__=b.prototype,a},z=Array.prototype.indexOf||function(a){for(var b=0,c=this.length;b1?"["+d+"]":"",e="/"+c.tagName.toLowerCase()+d+e,c=c.parentNode;return e}),c.get()},a.escape=function(a){return a.replace(/&(?!\w+;)/g,"&").replace(//g,">").replace(/"/g,""")},a.fn.escape=function(b){return arguments.length?this.html(a.escape(b)):this.html()},j=["log","debug","info","warn","exception","assert","dir","dirxml","trace","group","groupEnd","groupCollapsed","time","timeEnd","profile","profileEnd","count","clear","table","error","notifyFirebug","firebug","userObjects"];if(typeof console!="undefined"&&console!==null){console.group==null&&(console.group=function(a){return console.log("GROUP: ",a)}),console.groupCollapsed==null&&(console.groupCollapsed=console.group);for(p=0,r=j.length;p0?h.start.splitText(h.startOffset):h.start,h.start===h.end?(h.endOffset-h.startOffsetr;0<=r?g++:g--){if(l[g]!==f[g])break;d.push(l[g])}c=j+d.join("/"),k.commonAncestorContainer=this._nodeFromXPath(c);if(!k.commonAncestorContainer)return console.error(u("Error deserializing range: can't find XPath '")+c+u("'. Is this the right document?")),null;s=["start","end"];for(n=0,p=s.length;n=this[i+"Offset"]){k[i+"Container"]=m,k[i+"Offset"]=this[i+"Offset"]-h;break}h+=m.nodeValue.length}}return(new e.BrowserRange(k)).normalize(b)},b.prototype.serialize=function(a,b){return this.normalize(a).serialize(a,b)},b.prototype.toObject=function(){return{start:this.start,startOffset:this.startOffset,end:this.end,endOffset:this.endOffset}},b}(),m={uuid:function(){var a;return a=0,function(){return a++}}(),getGlobal:function(){return function(){return this}()},mousePosition:function(b,c){var d;return d=a(c).offset(),{top:b.pageY-d.top,left:b.pageX-d.left}},preventEventDefault:function(a){return a!=null?typeof a.preventDefault=="function"?a.preventDefault():void 0:void 0}},n=this.Annotator,b=function(b){function c(b,d){this.onDeleteAnnotation=x(this.onDeleteAnnotation,this),this.onEditAnnotation=x(this.onEditAnnotation,this),this.onAdderClick=x(this.onAdderClick,this),this.onAdderMousedown=x(this.onAdderMousedown,this),this.onHighlightMouseover=x(this.onHighlightMouseover,this),this.checkForEndSelection=x(this.checkForEndSelection,this),this.checkForStartSelection=x(this.checkForStartSelection,this),this.clearViewerHideTimer=x(this.clearViewerHideTimer,this),this.startViewerHideTimer=x(this.startViewerHideTimer,this),this.showViewer=x(this.showViewer,this),this.onEditorSubmit=x(this.onEditorSubmit,this),this.onEditorHide=x(this.onEditorHide,this),this.showEditor=x(this.showEditor,this);var e,f,g;c.__super__.constructor.apply(this,arguments),this.plugins={};if(!c.supported())return this;this.options.readOnly||this._setupDocumentEvents(),this._setupWrapper()._setupViewer()._setupEditor(),g=this.html;for(e in g)f=g[e],e!=="wrapper"&&(this[e]=a(f).appendTo(this.wrapper).hide())}return y(c,b),c.prototype.events={".annotator-adder button click":"onAdderClick",".annotator-adder button mousedown":"onAdderMousedown",".annotator-hl mouseover":"onHighlightMouseover",".annotator-hl mouseout":"startViewerHideTimer"},c.prototype.html={hl:'',adder:'
",wrapper:'
'},c.prototype.options={readOnly:!1},c.prototype.plugins={},c.prototype.editor=null,c.prototype.viewer=null,c.prototype.selectedRanges=null,c.prototype.mouseIsDown=!1,c.prototype.ignoreMouseup=!1,c.prototype.viewerHideTimer=null,c.prototype._setupWrapper=function(){return this.wrapper=a(this.html.wrapper),this.element.find("script").remove(),this.element.wrapInner(this.wrapper),this.wrapper=this.element.find(".annotator-wrapper"),this},c.prototype._setupViewer=function(){var b=this;return this.viewer=new c.Viewer({readOnly:this.options.readOnly}),this.viewer.hide().on("edit",this.onEditAnnotation).on("delete",this.onDeleteAnnotation).addField({load:function(c,d){return d.text?a(c).escape(d.text):a(c).html(""+u("No Comment")+""),b.publish("annotationViewerTextField",[c,d])}}).element.appendTo(this.wrapper).bind({mouseover:this.clearViewerHideTimer,mouseout:this.startViewerHideTimer}),this},c.prototype._setupEditor=function(){return this.editor=new c.Editor,this.editor.hide().on("hide",this.onEditorHide).on("save",this.onEditorSubmit).addField({type:"textarea",label:u("Comments")+"…",load:function(b,c){return a(b).find("textarea").val(c.text||"")},submit:function(b,c){return c.text=a(b).find("textarea").val()}}),this.editor.element.appendTo(this.wrapper),this},c.prototype._setupDocumentEvents=function(){return a(document).bind({mouseup:this.checkForEndSelection,mousedown:this.checkForStartSelection}),this},c.prototype.getSelectedRanges=function(){var b,c,d,f,g,h,i,j,k;i=m.getGlobal().getSelection(),g=[],h=[],i.isCollapsed||(g=function(){var a,g;g=[];for(c=0,a=i.rangeCount;0<=a?ca;0<=a?c++:c--)f=i.getRangeAt(c),b=new e.BrowserRange(f),d=b.normalize().limit(this.wrapper[0]),d===null&&h.push(f),g.push(d);return g}.call(this),i.removeAllRanges());for(j=0,k=h.length;j0?setTimeout(function(){return c(a)},1):d.publish("annotationsLoaded",[b])},b=a.slice(),a.length&&c(a),this},c.prototype.dumpAnnotations=function(){return this.plugins.Store?this.plugins.Store.dumpAnnotations():console.warn(u("Can't dump annotations without Store plugin."))},c.prototype.highlightRange=function(b){var c,d,e,f,g,h;d=/^\s*$/,g=b.textNodes(),h=[];for(e=0,f=g.length;e tag?"))),this},c.prototype.showEditor=function(a,b){return this.editor.element.css(b),this.editor.load(a),this},c.prototype.onEditorHide=function(){return this.publish("annotationEditorHidden",[this.editor]),this.ignoreMouseup=!1},c.prototype.onEditorSubmit=function(a){return this.publish("annotationEditorSubmit",[this.editor,a]),a.ranges===void 0?this.setupAnnotation(a):this.updateAnnotation(a)},c.prototype.showViewer=function(a,b){return this.viewer.element.css(b),this.viewer.load(a),this.publish("annotationViewerShown",[this.viewer,a])},c.prototype.startViewerHideTimer=function(){if(!this.viewerHideTimer)return this.viewerHideTimer=setTimeout(this.viewer.hide,250)},c.prototype.clearViewerHideTimer=function(){return clearTimeout(this.viewerHideTimer),this.viewerHideTimer=!1},c.prototype.checkForStartSelection=function(a){if(!a||!this.isAnnotator(a.target))return this.startViewerHideTimer(),this.mouseIsDown=!0},c.prototype.checkForEndSelection=function(a){var b,c,d,e,f;this.mouseIsDown=!1;if(this.ignoreMouseup)return;this.selectedRanges=this.getSelectedRanges(),f=this.selectedRanges;for(d=0,e=f.length;d0&&this.invertX(),this},d.prototype.resetOrientation=function(){return this.element.removeClass(this.classes.invert.x).removeClass(this.classes.invert.y),this},d.prototype.invertX=function(){return this.element.addClass(this.classes.invert.x),this},d.prototype.invertY=function(){return this.element.addClass(this.classes.invert.y),this},d.prototype.isInvertedY=function(){return this.element.hasClass(this.classes.invert.y)},d.prototype.isInvertedX=function(){return this.element.hasClass(this.classes.invert.x)},d}(c),b.Editor=function(b){function c(b){this.onCancelButtonMouseover=x(this.onCancelButtonMouseover,this),this.processKeypress=x(this.processKeypress,this),this.submit=x(this.submit,this),this.load=x(this.load,this),this.hide=x(this.hide,this),this.show=x(this.show,this),c.__super__.constructor.call(this,a(this.html)[0],b),this.fields=[],this.annotation={}}return y(c,b),c.prototype.events={"form submit":"submit",".annotator-save click":"submit",".annotator-cancel click":"hide",".annotator-cancel mouseover":"onCancelButtonMouseover","textarea keydown":"processKeypress"},c.prototype.classes={hide:"annotator-hide",focus:"annotator-focus"},c.prototype.html='
\n
\n
    \n \n
    \n
    ",c.prototype.options={},c.prototype.show=function(a){return m.preventEventDefault(a),this.element.removeClass(this.classes.hide),this.element.find(".annotator-save").addClass(this.classes.focus),this.checkOrientation(),this.element.find(":input:first").focus(),this.setupDraggables(),this.publish("show")},c.prototype.hide=function(a){return m.preventEventDefault(a),this.element.addClass(this.classes.hide),this.publish("hide")},c.prototype.load=function(a){var b,c,d,e;this.annotation=a,this.publish("load",[this.annotation]),e=this.fields;for(c=0,d=e.length;c'),d.element=c[0];switch(d.type){case"textarea":e=a("