Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

How to save annotations with store plugin? #711

Open
ANONIMNIQ opened this issue Apr 8, 2020 · 20 comments
Open

How to save annotations with store plugin? #711

ANONIMNIQ opened this issue Apr 8, 2020 · 20 comments

Comments

@ANONIMNIQ
Copy link

I test annotator on wordpress site, stable version works great, but can't understand how to use store plugin to save annotations. I use this code:

`jQuery(function ($) {
$('.main-container').annotator()
.annotator('addPlugin', 'Store', {
prefix: 'http://mysite.com/api' ,
annotationData: {
'uri': '/annotations'
},

urls: {
    create:  '/highlighter?page_id=',
    update:  '/update',
    destroy: '/delete',
    search:  '/annotations?page_id='
}

});
});
`
the plugin gets error messeges and in the console i see this error:

API request failed: '200'

I'm not sure how to make it work.

@alordiel
Copy link

Hi I'm successfully using this with WordPress. I will share some of my code here.
First here is how I include the scripts (there are two scripts):

wp_enqueue_script(
	'annotator',
	THEME_REL . '/assets/annotator.min.js',
	'jquery',
	filemtime( THEME_ABS . '/assets/annotator.min.js' ),
        true
);

wp_enqueue_script(
	'e-edition-script',
  	THEME_REL . '/assets/e-edition.js',
        jquery',
        filemtime( THEME_ABS . '/assets/e-edition.js' ),
	true
);

The e-edition.js script contains the following script:

jQuery(function($) {
    let app = new annotator.App();
    app.include(annotator.ui.main);
    app.include(annotator.storage.http, {
      prefix: theme_l10n.siteURL +'/wp-json/annotation-notes/v1'
    });
    app.start().then(function () {
    	app.annotations.load();
    })

});

Note: theme_l10n.siteURL is passed through wp_localize_script function and actually this is site_url().

The last thing you will need is to build your API like so:

add_action( 'rest_api_init', 'annotation_note_api');
function annotation_note_api() {
  register_rest_route( 'annotation-notes/v1', '/annotations/([\w-]+)', array(
    // DELETE
    array(
      'methods'       => WP_REST_Server::DELETABLE,
      'callback'        => 'delete_annotation_note'
    ),
    // PUT
    array(
      'methods'       => WP_REST_Server::EDITABLE,
      'callback'        => 'edit_annotation_note'
    )
  ));

  register_rest_route( 'annotation-notes/v1', '/annotations/', array(
    // POST
    array(
      'methods'       => WP_REST_Server::CREATABLE,
      'callback'        => 'add_annotation_note'
    )
  ));

  register_rest_route( 'annotation-notes/v1', '/search/', array(
    // GET
    array(
      'methods'       => WP_REST_Server::READABLE,
      'callback'        => 'get_all_notes'
    )
  ));
}

Note: All the callback are actually your functions that are going to be executed on the specific API call. You will need to code those on your own depending on what you want.

@ANONIMNIQ
Copy link
Author

Thank you, I try it but now I have this errors:

Failed to load resource: the server responded with a status of 403 ()
You don't have permission to perform this operation! (Error 403)

Maybe I can't configure my api correctly, I don't know. Annotator loads correctly, but can't save anything.
BTW И аз съм българин, може да ми отговаряш на български, ако ще се разберем по-добре! :)

@alordiel
Copy link

Здрасти, предпочитам на английски да го караме, че може да е от полза на някой друг с подобен пробле.
So do you get 403 on the API calls from the annotator ? What is the URL that gets that forbidden error?
I know few reasons - firewall on the server, or something like sucuri, or some security plugin. If you are using some plugin line iThemes Security, it is possible to have active setting from there for restrictions on the WP REST API.

@ANONIMNIQ
Copy link
Author

ANONIMNIQ commented May 16, 2020

I tried it on a blank test site, you can see it here:
https://tezi.tk
I don't have security plugins installed right now, but I'm not sure if I configured API correctly.

@alordiel
Copy link

Thanks for the link. I've checked the site and found that even https://tezi.tk/wp-json returns 403 which means that error would be valid for any API call. I've checked the WP REST API documentation and found that the most possible problem for this is the permalink. Most probably they are in the standard (default) ugly format. Try to change them to Post name and give it a try again (but my guess is that this time you will get 404 error). Also check the results of https://tezi.tk/wp-json in the routes if you have the annotation-notes route. Currently I'm not seeing it there (that is why I think you will get 404). So my guess is that there is problem with the registration of the endpoints (of the file where you have put them is not loaded). Also check the handbook for custom endpoints in WordPress for some debugging hints.

@ANONIMNIQ
Copy link
Author

ANONIMNIQ commented May 16, 2020

Can you check now? I get 404 could not connect to the annotation store, but I think https://tezi.tk/wp-json and annotation-notes are working now.
btw I'm not realy sure how to make callback functions for this cases (add, edit, delete notes etc.)

@alordiel
Copy link

The callbacks are easy to create. Check the code related to the add_action( 'rest_api_init', 'annotation_note_api'); There you have each method (DELETE, GET, PUT, POST). And each of them are having a callback parameter. You need to create function with that names so once the API is called it will trigger that callback function. In this documentation you will see details on how to create the callback (as those function should receive some request data form the API).

Also where have you paste the API code (the PHP one). If you are adding thin into a theme it should be in the functions.php file. Make sure your error login is on so you can see if there are any errors related to that functionality.

@ANONIMNIQ
Copy link
Author

Ok, thank you! I previously put API code (without callback part, because I don't have functions right now) in ScriptsnStyles plugin, now I move it to functions.php, but it's the same result. I enabled debug and error log, but can't find any errors related to annotator. I will try to create callbacks but can't understand are they needed for annotation store to function properly? Is this 404 error related to this, or there is something else?

@alordiel
Copy link

I think there is another problem here (for the 404) I can see in https://tezi.tk/wp-json the new api for the annotations. But their namespace is wp-json/annotation-notes/v1 which is wrong. Should be just annotation-notes/v1. Can you paste the code for the registration of the endpoint.

I will try to create callbacks but can't understand are they needed for annotation store to function properly?

When an annotation is made it need to be stored some where, so next time the page is loaded it will check for the saved annotations and will fetch them if there is a match. Without the callback you can't save or search for the annotations. Basically your callback are going to do just that: save annotation, get annotation, delete annotation. And you will need to provide that functionality in the callbacks.

@ANONIMNIQ
Copy link
Author

add_action( 'rest_api_init', 'annotation_note_api');
function annotation_note_api() {
  register_rest_route( 'wp-json/annotation-notes/v1', '/annotations/([\w-]+)', array(
    // DELETE
    array(
      'methods'       => WP_REST_Server::DELETABLE
    ),
    // PUT
    array(
      'methods'       => WP_REST_Server::EDITABLE
    )
  ));

  register_rest_route( 'wp-json/annotation-notes/v1', '/annotations/', array(
    // POST
    array(
      'methods'       => WP_REST_Server::CREATABLE
    )
  ));

  register_rest_route( 'wp-json/annotation-notes/v1', '/search/', array(
    // GET
    array(
      'methods'       => WP_REST_Server::READABLE
    )
  ));
}
jQuery(function($) {
    let app = new annotator.App();
    app.include(annotator.ui.main);
    app.include(annotator.storage.http, {
      prefix: 'https://tezi.tk/wp-json/annotation-notes/v1'
    });
    app.start().then(function () {
    	app.annotations.load();
    })

});

@alordiel
Copy link

On each place where you have 'wp-json/annotation-notes/v1' in the php code, you should change to 'annotation-notes/v1'. The prefix wp-json is added by default.

The way you have registered the endpoints will point to https://tezi.tk/wp-json/wp-json/annotation-notes/v1, in JS you are making request to different place: https://tezi.tk/wp-json/annotation-notes/v1. And that is way you get 404.

@alordiel
Copy link

Ah, and you are missing the callback parameter. This most probably will end in Fatal error (500 or 503). You need to add callback to make the annotation functional.

@ANONIMNIQ
Copy link
Author

Ok, fix this but as you said now I have 500 error, even with callback parameter, maybe because I don't create the corresponding functions. Can you help me with some examples of how to make functions for read, add, edit and save annotations, I'm not really sure how my functions to looks like because I never used API calls before.

@alordiel
Copy link

What is your use case of the annotation tool? I can past my callback here but may be you need something else.

@ANONIMNIQ
Copy link
Author

What is your use case of the annotation tool? I can past my callback here but may be you need something else.

Sorry for the late response! For one of my sites I want to use annotator as comment tool, I want unregistered users to make annotations, and all annotations to be visible for all visitors, but only I as admin to have privileges to edit or delete them. I'm not sure how easy will be to manage all published annotations and if there is any way to prevent spam.
For my other site I want only registered users to create annotations and to have privileges to edit or delete their own annotations, but I want all published annotations to be visible for all users, even non registered ones.

@alordiel
Copy link

Ok, so my case is quite simpler. I needed to store annotations per user. And each annotation is visible only to the use that have created it. Any way, You might find my code useful and may be modify it to fit your needs. Here is the code:

dd_action( 'rest_api_init', 'aa_note_api' );
function aa_note_api() {
	register_rest_route( 'annotation-notes/v1', '/annotations/([\w-]+)', array(
		// DELETE
		array(
			'methods'  => WP_REST_Server::DELETABLE,
			'callback' => 'annotations_delete_annotation_note'
		),
		// PUT
		array(
			'methods'  => WP_REST_Server::EDITABLE,
			'callback' => 'annotations_edit_annotation_note'
		)
	) );

	register_rest_route( 'annotation-notes/v1', '/annotations/', array(
		// POST
		array(
			'methods'  => WP_REST_Server::CREATABLE,
			'callback' => 'annotations_add_annotation_note'
		)
	) );

	register_rest_route( 'annotation-notes/v1', '/search/', array(
		// GET
		array(
			'methods'  => WP_REST_Server::READABLE,
			'callback' => 'annotations_get_all_notes'
		)
	) );
}

function annotations_get_all_notes( WP_REST_Request $request ) {

	$user_id = ! empty( $_COOKIE['uid'] ) ? $_COOKIE['uid'] : '';
	$page_id = ! empty( $_COOKIE['pid'] ) ? $_COOKIE['pid'] : '';

	if ( $user_id == '' || $page_id == '' ) {
		return new WP_REST_Response( [], 200 );
	}

	global $wpdb;
	$sql     = "SELECT uid, ranges, note, quote FROM {$wpdb->prefix}annotations_notes WHERE user_id = %d AND page_id = %d";
	$results = $wpdb->get_results( $wpdb->prepare( $sql, $user_id, $page_id ) );

	if ( ! empty( $results ) ) {

		$data = [ 'total' => count( $results ), 'rows' => [] ];
		foreach ( $results as $note ) {
			$data['rows'][] = [ 'id' => $note->uid, 'ranges' => unserialize( $note->ranges ), 'text' => $note->note, 'quote' => $note->quote ];
		}

		return new WP_REST_Response( $data, 200 );
	}

	return new WP_REST_Response( [], 200 );
}

function annotations_add_annotation_note( WP_REST_Request $request ) {

	$json = $request->get_json_params();

	$user_id = ! empty( $_COOKIE['uid'] ) ? $_COOKIE['uid'] : '';
	$page_id = ! empty( $_COOKIE['pid'] ) ? $_COOKIE['pid'] : '';

	if ( $user_id == '' || $page_id == '' ) {
		return new WP_REST_Response( [], 200 );
	}

	$note_id = $user_id . '-' . uniqid() . '-' . $page_id;
	$ranges  = serialize( $json['ranges'] );

	global $wpdb;
	$sql = "INSERT INTO {$wpdb->prefix}annotations_notes (uid, user_id, page_id, ranges, note, quote) VALUES (%s, %d, %d, %s, %s, %s)";
	$wpdb->query( $wpdb->prepare( $sql, $note_id, $user_id, $page_id, $ranges, $json['text'], $json['quote'] ) );

	if ( $wpdb->insert_id ) {
		$data = [ 'id' => $note_id, 'ranges' => $json['ranges'], 'text' => $json['text'], 'quote' => $json['quote'] ];

		return new WP_REST_Response( $data, 200 );
	}

	return new WP_Error( 'cant-delete', __( 'Can\'t create!', 'cpotheme' ), array( 'status' => 404 ) );
}

function annotations_delete_annotation_note( WP_REST_Request $request ) {
	$json = $request->get_json_params();

	global $wpdb;
	$sql    = "DELETE FROM {$wpdb->prefix}annotations_notes WHERE uid LIKE '%s'";
	$delete = $wpdb->query( $wpdb->prepare( $sql, $json['id'] ) );

	if ( $delete !== false && $delete !== 0 ) {
		$data = [];

		return new WP_REST_Response( $data, 204 );
	}

	return new WP_Error( 'cant-edit', __( 'Note can\'t be deleted', 'cpotheme' ), array( 'status' => 404 ) );
}

function annotations_edit_annotation_note( WP_REST_Request $request ) {
	$json = $request->get_json_params();

	global $wpdb;
	$sql    = "UPDATE {$wpdb->prefix}annotations_notes SET note = '%s' WHERE uid LIKE '%s'";
	$update = $wpdb->query( $wpdb->prepare( $sql, $json['text'], $json['id'] ) );
	if ( $update !== false && $update !== 0 ) {
		$data = [
			'id'     => $json['id'],
			'ranges' => $json['ranges'],
			'text'   => $json['text'],
			'quote'  => $json['quote']
		];

		return new WP_REST_Response( $data, 200 );
	}

	return new WP_Error( 'cant-edit', __( 'Note can\'t be edited', 'cpotheme' ), array( 'status' => 400 ) );
}

Note that there is custom database where I store the annotations. It is called {$wpdb->prefix}annotations_notes and has the following columns: id, uid, user_id, page_id, ranges, note, quote.

Hope this helps.

@ANONIMNIQ
Copy link
Author

Thank you! I use your code and create table annotations_notes with same prefix as my other tables in the database and add id, uid, user_id, page_id, ranges, note, quote columns to the table. Now I can't see any errors, but when I try to add annotation noting happened. The text is not highlighted and nothing is stored in database table.

@alordiel
Copy link

Check the function annotations_add_annotation_note. There is made a check for two cookies that represents the user_id and post_id. You need to set those in the code of the current page ( before get_header() function). Or you can try use another way to get the current post ID and user ID from within that function as you will need that info for the database (even if you don't need the user ID, you still need the page ID, as this is the main parameter by which you will assign the annotations.

@ANONIMNIQ
Copy link
Author

<script>
	function setCookie1() {
    global $wp_query;
    if ($wp_query->have_posts()) {
       $post_id = $wp_query->current_post;
       setcookie('pid', $post_id);
    }
    $wp_query->rewind_posts();
    return;
}
add_action( 'wp', 'setCookie1', 10);
function setCookie2() {
                    $user = wp_get_current_user(); 
                    if (!$user instanceof WP_User) 
                            return;

                    setcookie('uid', $user->ID);
            }

            add_action('wp', 'setCookie2', 10);
	
</script>

I try to set cookies in single post template file but still not working.

@alordiel
Copy link

This might be a mistake from the markdown but you don't set php code in <script> tags.
And hooking that functions to wp might be to early to get the post ID. I'm not sure when the wp hook is executed in the work flow. Still you should be able to see if those two cookies exists from the browser's developer tool. If the cookies are empty, then nothing will be recorded because of that code from the function:

if ( $user_id == '' || $page_id == '' ) {
	return new WP_REST_Response( [], 200 );
}

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants