add icons for Character groups
This commit is contained in:
commit
2d9a41a5fe
3461 changed files with 594457 additions and 0 deletions
371
www/analytics/plugins/Annotations/API.php
Executable file
371
www/analytics/plugins/Annotations/API.php
Executable file
|
|
@ -0,0 +1,371 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\Annotations;
|
||||
|
||||
use Exception;
|
||||
|
||||
use Piwik\Date;
|
||||
use Piwik\Period;
|
||||
use Piwik\Period\Range;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugins\CoreVisualizations\Visualizations\JqplotGraph\Evolution as EvolutionViz;
|
||||
|
||||
/**
|
||||
* @see plugins/Annotations/AnnotationList.php
|
||||
*/
|
||||
require_once PIWIK_INCLUDE_PATH . '/plugins/Annotations/AnnotationList.php';
|
||||
|
||||
/**
|
||||
* API for annotations plugin. Provides methods to create, modify, delete & query
|
||||
* annotations.
|
||||
*
|
||||
* @method static \Piwik\Plugins\Annotations\API getInstance()
|
||||
*/
|
||||
class API extends \Piwik\Plugin\API
|
||||
{
|
||||
/**
|
||||
* Create a new annotation for a site.
|
||||
*
|
||||
* @param string $idSite The site ID to add the annotation to.
|
||||
* @param string $date The date the annotation is attached to.
|
||||
* @param string $note The text of the annotation.
|
||||
* @param int $starred Either 0 or 1. Whether the annotation should be starred.
|
||||
* @return array Returns an array of two elements. The first element (indexed by
|
||||
* 'annotation') is the new annotation. The second element (indexed
|
||||
* by 'idNote' is the new note's ID).
|
||||
*/
|
||||
public function add($idSite, $date, $note, $starred = 0)
|
||||
{
|
||||
$this->checkSingleIdSite($idSite, $extraMessage = "Note: Cannot add one note to multiple sites.");
|
||||
$this->checkDateIsValid($date);
|
||||
$this->checkUserCanAddNotesFor($idSite);
|
||||
|
||||
// add, save & return a new annotation
|
||||
$annotations = new AnnotationList($idSite);
|
||||
|
||||
$newAnnotation = $annotations->add($idSite, $date, $note, $starred);
|
||||
$annotations->save($idSite);
|
||||
|
||||
return $newAnnotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies an annotation for a site and returns the modified annotation
|
||||
* and its ID.
|
||||
*
|
||||
* If the current user is not allowed to modify an annotation, an exception
|
||||
* will be thrown. A user can modify a note if:
|
||||
* - the user has admin access for the site, OR
|
||||
* - the user has view access, is not the anonymous user and is the user that
|
||||
* created the note
|
||||
*
|
||||
* @param string $idSite The site ID to add the annotation to.
|
||||
* @param string $idNote The ID of the note.
|
||||
* @param string|null $date The date the annotation is attached to. If null, the annotation's
|
||||
* date is not modified.
|
||||
* @param string|null $note The text of the annotation. If null, the annotation's text
|
||||
* is not modified.
|
||||
* @param string|null $starred Either 0 or 1. Whether the annotation should be starred.
|
||||
* If null, the annotation is not starred/un-starred.
|
||||
* @return array Returns an array of two elements. The first element (indexed by
|
||||
* 'annotation') is the new annotation. The second element (indexed
|
||||
* by 'idNote' is the new note's ID).
|
||||
*/
|
||||
public function save($idSite, $idNote, $date = null, $note = null, $starred = null)
|
||||
{
|
||||
$this->checkSingleIdSite($idSite, $extraMessage = "Note: Cannot modify more than one note at a time.");
|
||||
$this->checkDateIsValid($date, $canBeNull = true);
|
||||
|
||||
// get the annotations for the site
|
||||
$annotations = new AnnotationList($idSite);
|
||||
|
||||
// check permissions
|
||||
$this->checkUserCanModifyOrDelete($idSite, $annotations->get($idSite, $idNote));
|
||||
|
||||
// modify the annotation, and save the whole list
|
||||
$annotations->update($idSite, $idNote, $date, $note, $starred);
|
||||
$annotations->save($idSite);
|
||||
|
||||
return $annotations->get($idSite, $idNote);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an annotation from a site's list of annotations.
|
||||
*
|
||||
* If the current user is not allowed to delete the annotation, an exception
|
||||
* will be thrown. A user can delete a note if:
|
||||
* - the user has admin access for the site, OR
|
||||
* - the user has view access, is not the anonymous user and is the user that
|
||||
* created the note
|
||||
*
|
||||
* @param string $idSite The site ID to add the annotation to.
|
||||
* @param string $idNote The ID of the note to delete.
|
||||
*/
|
||||
public function delete($idSite, $idNote)
|
||||
{
|
||||
$this->checkSingleIdSite($idSite, $extraMessage = "Note: Cannot delete annotations from multiple sites.");
|
||||
|
||||
$annotations = new AnnotationList($idSite);
|
||||
|
||||
// check permissions
|
||||
$this->checkUserCanModifyOrDelete($idSite, $annotations->get($idSite, $idNote));
|
||||
|
||||
// remove the note & save the list
|
||||
$annotations->remove($idSite, $idNote);
|
||||
$annotations->save($idSite);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all annotations for a single site. Only super users can use this method.
|
||||
*
|
||||
* @param string $idSite The ID of the site to remove annotations for.
|
||||
*/
|
||||
public function deleteAll($idSite)
|
||||
{
|
||||
$this->checkSingleIdSite($idSite, $extraMessage = "Note: Cannot delete annotations from multiple sites.");
|
||||
Piwik::checkUserHasSuperUserAccess();
|
||||
|
||||
$annotations = new AnnotationList($idSite);
|
||||
|
||||
// remove the notes & save the list
|
||||
$annotations->removeAll($idSite);
|
||||
$annotations->save($idSite);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a single note for one site.
|
||||
*
|
||||
* @param string $idSite The site ID to add the annotation to.
|
||||
* @param string $idNote The ID of the note to get.
|
||||
* @return array The annotation. It will contain the following properties:
|
||||
* - date: The date the annotation was recorded for.
|
||||
* - note: The note text.
|
||||
* - starred: Whether the note is starred or not.
|
||||
* - user: The user that created the note.
|
||||
* - canEditOrDelete: Whether the user that called this method can edit or
|
||||
* delete the annotation returned.
|
||||
*/
|
||||
public function get($idSite, $idNote)
|
||||
{
|
||||
$this->checkSingleIdSite($idSite, $extraMessage = "Note: Specify only one site ID when getting ONE note.");
|
||||
Piwik::checkUserHasViewAccess($idSite);
|
||||
|
||||
// get single annotation
|
||||
$annotations = new AnnotationList($idSite);
|
||||
return $annotations->get($idSite, $idNote);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns every annotation for a specific site within a specific date range.
|
||||
* The date range is specified by a date, the period type (day/week/month/year)
|
||||
* and an optional number of N periods in the past to include.
|
||||
*
|
||||
* @param string $idSite The site ID to add the annotation to. Can be one ID or
|
||||
* a list of site IDs.
|
||||
* @param bool|string $date The date of the period.
|
||||
* @param string $period The period type.
|
||||
* @param bool|int $lastN Whether to include the last N number of periods in the
|
||||
* date range or not.
|
||||
* @return array An array that indexes arrays of annotations by site ID. ie,
|
||||
* array(
|
||||
* 5 => array(
|
||||
* array(...), // annotation #1
|
||||
* array(...), // annotation #2
|
||||
* ),
|
||||
* 8 => array(...)
|
||||
* )
|
||||
*/
|
||||
public function getAll($idSite, $date = false, $period = 'day', $lastN = false)
|
||||
{
|
||||
Piwik::checkUserHasViewAccess($idSite);
|
||||
|
||||
$annotations = new AnnotationList($idSite);
|
||||
|
||||
// if date/period are supplied, determine start/end date for search
|
||||
list($startDate, $endDate) = self::getDateRangeForPeriod($date, $period, $lastN);
|
||||
|
||||
return $annotations->search($startDate, $endDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the count of annotations for a list of periods, including the count of
|
||||
* starred annotations.
|
||||
*
|
||||
* @param string $idSite The site ID to add the annotation to.
|
||||
* @param string|bool $date The date of the period.
|
||||
* @param string $period The period type.
|
||||
* @param int|bool $lastN Whether to get counts for the last N number of periods or not.
|
||||
* @param bool $getAnnotationText
|
||||
* @return array An array mapping site IDs to arrays holding dates & the count of
|
||||
* annotations made for those dates. eg,
|
||||
* array(
|
||||
* 5 => array(
|
||||
* array('2012-01-02', array('count' => 4, 'starred' => 2)),
|
||||
* array('2012-01-03', array('count' => 0, 'starred' => 0)),
|
||||
* array('2012-01-04', array('count' => 2, 'starred' => 0)),
|
||||
* ),
|
||||
* 6 => array(
|
||||
* array('2012-01-02', array('count' => 1, 'starred' => 0)),
|
||||
* array('2012-01-03', array('count' => 4, 'starred' => 3)),
|
||||
* array('2012-01-04', array('count' => 2, 'starred' => 0)),
|
||||
* ),
|
||||
* ...
|
||||
* )
|
||||
*/
|
||||
public function getAnnotationCountForDates($idSite, $date, $period, $lastN = false, $getAnnotationText = false)
|
||||
{
|
||||
Piwik::checkUserHasViewAccess($idSite);
|
||||
|
||||
// get start & end date for request. lastN is ignored if $period == 'range'
|
||||
list($startDate, $endDate) = self::getDateRangeForPeriod($date, $period, $lastN);
|
||||
if ($period == 'range') {
|
||||
$period = 'day';
|
||||
}
|
||||
|
||||
// create list of dates
|
||||
$dates = array();
|
||||
for (; $startDate->getTimestamp() <= $endDate->getTimestamp(); $startDate = $startDate->addPeriod(1, $period)) {
|
||||
$dates[] = $startDate;
|
||||
}
|
||||
// we add one for the end of the last period (used in for loop below to bound annotation dates)
|
||||
$dates[] = $startDate;
|
||||
|
||||
// get annotations for the site
|
||||
$annotations = new AnnotationList($idSite);
|
||||
|
||||
// create result w/ 0-counts
|
||||
$result = array();
|
||||
for ($i = 0; $i != count($dates) - 1; ++$i) {
|
||||
$date = $dates[$i];
|
||||
$nextDate = $dates[$i + 1];
|
||||
$strDate = $date->toString();
|
||||
|
||||
foreach ($annotations->getIdSites() as $idSite) {
|
||||
$result[$idSite][$strDate] = $annotations->count($idSite, $date, $nextDate);
|
||||
|
||||
// if only one annotation, return the one annotation's text w/ the counts
|
||||
if ($getAnnotationText
|
||||
&& $result[$idSite][$strDate]['count'] == 1
|
||||
) {
|
||||
$annotationsForSite = $annotations->search(
|
||||
$date, Date::factory($nextDate->getTimestamp() - 1), $idSite);
|
||||
$annotation = reset($annotationsForSite[$idSite]);
|
||||
|
||||
$result[$idSite][$strDate]['note'] = $annotation['note'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// convert associative array into array of pairs (so it can be traversed by index)
|
||||
$pairResult = array();
|
||||
foreach ($result as $idSite => $counts) {
|
||||
foreach ($counts as $date => $count) {
|
||||
$pairResult[$idSite][] = array($date, $count);
|
||||
}
|
||||
}
|
||||
return $pairResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws if the current user is not allowed to modify or delete an annotation.
|
||||
*
|
||||
* @param int $idSite The site ID the annotation belongs to.
|
||||
* @param array $annotation The annotation.
|
||||
* @throws Exception if the current user is not allowed to modify/delete $annotation.
|
||||
*/
|
||||
private function checkUserCanModifyOrDelete($idSite, $annotation)
|
||||
{
|
||||
if (!$annotation['canEditOrDelete']) {
|
||||
throw new Exception(Piwik::translate('Annotations_YouCannotModifyThisNote'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws if the current user is not allowed to create annotations for a site.
|
||||
*
|
||||
* @param int $idSite The site ID.
|
||||
* @throws Exception if the current user is anonymous or does not have view access
|
||||
* for site w/ id=$idSite.
|
||||
*/
|
||||
private static function checkUserCanAddNotesFor($idSite)
|
||||
{
|
||||
if (!AnnotationList::canUserAddNotesFor($idSite)) {
|
||||
throw new Exception("The current user is not allowed to add notes for site #$idSite.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns start & end dates for the range described by a period and optional lastN
|
||||
* argument.
|
||||
*
|
||||
* @param string|bool $date The start date of the period (or the date range of a range
|
||||
* period).
|
||||
* @param string $period The period type ('day', 'week', 'month', 'year' or 'range').
|
||||
* @param bool|int $lastN Whether to include the last N periods in the range or not.
|
||||
* Ignored if period == range.
|
||||
*
|
||||
* @return Date[] array of Date objects or array(false, false)
|
||||
* @ignore
|
||||
*/
|
||||
public static function getDateRangeForPeriod($date, $period, $lastN = false)
|
||||
{
|
||||
if ($date === false) {
|
||||
return array(false, false);
|
||||
}
|
||||
|
||||
// if the range is just a normal period (or the period is a range in which case lastN is ignored)
|
||||
if ($lastN === false
|
||||
|| $period == 'range'
|
||||
) {
|
||||
if ($period == 'range') {
|
||||
$oPeriod = new Range('day', $date);
|
||||
} else {
|
||||
$oPeriod = Period::factory($period, Date::factory($date));
|
||||
}
|
||||
|
||||
$startDate = $oPeriod->getDateStart();
|
||||
$endDate = $oPeriod->getDateEnd();
|
||||
} else // if the range includes the last N periods
|
||||
{
|
||||
list($date, $lastN) = EvolutionViz::getDateRangeAndLastN($period, $date, $lastN);
|
||||
list($startDate, $endDate) = explode(',', $date);
|
||||
|
||||
$startDate = Date::factory($startDate);
|
||||
$endDate = Date::factory($endDate);
|
||||
}
|
||||
return array($startDate, $endDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function, makes sure idSite string has only one site ID and throws if
|
||||
* otherwise.
|
||||
*/
|
||||
private function checkSingleIdSite($idSite, $extraMessage)
|
||||
{
|
||||
// can only add a note to one site
|
||||
if (!is_numeric($idSite)) {
|
||||
throw new Exception("Invalid idSite: '$idSite'. $extraMessage");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function, makes sure date string is valid date, and throws if
|
||||
* otherwise.
|
||||
*/
|
||||
private function checkDateIsValid($date, $canBeNull = false)
|
||||
{
|
||||
if ($date === null
|
||||
&& $canBeNull
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
Date::factory($date);
|
||||
}
|
||||
}
|
||||
455
www/analytics/plugins/Annotations/AnnotationList.php
Executable file
455
www/analytics/plugins/Annotations/AnnotationList.php
Executable file
|
|
@ -0,0 +1,455 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\Annotations;
|
||||
|
||||
use Exception;
|
||||
use Piwik\Date;
|
||||
use Piwik\Option;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Site;
|
||||
|
||||
/**
|
||||
* This class can be used to query & modify annotations for multiple sites
|
||||
* at once.
|
||||
*
|
||||
* Example use:
|
||||
* $annotations = new AnnotationList($idSites = "1,2,5");
|
||||
* $annotation = $annotations->get($idSite = 1, $idNote = 4);
|
||||
* // do stuff w/ annotation
|
||||
* $annotations->update($idSite = 2, $idNote = 4, $note = "This is the new text.");
|
||||
* $annotations->save($idSite);
|
||||
*
|
||||
* Note: There is a concurrency issue w/ this code. If two users try to save
|
||||
* an annotation for the same site, it's possible one of their changes will
|
||||
* never get made (as it will be overwritten by the other's).
|
||||
*
|
||||
*/
|
||||
class AnnotationList
|
||||
{
|
||||
const ANNOTATION_COLLECTION_OPTION_SUFFIX = '_annotations';
|
||||
|
||||
/**
|
||||
* List of site IDs this instance holds annotations for.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $idSites;
|
||||
|
||||
/**
|
||||
* Array that associates lists of annotations with site IDs.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $annotations;
|
||||
|
||||
/**
|
||||
* Constructor. Loads annotations from the database.
|
||||
*
|
||||
* @param string|int $idSites The list of site IDs to load annotations for.
|
||||
*/
|
||||
public function __construct($idSites)
|
||||
{
|
||||
$this->idSites = Site::getIdSitesFromIdSitesString($idSites);
|
||||
$this->annotations = $this->getAnnotationsForSite();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of site IDs this list contains annotations for.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getIdSites()
|
||||
{
|
||||
return $this->idSites;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new annotation for a site. This method does not perist the result.
|
||||
* To save the new annotation in the database, call $this->save.
|
||||
*
|
||||
* @param int $idSite The ID of the site to add an annotation to.
|
||||
* @param string $date The date the annotation is in reference to.
|
||||
* @param string $note The text of the new annotation.
|
||||
* @param int $starred Either 1 or 0. If 1, the new annotation has been starred,
|
||||
* otherwise it will start out unstarred.
|
||||
* @return array The added annotation.
|
||||
* @throws Exception if $idSite is not an ID that was supplied upon construction.
|
||||
*/
|
||||
public function add($idSite, $date, $note, $starred = 0)
|
||||
{
|
||||
$this->checkIdSiteIsLoaded($idSite);
|
||||
|
||||
$this->annotations[$idSite][] = self::makeAnnotation($date, $note, $starred);
|
||||
|
||||
// get the id of the new annotation
|
||||
end($this->annotations[$idSite]);
|
||||
$newNoteId = key($this->annotations[$idSite]);
|
||||
|
||||
return $this->get($idSite, $newNoteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists the annotations list for a site, overwriting whatever exists.
|
||||
*
|
||||
* @param int $idSite The ID of the site to save annotations for.
|
||||
* @throws Exception if $idSite is not an ID that was supplied upon construction.
|
||||
*/
|
||||
public function save($idSite)
|
||||
{
|
||||
$this->checkIdSiteIsLoaded($idSite);
|
||||
|
||||
$optionName = self::getAnnotationCollectionOptionName($idSite);
|
||||
Option::set($optionName, serialize($this->annotations[$idSite]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies an annotation in this instance's collection of annotations.
|
||||
*
|
||||
* Note: This method does not perist the change in the DB. The save method must
|
||||
* be called for that.
|
||||
*
|
||||
* @param int $idSite The ID of the site whose annotation will be updated.
|
||||
* @param int $idNote The ID of the note.
|
||||
* @param string|null $date The new date of the annotation, eg '2012-01-01'. If
|
||||
* null, no change is made.
|
||||
* @param string|null $note The new text of the annotation. If null, no change
|
||||
* is made.
|
||||
* @param int|null $starred Either 1 or 0, whether the annotation should be
|
||||
* starred or not. If null, no change is made.
|
||||
* @throws Exception if $idSite is not an ID that was supplied upon construction.
|
||||
* @throws Exception if $idNote does not refer to valid note for the site.
|
||||
*/
|
||||
public function update($idSite, $idNote, $date = null, $note = null, $starred = null)
|
||||
{
|
||||
$this->checkIdSiteIsLoaded($idSite);
|
||||
$this->checkNoteExists($idSite, $idNote);
|
||||
|
||||
$annotation =& $this->annotations[$idSite][$idNote];
|
||||
if ($date !== null) {
|
||||
$annotation['date'] = $date;
|
||||
}
|
||||
if ($note !== null) {
|
||||
$annotation['note'] = $note;
|
||||
}
|
||||
if ($starred !== null) {
|
||||
$annotation['starred'] = $starred;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a note from a site's collection of annotations.
|
||||
*
|
||||
* Note: This method does not perist the change in the DB. The save method must
|
||||
* be called for that.
|
||||
*
|
||||
* @param int $idSite The ID of the site whose annotation will be updated.
|
||||
* @param int $idNote The ID of the note.
|
||||
* @throws Exception if $idSite is not an ID that was supplied upon construction.
|
||||
* @throws Exception if $idNote does not refer to valid note for the site.
|
||||
*/
|
||||
public function remove($idSite, $idNote)
|
||||
{
|
||||
$this->checkIdSiteIsLoaded($idSite);
|
||||
$this->checkNoteExists($idSite, $idNote);
|
||||
|
||||
unset($this->annotations[$idSite][$idNote]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all notes for a single site.
|
||||
*
|
||||
* Note: This method does not perist the change in the DB. The save method must
|
||||
* be called for that.
|
||||
*
|
||||
* @param int $idSite The ID of the site to get an annotation for.
|
||||
* @throws Exception if $idSite is not an ID that was supplied upon construction.
|
||||
*/
|
||||
public function removeAll($idSite)
|
||||
{
|
||||
$this->checkIdSiteIsLoaded($idSite);
|
||||
|
||||
$this->annotations[$idSite] = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an annotation by ID.
|
||||
*
|
||||
* This function returns an array with the following elements:
|
||||
* - idNote: The ID of the annotation.
|
||||
* - date: The date of the annotation.
|
||||
* - note: The text of the annotation.
|
||||
* - starred: 1 or 0, whether the annotation is stared;
|
||||
* - user: (unless current user is anonymous) The user that created the annotation.
|
||||
* - canEditOrDelete: True if the user can edit/delete the annotation.
|
||||
*
|
||||
* @param int $idSite The ID of the site to get an annotation for.
|
||||
* @param int $idNote The ID of the note to get.
|
||||
* @throws Exception if $idSite is not an ID that was supplied upon construction.
|
||||
* @throws Exception if $idNote does not refer to valid note for the site.
|
||||
*/
|
||||
public function get($idSite, $idNote)
|
||||
{
|
||||
$this->checkIdSiteIsLoaded($idSite);
|
||||
$this->checkNoteExists($idSite, $idNote);
|
||||
|
||||
$annotation = $this->annotations[$idSite][$idNote];
|
||||
$this->augmentAnnotationData($idSite, $idNote, $annotation);
|
||||
return $annotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all annotations within a specific date range. The result is
|
||||
* an array that maps site IDs with arrays of annotations within the range.
|
||||
*
|
||||
* Note: The date range is inclusive.
|
||||
*
|
||||
* @see self::get for info on what attributes stored within annotations.
|
||||
*
|
||||
* @param Date|bool $startDate The start of the date range.
|
||||
* @param Date|bool $endDate The end of the date range.
|
||||
* @param array|bool|int|string $idSite IDs of the sites whose annotations to
|
||||
* search through.
|
||||
* @return array Array mapping site IDs with arrays of annotations, eg:
|
||||
* array(
|
||||
* '5' => array(
|
||||
* array(...), // annotation
|
||||
* array(...), // annotation
|
||||
* ...
|
||||
* ),
|
||||
* '6' => array(
|
||||
* array(...), // annotation
|
||||
* array(...), // annotation
|
||||
* ...
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function search($startDate, $endDate, $idSite = false)
|
||||
{
|
||||
if ($idSite) {
|
||||
$idSites = Site::getIdSitesFromIdSitesString($idSite);
|
||||
} else {
|
||||
$idSites = array_keys($this->annotations);
|
||||
}
|
||||
|
||||
// collect annotations that are within the right date range & belong to the right
|
||||
// site
|
||||
$result = array();
|
||||
foreach ($idSites as $idSite) {
|
||||
if (!isset($this->annotations[$idSite])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($this->annotations[$idSite] as $idNote => $annotation) {
|
||||
if ($startDate !== false) {
|
||||
$annotationDate = Date::factory($annotation['date']);
|
||||
if ($annotationDate->getTimestamp() < $startDate->getTimestamp()
|
||||
|| $annotationDate->getTimestamp() > $endDate->getTimestamp()
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$this->augmentAnnotationData($idSite, $idNote, $annotation);
|
||||
$result[$idSite][] = $annotation;
|
||||
}
|
||||
|
||||
// sort by annotation date
|
||||
if (!empty($result[$idSite])) {
|
||||
uasort($result[$idSite], array($this, 'compareAnnotationDate'));
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts annotations & starred annotations within a date range and returns
|
||||
* the counts. The date range includes the start date, but not the end date.
|
||||
*
|
||||
* @param int $idSite The ID of the site to count annotations for.
|
||||
* @param string|false $startDate The start date of the range or false if no
|
||||
* range check is desired.
|
||||
* @param string|false $endDate The end date of the range or false if no
|
||||
* range check is desired.
|
||||
* @return array eg, array('count' => 5, 'starred' => 2)
|
||||
*/
|
||||
public function count($idSite, $startDate, $endDate)
|
||||
{
|
||||
$this->checkIdSiteIsLoaded($idSite);
|
||||
|
||||
// search includes end date, and count should not, so subtract one from the timestamp
|
||||
$annotations = $this->search($startDate, Date::factory($endDate->getTimestamp() - 1));
|
||||
|
||||
// count the annotations
|
||||
$count = $starred = 0;
|
||||
if (!empty($annotations[$idSite])) {
|
||||
$count = count($annotations[$idSite]);
|
||||
foreach ($annotations[$idSite] as $annotation) {
|
||||
if ($annotation['starred']) {
|
||||
++$starred;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array('count' => $count, 'starred' => $starred);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function. Creates a new annotation.
|
||||
*
|
||||
* @param string $date
|
||||
* @param string $note
|
||||
* @param int $starred
|
||||
* @return array
|
||||
*/
|
||||
private function makeAnnotation($date, $note, $starred = 0)
|
||||
{
|
||||
return array('date' => $date,
|
||||
'note' => $note,
|
||||
'starred' => (int)$starred,
|
||||
'user' => Piwik::getCurrentUserLogin());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves annotations from the database for the sites supplied to the
|
||||
* constructor.
|
||||
*
|
||||
* @return array Lists of annotations mapped by site ID.
|
||||
*/
|
||||
private function getAnnotationsForSite()
|
||||
{
|
||||
$result = array();
|
||||
foreach ($this->idSites as $id) {
|
||||
$optionName = self::getAnnotationCollectionOptionName($id);
|
||||
$serialized = Option::get($optionName);
|
||||
|
||||
if ($serialized !== false) {
|
||||
$result[$id] = @unserialize($serialized);
|
||||
if(empty($result[$id])) {
|
||||
// in case unserialize failed
|
||||
$result[$id] = array();
|
||||
}
|
||||
} else {
|
||||
$result[$id] = array();
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function that checks if a site ID was supplied and if not,
|
||||
* throws an exception.
|
||||
*
|
||||
* We can only modify/read annotations for sites that we've actually
|
||||
* loaded the annotations for.
|
||||
*
|
||||
* @param int $idSite
|
||||
* @throws Exception
|
||||
*/
|
||||
private function checkIdSiteIsLoaded($idSite)
|
||||
{
|
||||
if (!in_array($idSite, $this->idSites)) {
|
||||
throw new Exception("This AnnotationList was not initialized with idSite '$idSite'.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function that checks if a note exists for a site, and if not,
|
||||
* throws an exception.
|
||||
*
|
||||
* @param int $idSite
|
||||
* @param int $idNote
|
||||
* @throws Exception
|
||||
*/
|
||||
private function checkNoteExists($idSite, $idNote)
|
||||
{
|
||||
if (empty($this->annotations[$idSite][$idNote])) {
|
||||
throw new Exception("There is no note with id '$idNote' for site with id '$idSite'.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current user can modify or delete a specific annotation.
|
||||
*
|
||||
* A user can modify/delete a note if the user has admin access for the site OR
|
||||
* the user has view access, is not the anonymous user and is the user that
|
||||
* created the note in question.
|
||||
*
|
||||
* @param int $idSite The site ID the annotation belongs to.
|
||||
* @param array $annotation The annotation.
|
||||
* @return bool
|
||||
*/
|
||||
public static function canUserModifyOrDelete($idSite, $annotation)
|
||||
{
|
||||
// user can save if user is admin or if has view access, is not anonymous & is user who wrote note
|
||||
$canEdit = Piwik::isUserHasAdminAccess($idSite)
|
||||
|| (!Piwik::isUserIsAnonymous()
|
||||
&& Piwik::getCurrentUserLogin() == $annotation['user']);
|
||||
return $canEdit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds extra data to an annotation, including the annotation's ID and whether
|
||||
* the current user can edit or delete it.
|
||||
*
|
||||
* Also, if the current user is anonymous, the user attribute is removed.
|
||||
*
|
||||
* @param int $idSite
|
||||
* @param int $idNote
|
||||
* @param array $annotation
|
||||
*/
|
||||
private function augmentAnnotationData($idSite, $idNote, &$annotation)
|
||||
{
|
||||
$annotation['idNote'] = $idNote;
|
||||
$annotation['canEditOrDelete'] = self::canUserModifyOrDelete($idSite, $annotation);
|
||||
|
||||
// we don't supply user info if the current user is anonymous
|
||||
if (Piwik::isUserIsAnonymous()) {
|
||||
unset($annotation['user']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function that compares two annotations.
|
||||
*
|
||||
* @param array $lhs An annotation.
|
||||
* @param array $rhs An annotation.
|
||||
* @return int -1, 0 or 1
|
||||
*/
|
||||
public function compareAnnotationDate($lhs, $rhs)
|
||||
{
|
||||
if ($lhs['date'] == $rhs['date']) {
|
||||
return $lhs['idNote'] <= $rhs['idNote'] ? -1 : 1;
|
||||
}
|
||||
|
||||
return $lhs['date'] < $rhs['date'] ? -1 : 1; // string comparison works because date format should be YYYY-MM-DD
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current user can add notes for a specific site.
|
||||
*
|
||||
* @param int $idSite The site to add notes to.
|
||||
* @return bool
|
||||
*/
|
||||
public static function canUserAddNotesFor($idSite)
|
||||
{
|
||||
return Piwik::isUserHasViewAccess($idSite)
|
||||
&& !Piwik::isUserIsAnonymous($idSite);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the option name used to store annotations for a site.
|
||||
*
|
||||
* @param int $idSite The site ID.
|
||||
* @return string
|
||||
*/
|
||||
public static function getAnnotationCollectionOptionName($idSite)
|
||||
{
|
||||
return $idSite . self::ANNOTATION_COLLECTION_OPTION_SUFFIX;
|
||||
}
|
||||
}
|
||||
44
www/analytics/plugins/Annotations/Annotations.php
Executable file
44
www/analytics/plugins/Annotations/Annotations.php
Executable file
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\Annotations;
|
||||
|
||||
/**
|
||||
* Annotations plugins. Provides the ability to attach text notes to
|
||||
* dates for each sites. Notes can be viewed, modified, deleted or starred.
|
||||
*
|
||||
*/
|
||||
class Annotations extends \Piwik\Plugin
|
||||
{
|
||||
/**
|
||||
* @see Piwik\Plugin::getListHooksRegistered
|
||||
*/
|
||||
public function getListHooksRegistered()
|
||||
{
|
||||
return array(
|
||||
'AssetManager.getStylesheetFiles' => 'getStylesheetFiles',
|
||||
'AssetManager.getJavaScriptFiles' => 'getJsFiles',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds css files for this plugin to the list in the event notification.
|
||||
*/
|
||||
public function getStylesheetFiles(&$stylesheets)
|
||||
{
|
||||
$stylesheets[] = "plugins/Annotations/stylesheets/annotations.less";
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds js files for this plugin to the list in the event notification.
|
||||
*/
|
||||
public function getJsFiles(&$jsFiles)
|
||||
{
|
||||
$jsFiles[] = "plugins/Annotations/javascripts/annotations.js";
|
||||
}
|
||||
}
|
||||
215
www/analytics/plugins/Annotations/Controller.php
Executable file
215
www/analytics/plugins/Annotations/Controller.php
Executable file
|
|
@ -0,0 +1,215 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\Annotations;
|
||||
|
||||
use Piwik\API\Request;
|
||||
use Piwik\Common;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\View;
|
||||
|
||||
/**
|
||||
* Controller for the Annotations plugin.
|
||||
*
|
||||
*/
|
||||
class Controller extends \Piwik\Plugin\Controller
|
||||
{
|
||||
/**
|
||||
* Controller action that returns HTML displaying annotations for a site and
|
||||
* specific date range.
|
||||
*
|
||||
* Query Param Input:
|
||||
* - idSite: The ID of the site to get annotations for. Only one allowed.
|
||||
* - date: The date to get annotations for. If lastN is not supplied, this is the start date,
|
||||
* otherwise the start date in the last period.
|
||||
* - period: The period type.
|
||||
* - lastN: If supplied, the last N # of periods will be included w/ the range specified
|
||||
* by date + period.
|
||||
*
|
||||
* Output:
|
||||
* - HTML displaying annotations for a specific range.
|
||||
*
|
||||
* @param bool $fetch True if the annotation manager should be returned as a string,
|
||||
* false if it should be echo-ed.
|
||||
* @param bool|string $date Override for 'date' query parameter.
|
||||
* @param bool|string $period Override for 'period' query parameter.
|
||||
* @param bool|string $lastN Override for 'lastN' query parameter.
|
||||
* @return string|void
|
||||
*/
|
||||
public function getAnnotationManager($fetch = false, $date = false, $period = false, $lastN = false)
|
||||
{
|
||||
$idSite = Common::getRequestVar('idSite');
|
||||
|
||||
if ($date === false) {
|
||||
$date = Common::getRequestVar('date', false);
|
||||
}
|
||||
|
||||
if ($period === false) {
|
||||
$period = Common::getRequestVar('period', 'day');
|
||||
}
|
||||
|
||||
if ($lastN === false) {
|
||||
$lastN = Common::getRequestVar('lastN', false);
|
||||
}
|
||||
|
||||
// create & render the view
|
||||
$view = new View('@Annotations/getAnnotationManager');
|
||||
|
||||
$allAnnotations = Request::processRequest(
|
||||
'Annotations.getAll', array('date' => $date, 'period' => $period, 'lastN' => $lastN));
|
||||
$view->annotations = empty($allAnnotations[$idSite]) ? array() : $allAnnotations[$idSite];
|
||||
|
||||
$view->period = $period;
|
||||
$view->lastN = $lastN;
|
||||
|
||||
list($startDate, $endDate) = API::getDateRangeForPeriod($date, $period, $lastN);
|
||||
$view->startDate = $startDate->toString();
|
||||
$view->endDate = $endDate->toString();
|
||||
|
||||
$dateFormat = Piwik::translate('CoreHome_ShortDateFormatWithYear');
|
||||
$view->startDatePretty = $startDate->getLocalized($dateFormat);
|
||||
$view->endDatePretty = $endDate->getLocalized($dateFormat);
|
||||
|
||||
$view->canUserAddNotes = AnnotationList::canUserAddNotesFor($idSite);
|
||||
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Controller action that modifies an annotation and returns HTML displaying
|
||||
* the modified annotation.
|
||||
*
|
||||
* Query Param Input:
|
||||
* - idSite: The ID of the site the annotation belongs to. Only one ID is allowed.
|
||||
* - idNote: The ID of the annotation.
|
||||
* - date: The new date value for the annotation. (optional)
|
||||
* - note: The new text for the annotation. (optional)
|
||||
* - starred: Either 1 or 0. Whether the note should be starred or not. (optional)
|
||||
*
|
||||
* Output:
|
||||
* - HTML displaying modified annotation.
|
||||
*
|
||||
* If an optional query param is not supplied, that part of the annotation is
|
||||
* not modified.
|
||||
*/
|
||||
public function saveAnnotation()
|
||||
{
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$this->checkTokenInUrl();
|
||||
|
||||
$view = new View('@Annotations/saveAnnotation');
|
||||
|
||||
// NOTE: permissions checked in API method
|
||||
// save the annotation
|
||||
$view->annotation = Request::processRequest("Annotations.save");
|
||||
|
||||
return $view->render();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Controller action that adds a new annotation for a site and returns new
|
||||
* annotation manager HTML for the site and date range.
|
||||
*
|
||||
* Query Param Input:
|
||||
* - idSite: The ID of the site to add an annotation to.
|
||||
* - date: The date for the new annotation.
|
||||
* - note: The text of the annotation.
|
||||
* - starred: Either 1 or 0, whether the annotation should be starred or not.
|
||||
* Defaults to 0.
|
||||
* - managerDate: The date for the annotation manager. If a range is given, the start
|
||||
* date is used for the new annotation.
|
||||
* - managerPeriod: For rendering the annotation manager. @see self::getAnnotationManager
|
||||
* for more info.
|
||||
* - lastN: For rendering the annotation manager. @see self::getAnnotationManager
|
||||
* for more info.
|
||||
* Output:
|
||||
* - @see self::getAnnotationManager
|
||||
*/
|
||||
public function addAnnotation()
|
||||
{
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$this->checkTokenInUrl();
|
||||
|
||||
// the date used is for the annotation manager HTML that gets echo'd. we
|
||||
// use this date for the new annotation, unless it is a date range, in
|
||||
// which case we use the first date of the range.
|
||||
$date = Common::getRequestVar('date');
|
||||
if (strpos($date, ',') !== false) {
|
||||
$date = reset(explode(',', $date));
|
||||
}
|
||||
|
||||
// add the annotation. NOTE: permissions checked in API method
|
||||
Request::processRequest("Annotations.add", array('date' => $date));
|
||||
|
||||
$managerDate = Common::getRequestVar('managerDate', false);
|
||||
$managerPeriod = Common::getRequestVar('managerPeriod', false);
|
||||
return $this->getAnnotationManager($fetch = true, $managerDate, $managerPeriod);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Controller action that deletes an annotation and returns new annotation
|
||||
* manager HTML for the site & date range.
|
||||
*
|
||||
* Query Param Input:
|
||||
* - idSite: The ID of the site this annotation belongs to.
|
||||
* - idNote: The ID of the annotation to delete.
|
||||
* - date: For rendering the annotation manager. @see self::getAnnotationManager
|
||||
* for more info.
|
||||
* - period: For rendering the annotation manager. @see self::getAnnotationManager
|
||||
* for more info.
|
||||
* - lastN: For rendering the annotation manager. @see self::getAnnotationManager
|
||||
* for more info.
|
||||
*
|
||||
* Output:
|
||||
* - @see self::getAnnotationManager
|
||||
*/
|
||||
public function deleteAnnotation()
|
||||
{
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$this->checkTokenInUrl();
|
||||
|
||||
// delete annotation. NOTE: permissions checked in API method
|
||||
Request::processRequest("Annotations.delete");
|
||||
|
||||
return $this->getAnnotationManager($fetch = true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Controller action that echo's HTML that displays marker icons for an
|
||||
* evolution graph's x-axis. The marker icons still need to be positioned
|
||||
* by the JavaScript.
|
||||
*
|
||||
* Query Param Input:
|
||||
* - idSite: The ID of the site this annotation belongs to. Only one is allowed.
|
||||
* - date: The date to check for annotations. If lastN is not supplied, this is
|
||||
* the start of the date range used to check for annotations. If supplied,
|
||||
* this is the start of the last period in the date range.
|
||||
* - period: The period type.
|
||||
* - lastN: If supplied, the last N # of periods are included in the date range
|
||||
* used to check for annotations.
|
||||
*
|
||||
* Output:
|
||||
* - HTML that displays marker icons for an evolution graph based on the
|
||||
* number of annotations & starred annotations in the graph's date range.
|
||||
*/
|
||||
public function getEvolutionIcons()
|
||||
{
|
||||
// get annotation the count
|
||||
$annotationCounts = Request::processRequest(
|
||||
"Annotations.getAnnotationCountForDates", array('getAnnotationText' => 1));
|
||||
|
||||
// create & render the view
|
||||
$view = new View('@Annotations/getEvolutionIcons');
|
||||
$view->annotationCounts = reset($annotationCounts); // only one idSite allowed for this action
|
||||
|
||||
return $view->render();
|
||||
}
|
||||
}
|
||||
599
www/analytics/plugins/Annotations/javascripts/annotations.js
Executable file
599
www/analytics/plugins/Annotations/javascripts/annotations.js
Executable file
|
|
@ -0,0 +1,599 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
(function ($, piwik) {
|
||||
|
||||
var annotationsApi = {
|
||||
|
||||
// calls Annotations.getAnnotationManager
|
||||
getAnnotationManager: function (idSite, date, period, lastN, callback) {
|
||||
var ajaxParams =
|
||||
{
|
||||
module: 'Annotations',
|
||||
action: 'getAnnotationManager',
|
||||
idSite: idSite,
|
||||
date: date,
|
||||
period: period
|
||||
};
|
||||
if (lastN) {
|
||||
ajaxParams.lastN = lastN;
|
||||
}
|
||||
|
||||
var ajaxRequest = new ajaxHelper();
|
||||
ajaxRequest.addParams(ajaxParams, 'get');
|
||||
ajaxRequest.setCallback(callback);
|
||||
ajaxRequest.setFormat('html');
|
||||
ajaxRequest.send(false);
|
||||
},
|
||||
|
||||
// calls Annotations.addAnnotation
|
||||
addAnnotation: function (idSite, managerDate, managerPeriod, date, note, callback) {
|
||||
var ajaxParams =
|
||||
{
|
||||
module: 'Annotations',
|
||||
action: 'addAnnotation',
|
||||
idSite: idSite,
|
||||
date: date,
|
||||
managerDate: managerDate,
|
||||
managerPeriod: managerPeriod,
|
||||
note: note
|
||||
};
|
||||
|
||||
var ajaxRequest = new ajaxHelper();
|
||||
ajaxRequest.addParams(ajaxParams, 'get');
|
||||
ajaxRequest.setCallback(callback);
|
||||
ajaxRequest.setFormat('html');
|
||||
ajaxRequest.send(false);
|
||||
},
|
||||
|
||||
// calls Annotations.saveAnnotation
|
||||
saveAnnotation: function (idSite, idNote, date, noteData, callback) {
|
||||
var ajaxParams =
|
||||
{
|
||||
module: 'Annotations',
|
||||
action: 'saveAnnotation',
|
||||
idSite: idSite,
|
||||
idNote: idNote,
|
||||
date: date
|
||||
};
|
||||
|
||||
for (var key in noteData) {
|
||||
ajaxParams[key] = noteData[key];
|
||||
}
|
||||
|
||||
var ajaxRequest = new ajaxHelper();
|
||||
ajaxRequest.addParams(ajaxParams, 'get');
|
||||
ajaxRequest.setCallback(callback);
|
||||
ajaxRequest.setFormat('html');
|
||||
ajaxRequest.send(false);
|
||||
},
|
||||
|
||||
// calls Annotations.deleteAnnotation
|
||||
deleteAnnotation: function (idSite, idNote, managerDate, managerPeriod, callback) {
|
||||
var ajaxParams =
|
||||
{
|
||||
module: 'Annotations',
|
||||
action: 'deleteAnnotation',
|
||||
idSite: idSite,
|
||||
idNote: idNote,
|
||||
date: managerDate,
|
||||
period: managerPeriod
|
||||
};
|
||||
|
||||
var ajaxRequest = new ajaxHelper();
|
||||
ajaxRequest.addParams(ajaxParams, 'get');
|
||||
ajaxRequest.setCallback(callback);
|
||||
ajaxRequest.setFormat('html');
|
||||
ajaxRequest.send(false);
|
||||
},
|
||||
|
||||
// calls Annotations.getEvolutionIcons
|
||||
getEvolutionIcons: function (idSite, date, period, lastN, callback) {
|
||||
var ajaxParams =
|
||||
{
|
||||
module: 'Annotations',
|
||||
action: 'getEvolutionIcons',
|
||||
idSite: idSite,
|
||||
date: date,
|
||||
period: period
|
||||
};
|
||||
if (lastN) {
|
||||
ajaxParams.lastN = lastN;
|
||||
}
|
||||
|
||||
var ajaxRequest = new ajaxHelper();
|
||||
ajaxRequest.addParams(ajaxParams, 'get');
|
||||
ajaxRequest.setFormat('html');
|
||||
ajaxRequest.setCallback(callback);
|
||||
ajaxRequest.send(false);
|
||||
}
|
||||
};
|
||||
|
||||
var today = new Date();
|
||||
|
||||
/**
|
||||
* Returns options to configure an annotation's datepicker shown in edit mode.
|
||||
*
|
||||
* @param {Element} annotation The annotation element.
|
||||
*/
|
||||
var getDatePickerOptions = function (annotation) {
|
||||
var annotationDateStr = annotation.attr('data-date'),
|
||||
parts = annotationDateStr.split('-'),
|
||||
annotationDate = new Date(parts[0], parts[1] - 1, parts[2]);
|
||||
|
||||
var result = piwik.getBaseDatePickerOptions(annotationDate);
|
||||
|
||||
// make sure days before site start & after today cannot be selected
|
||||
var piwikMinDate = result.minDate;
|
||||
result.beforeShowDay = function (date) {
|
||||
var valid = true;
|
||||
|
||||
// if date is after today or before date of site creation, it cannot be selected
|
||||
if (date > today
|
||||
|| date < piwikMinDate) {
|
||||
valid = false;
|
||||
}
|
||||
|
||||
return [valid, ''];
|
||||
};
|
||||
|
||||
// on select a date, change the text of the edit date link
|
||||
result.onSelect = function (dateText) {
|
||||
$('.annotation-period-edit>a', annotation).text(dateText);
|
||||
$('.datepicker', annotation).hide();
|
||||
};
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Switches the current mode of an annotation between the view/edit modes.
|
||||
*
|
||||
* @param {Element} inAnnotationElement An element within the annotation to toggle the mode of.
|
||||
* Should be two levels nested in the .annotation-value
|
||||
* element.
|
||||
* @return {Element} The .annotation-value element.
|
||||
*/
|
||||
var toggleAnnotationMode = function (inAnnotationElement) {
|
||||
var annotation = $(inAnnotationElement).closest('.annotation');
|
||||
$('.annotation-period,.annotation-period-edit,.delete-annotation,' +
|
||||
'.annotation-edit-mode,.annotation-view-mode', annotation).toggle();
|
||||
|
||||
return $(inAnnotationElement).find('.annotation-value');
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the datepicker for an annotation element.
|
||||
*
|
||||
* @param {Element} annotation The annotation element.
|
||||
*/
|
||||
var createDatePicker = function (annotation) {
|
||||
$('.datepicker', annotation).datepicker(getDatePickerOptions(annotation)).hide();
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates datepickers for every period edit in an annotation manager.
|
||||
*
|
||||
* @param {Element} manager The annotation manager element.
|
||||
*/
|
||||
var createDatePickers = function (manager) {
|
||||
$('.annotation-period-edit', manager).each(function () {
|
||||
createDatePicker($(this).parent().parent());
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Replaces the HTML of an annotation manager element, and resets date/period
|
||||
* attributes.
|
||||
*
|
||||
* @param {Element} manager The annotation manager.
|
||||
* @param {string} html The HTML of the new annotation manager.
|
||||
*/
|
||||
var replaceAnnotationManager = function (manager, html) {
|
||||
var newManager = $(html);
|
||||
manager.html(newManager.html())
|
||||
.attr('data-date', newManager.attr('data-date'))
|
||||
.attr('data-period', newManager.attr('data-period'));
|
||||
createDatePickers(manager);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if an annotation element is starred, false if otherwise.
|
||||
*
|
||||
* @param {Element} annotation The annotation element.
|
||||
* @return {boolean}
|
||||
*/
|
||||
var isAnnotationStarred = function (annotation) {
|
||||
return !!(+$('.annotation-star', annotation).attr('data-starred') == 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Replaces the HTML of an annotation element with HTML returned from Piwik, and
|
||||
* makes sure the data attributes are correct.
|
||||
*
|
||||
* @param {Element} annotation The annotation element.
|
||||
* @param {string} html The replacement HTML (or alternatively, the replacement
|
||||
* element/jQuery object).
|
||||
*/
|
||||
var replaceAnnotationHtml = function (annotation, html) {
|
||||
var newHtml = $(html);
|
||||
annotation.html(newHtml.html()).attr('data-date', newHtml.attr('data-date'));
|
||||
createDatePicker(annotation);
|
||||
};
|
||||
|
||||
/**
|
||||
* Binds events to an annotation manager element.
|
||||
*
|
||||
* @param {Element} manager The annotation manager.
|
||||
* @param {int} idSite The site ID the manager is showing annotations for.
|
||||
* @param {function} onAnnotationCountChange Callback that is called when there is a change
|
||||
* in the number of annotations and/or starred annotations,
|
||||
* eg, when a user adds a new one or deletes an existing one.
|
||||
*/
|
||||
var bindAnnotationManagerEvents = function (manager, idSite, onAnnotationCountChange) {
|
||||
if (!onAnnotationCountChange) {
|
||||
onAnnotationCountChange = function () {};
|
||||
}
|
||||
|
||||
// show new annotation row if create new annotation link is clicked
|
||||
manager.on('click', '.add-annotation', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
$('.new-annotation-row', manager).show();
|
||||
$(this).hide();
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
// hide new annotation row if cancel button clicked
|
||||
manager.on('click', '.new-annotation-cancel', function () {
|
||||
var newAnnotationRow = $(this).parent().parent();
|
||||
newAnnotationRow.hide();
|
||||
|
||||
$('.add-annotation', newAnnotationRow.closest('.annotation-manager')).show();
|
||||
});
|
||||
|
||||
// save new annotation when new annotation row save is clicked
|
||||
manager.on('click', '.new-annotation-save', function () {
|
||||
var addRow = $(this).parent().parent(),
|
||||
addNoteInput = addRow.find('.new-annotation-edit'),
|
||||
noteDate = addRow.find('.annotation-period-edit>a').text();
|
||||
|
||||
// do nothing if input is empty
|
||||
if (!addNoteInput.val()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// disable input & link
|
||||
addNoteInput.attr('disabled', 'disabled');
|
||||
$(this).attr('disabled', 'disabled');
|
||||
|
||||
// add a new annotation for the site, date & period
|
||||
annotationsApi.addAnnotation(
|
||||
idSite,
|
||||
manager.attr('data-date'),
|
||||
manager.attr('data-period'),
|
||||
noteDate,
|
||||
addNoteInput.val(),
|
||||
function (response) {
|
||||
replaceAnnotationManager(manager, response);
|
||||
|
||||
// increment annotation count for this date
|
||||
onAnnotationCountChange(noteDate, 1, 0);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// add new annotation when enter key pressed on new annotation input
|
||||
manager.on('keypress', '.new-annotation-edit', function (e) {
|
||||
if (e.which == 13) {
|
||||
$(this).parent().find('.new-annotation-save').click();
|
||||
}
|
||||
});
|
||||
|
||||
// show annotation editor if edit link, annotation text or period text is clicked
|
||||
manager.on('click', '.annotation-enter-edit-mode', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
var annotationContent = toggleAnnotationMode(this);
|
||||
annotationContent.find('.annotation-edit').focus();
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
// hide annotation editor if cancel button is clicked
|
||||
manager.on('click', '.annotation-cancel', function () {
|
||||
toggleAnnotationMode(this);
|
||||
});
|
||||
|
||||
// save annotation if save button clicked
|
||||
manager.on('click', '.annotation-edit-mode .annotation-save', function () {
|
||||
var annotation = $(this).parent().parent().parent(),
|
||||
input = $('.annotation-edit', annotation),
|
||||
dateEditText = $('.annotation-period-edit>a', annotation).text();
|
||||
|
||||
// if annotation value/date has not changed, just show the view mode instead of edit
|
||||
if (input[0].defaultValue == input.val()
|
||||
&& dateEditText == annotation.attr('data-date')) {
|
||||
toggleAnnotationMode(this);
|
||||
return;
|
||||
}
|
||||
|
||||
// disable input while ajax is happening
|
||||
input.attr('disabled', 'disabled');
|
||||
$(this).attr('disabled', 'disabled');
|
||||
|
||||
// save the note w/ the new note text & date
|
||||
annotationsApi.saveAnnotation(
|
||||
idSite,
|
||||
annotation.attr('data-id'),
|
||||
dateEditText,
|
||||
{
|
||||
note: input.val()
|
||||
},
|
||||
function (response) {
|
||||
response = $(response);
|
||||
|
||||
var newDate = response.attr('data-date'),
|
||||
isStarred = isAnnotationStarred(response),
|
||||
originalDate = annotation.attr('data-date');
|
||||
|
||||
replaceAnnotationHtml(annotation, response);
|
||||
|
||||
// if the date has been changed, update the evolution icon counts to reflect the change
|
||||
if (originalDate != newDate) {
|
||||
// reduce count for original date
|
||||
onAnnotationCountChange(originalDate, -1, isStarred ? -1 : 0);
|
||||
|
||||
// increase count for new date
|
||||
onAnnotationCountChange(newDate, 1, isStarred ? 1 : 0);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// save annotation if 'enter' pressed on input
|
||||
manager.on('keypress', '.annotation-value input', function (e) {
|
||||
if (e.which == 13) {
|
||||
$(this).parent().find('.annotation-save').click();
|
||||
}
|
||||
});
|
||||
|
||||
// delete annotation if delete link clicked
|
||||
manager.on('click', '.delete-annotation', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
var annotation = $(this).parent().parent();
|
||||
$(this).attr('disabled', 'disabled');
|
||||
|
||||
// delete annotation by ajax
|
||||
annotationsApi.deleteAnnotation(
|
||||
idSite,
|
||||
annotation.attr('data-id'),
|
||||
manager.attr('data-date'),
|
||||
manager.attr('data-period'),
|
||||
function (response) {
|
||||
replaceAnnotationManager(manager, response);
|
||||
|
||||
// update evolution icons
|
||||
var isStarred = isAnnotationStarred(annotation);
|
||||
onAnnotationCountChange(annotation.attr('data-date'), -1, isStarred ? -1 : 0);
|
||||
}
|
||||
);
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
// star/unstar annotation if star clicked
|
||||
manager.on('click', '.annotation-star-changeable', function (e) {
|
||||
var annotation = $(this).parent().parent(),
|
||||
newStarredVal = $(this).attr('data-starred') == 0 ? 1 : 0 // flip existing 'starred' value
|
||||
;
|
||||
|
||||
// perform ajax request to star annotation
|
||||
annotationsApi.saveAnnotation(
|
||||
idSite,
|
||||
annotation.attr('data-id'),
|
||||
annotation.attr('data-date'),
|
||||
{
|
||||
starred: newStarredVal
|
||||
},
|
||||
function (response) {
|
||||
replaceAnnotationHtml(annotation, response);
|
||||
|
||||
// change starred count for this annotation in evolution graph based on what we're
|
||||
// changing the starred value to
|
||||
onAnnotationCountChange(annotation.attr('data-date'), 0, newStarredVal == 0 ? -1 : 1);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// when period edit is clicked, show datepicker
|
||||
manager.on('click', '.annotation-period-edit>a', function (e) {
|
||||
e.preventDefault();
|
||||
$('.datepicker', $(this).parent()).toggle();
|
||||
return false;
|
||||
});
|
||||
|
||||
// make sure datepicker popups are closed if someone clicks elsewhere
|
||||
$('body').on('mouseup', function (e) {
|
||||
var container = $('.annotation-period-edit>.datepicker:visible').parent();
|
||||
|
||||
if (!container.has(e.target).length) {
|
||||
container.find('.datepicker').hide();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// used in below function
|
||||
var loadingAnnotationManager = false;
|
||||
|
||||
/**
|
||||
* Shows an annotation manager under a report for a specific site & date range.
|
||||
*
|
||||
* @param {Element} domElem The element of the report to show the annotation manger
|
||||
* under.
|
||||
* @param {int} idSite The ID of the site to show the annotations of.
|
||||
* @param {string} date The start date of the period.
|
||||
* @param {string} period The period type.
|
||||
* @param {int} lastN Whether to include the last N periods in the date range or not. Can
|
||||
* be undefined.
|
||||
* @param {function} [callback]
|
||||
*/
|
||||
var showAnnotationViewer = function (domElem, idSite, date, period, lastN, callback) {
|
||||
var addToAnnotationCount = function (date, amt, starAmt) {
|
||||
if (date.indexOf(',') != -1) {
|
||||
date = date.split(',')[0];
|
||||
}
|
||||
|
||||
$('.evolution-annotations>span', domElem).each(function () {
|
||||
if ($(this).attr('data-date') == date) {
|
||||
// get counts from attributes (and convert them to ints)
|
||||
var starredCount = +$(this).attr('data-starred'),
|
||||
annotationCount = +$(this).attr('data-count');
|
||||
|
||||
// modify the starred count & make sure the correct image is used
|
||||
var newStarCount = starredCount + starAmt;
|
||||
if (newStarCount > 0) {
|
||||
var newImg = 'plugins/Zeitgeist/images/annotations_starred.png';
|
||||
} else {
|
||||
var newImg = 'plugins/Zeitgeist/images/annotations.png';
|
||||
}
|
||||
$(this).attr('data-starred', newStarCount).find('img').attr('src', newImg);
|
||||
|
||||
// modify the annotation count & hide/show based on new count
|
||||
var newCount = annotationCount + amt;
|
||||
$(this).attr('data-count', newCount).css('opacity', newCount > 0 ? 1 : 0);
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var manager = $('.annotation-manager', domElem);
|
||||
if (manager.length) {
|
||||
// if annotations for the requested date + period are already loaded, then just toggle the
|
||||
// visibility of the annotation viewer. otherwise, we reload the annotations.
|
||||
if (manager.attr('data-date') == date
|
||||
&& manager.attr('data-period') == period) {
|
||||
// toggle manager view
|
||||
if (manager.is(':hidden')) {
|
||||
manager.slideDown('slow', function () { if (callback) callback(manager) });
|
||||
}
|
||||
else {
|
||||
manager.slideUp('slow', function () { if (callback) callback(manager) });
|
||||
}
|
||||
}
|
||||
else {
|
||||
// show nothing but the loading gif
|
||||
$('.annotations', manager).html('');
|
||||
$('.loadingPiwik', manager).show();
|
||||
|
||||
// reload annotation manager for new date/period
|
||||
annotationsApi.getAnnotationManager(idSite, date, period, lastN, function (response) {
|
||||
replaceAnnotationManager(manager, response);
|
||||
|
||||
createDatePickers(manager);
|
||||
|
||||
// show if hidden
|
||||
if (manager.is(':hidden')) {
|
||||
manager.slideDown('slow', function () { if (callback) callback(manager) });
|
||||
}
|
||||
else {
|
||||
if (callback) {
|
||||
callback(manager);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
// if we are already loading the annotation manager, don't load it again
|
||||
if (loadingAnnotationManager) {
|
||||
return;
|
||||
}
|
||||
|
||||
loadingAnnotationManager = true;
|
||||
|
||||
var isDashboard = !!$('#dashboardWidgetsArea').length;
|
||||
|
||||
if (isDashboard) {
|
||||
$('.loadingPiwikBelow', domElem).insertAfter($('.evolution-annotations', domElem));
|
||||
}
|
||||
|
||||
var loading = $('.loadingPiwikBelow', domElem).css({display: 'block'});
|
||||
|
||||
// the annotations for this report have not been retrieved yet, so do an ajax request
|
||||
// & show the result
|
||||
annotationsApi.getAnnotationManager(idSite, date, period, lastN, function (response) {
|
||||
var manager = $(response).hide();
|
||||
|
||||
// if an error occurred (and response does not contain the annotation manager), do nothing
|
||||
if (!manager.hasClass('annotation-manager')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// create datepickers for each shown annotation
|
||||
createDatePickers(manager);
|
||||
|
||||
bindAnnotationManagerEvents(manager, idSite, addToAnnotationCount);
|
||||
|
||||
loading.css('visibility', 'hidden');
|
||||
|
||||
// add & show annotation manager
|
||||
if (isDashboard) {
|
||||
manager.insertAfter($('.evolution-annotations', domElem));
|
||||
} else {
|
||||
$('.dataTableFeatures', domElem).append(manager);
|
||||
}
|
||||
|
||||
manager.slideDown('slow', function () {
|
||||
loading.hide().css('visibility', 'visible');
|
||||
loadingAnnotationManager = false;
|
||||
|
||||
if (callback) callback(manager)
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines the x-coordinates of a set of evolution annotation icons.
|
||||
*
|
||||
* @param {Element} annotations The '.evolution-annotations' element.
|
||||
* @param {Element} graphElem The evolution graph's datatable element.
|
||||
*/
|
||||
var placeEvolutionIcons = function (annotations, graphElem) {
|
||||
var canvases = $('.piwik-graph .jqplot-xaxis canvas', graphElem),
|
||||
noteSize = 16;
|
||||
|
||||
// if no graph available, hide all icons
|
||||
if (!canvases || canvases.length == 0) {
|
||||
$('span', annotations).hide();
|
||||
return true;
|
||||
}
|
||||
|
||||
// set position of each individual icon
|
||||
$('span', annotations).each(function (i) {
|
||||
var canvas = $(canvases[i]),
|
||||
canvasCenterX = canvas.position().left + (canvas.width() / 2);
|
||||
$(this).css({
|
||||
left: canvasCenterX - noteSize / 2,
|
||||
// show if there are annotations for this x-axis tick
|
||||
opacity: +$(this).attr('data-count') > 0 ? 1 : 0
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// make showAnnotationViewer, placeEvolutionIcons & annotationsApi globally accessible
|
||||
piwik.annotations = {
|
||||
showAnnotationViewer: showAnnotationViewer,
|
||||
placeEvolutionIcons: placeEvolutionIcons,
|
||||
api: annotationsApi
|
||||
};
|
||||
|
||||
}(jQuery, piwik));
|
||||
209
www/analytics/plugins/Annotations/stylesheets/annotations.less
Executable file
209
www/analytics/plugins/Annotations/stylesheets/annotations.less
Executable file
|
|
@ -0,0 +1,209 @@
|
|||
.evolution-annotations {
|
||||
position: relative;
|
||||
height: 16px;
|
||||
width: 100%;
|
||||
margin-top: 12px;
|
||||
margin-bottom: -28px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.evolution-annotations > span {
|
||||
position: absolute;
|
||||
top:10px;
|
||||
}
|
||||
|
||||
#dashboard, .ui-dialog {
|
||||
.evolution-annotations {
|
||||
margin-top: -5px;
|
||||
margin-bottom: -5px;
|
||||
}
|
||||
.evolution-annotations > span {
|
||||
top: -1px;
|
||||
position: absolute;
|
||||
}
|
||||
.annotation-manager {
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.annotation-manager {
|
||||
text-align: left;
|
||||
margin-top: -18px;
|
||||
}
|
||||
|
||||
.annotations-header {
|
||||
display: inline-block;
|
||||
width: 128px;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
margin-bottom: 8px;
|
||||
vertical-align: top;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.annotation-controls {
|
||||
display: inline-block;
|
||||
margin-left: 132px;
|
||||
}
|
||||
|
||||
.annotation-controls>a {
|
||||
font-size: 11px;
|
||||
font-style: italic;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
padding: 3px 0 6px 0;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.annotation-controls>a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.annotation-list {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.annotation-list table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.annotation-list-range {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
color: #666;
|
||||
vertical-align: top;
|
||||
margin: 0 0 8px 8px;
|
||||
}
|
||||
|
||||
.empty-annotation-list, .annotation-list .loadingPiwik {
|
||||
display: block;
|
||||
|
||||
font-style: italic;
|
||||
color: #666;
|
||||
margin: 0 0 12px 140px;
|
||||
}
|
||||
|
||||
.annotation-meta {
|
||||
width: 128px;
|
||||
text-align: right;
|
||||
vertical-align: top;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.annotation-user {
|
||||
font-style: italic;
|
||||
font-size: 11px;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.annotation-user-cell {
|
||||
vertical-align: top;
|
||||
width: 92px;
|
||||
}
|
||||
|
||||
.annotation-period {
|
||||
display: inline-block;
|
||||
font-style: italic;
|
||||
margin: 0 8px 8px 8px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.annotation-value {
|
||||
margin: 0 12px 12px 8px;
|
||||
vertical-align: top;
|
||||
position: relative;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.annotation-enter-edit-mode {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.annotation-edit, .new-annotation-edit {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
width: 98%;
|
||||
}
|
||||
|
||||
.annotation-star {
|
||||
display: inline-block;
|
||||
margin: 0 8px 8px 0;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.annotation-star-changeable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.delete-annotation {
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
color: red;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.delete-annotation:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.annotation-manager .submit {
|
||||
float: none;
|
||||
}
|
||||
|
||||
.edit-annotation {
|
||||
font-size: 10px;
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.edit-annotation:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.annotationView {
|
||||
float: right;
|
||||
margin-left: 5px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.annotationView > span {
|
||||
font-style: italic;
|
||||
display: inline-block;
|
||||
margin: 4px 4px 0 4px;
|
||||
}
|
||||
|
||||
.annotation-period-edit {
|
||||
display: inline-block;
|
||||
background: white;
|
||||
color: #444;
|
||||
font-size: 12px;
|
||||
border: 1px solid #e4e5e4;
|
||||
padding: 5px 5px 6px 3px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.annotation-period-edit:hover {
|
||||
background: #f1f0eb;
|
||||
border-color: #a9a399;
|
||||
}
|
||||
|
||||
.annotation-period-edit>a {
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.annotation-period-edit>.datepicker {
|
||||
position: absolute;
|
||||
margin-top: 6px;
|
||||
margin-left: -5px;
|
||||
z-index: 15;
|
||||
background: white;
|
||||
border: 1px solid #e4e5e4;
|
||||
border-radius: 4px;
|
||||
}
|
||||
45
www/analytics/plugins/Annotations/templates/_annotation.twig
Executable file
45
www/analytics/plugins/Annotations/templates/_annotation.twig
Executable file
|
|
@ -0,0 +1,45 @@
|
|||
<tr class="annotation" data-id="{{ annotation.idNote }}" data-date="{{ annotation.date }}">
|
||||
<td class="annotation-meta">
|
||||
<div class="annotation-star{% if annotation.canEditOrDelete %} annotation-star-changeable{% endif %}" data-starred="{{ annotation.starred }}"
|
||||
{% if annotation.canEditOrDelete %}title="{{ 'Annotations_ClickToStarOrUnstar'|translate }}"{% endif %}>
|
||||
{% if annotation.starred %}
|
||||
<img src="plugins/Zeitgeist/images/star.png"/>
|
||||
{% else %}
|
||||
<img src="plugins/Zeitgeist/images/star_empty.png"/>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="annotation-period {% if annotation.canEditOrDelete %}annotation-enter-edit-mode{% endif %}">({{ annotation.date }})</div>
|
||||
{% if annotation.canEditOrDelete %}
|
||||
<div class="annotation-period-edit" style="display:none;">
|
||||
<a href="#">{{ annotation.date }}</a>
|
||||
<div class="datepicker" style="display:none;"/>
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="annotation-value">
|
||||
<div class="annotation-view-mode">
|
||||
<span {% if annotation.canEditOrDelete %}title="{{ 'Annotations_ClickToEdit'|translate }}"
|
||||
class="annotation-enter-edit-mode"{% endif %}>{{ annotation.note|raw }}</span>
|
||||
{% if annotation.canEditOrDelete %}
|
||||
<a href="#" class="edit-annotation annotation-enter-edit-mode" title="{{ 'Annotations_ClickToEdit'|translate }}">{{ 'General_Edit'|translate }}...</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if annotation.canEditOrDelete %}
|
||||
<div class="annotation-edit-mode" style="display:none;">
|
||||
<input class="annotation-edit" type="text" value="{{ annotation.note|raw }}"/>
|
||||
<br/>
|
||||
<input class="annotation-save submit" type="button" value="{{ 'General_Save'|translate }}"/>
|
||||
<input class="annotation-cancel submit" type="button" value="{{ 'General_Cancel'|translate }}"/>
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% if annotation.user is defined and userLogin != 'anonymous' %}
|
||||
<td class="annotation-user-cell">
|
||||
<span class="annotation-user">{{ annotation.user }}</span><br/>
|
||||
{% if annotation.canEditOrDelete %}
|
||||
<a href="#" class="delete-annotation" style="display:none;" title="{{ 'Annotations_ClickToDelete'|translate }}">{{ 'General_Delete'|translate }}</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
|
||||
29
www/analytics/plugins/Annotations/templates/_annotationList.twig
Executable file
29
www/analytics/plugins/Annotations/templates/_annotationList.twig
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
<div class="annotations">
|
||||
|
||||
{% if annotations is empty %}
|
||||
<div class="empty-annotation-list">{{ 'Annotations_NoAnnotations'|translate }}</div>
|
||||
{% endif %}
|
||||
|
||||
<table>
|
||||
{% for annotation in annotations %}
|
||||
{% include "@Annotations/_annotation.twig" %}
|
||||
{% endfor %}
|
||||
<tr class="new-annotation-row" style="display:none;" data-date="{{ startDate }}">
|
||||
<td class="annotation-meta">
|
||||
<div class="annotation-star"> </div>
|
||||
<div class="annotation-period-edit">
|
||||
<a href="#">{{ startDate }}</a>
|
||||
|
||||
<div class="datepicker" style="display:none;"/>
|
||||
</div>
|
||||
</td>
|
||||
<td class="annotation-value">
|
||||
<input type="text" value="" class="new-annotation-edit" placeholder="{{ 'Annotations_EnterAnnotationText'|translate }}"/><br/>
|
||||
<input type="button" class="submit new-annotation-save" value="{{ 'General_Save'|translate }}"/>
|
||||
<input type="button" class="submit new-annotation-cancel" value="{{ 'General_Cancel'|translate }}"/>
|
||||
</td>
|
||||
<td class="annotation-user-cell"><span class="annotation-user">{{ userLogin }}</span></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
27
www/analytics/plugins/Annotations/templates/getAnnotationManager.twig
Executable file
27
www/analytics/plugins/Annotations/templates/getAnnotationManager.twig
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
<div class="annotation-manager"
|
||||
{% if startDate != endDate %}data-date="{{ startDate }},{{ endDate }}" data-period="range"
|
||||
{% else %}data-date="{{ startDate }}" data-period="{{ period }}"
|
||||
{% endif %}>
|
||||
|
||||
<div class="annotations-header">
|
||||
<span>{{ 'Annotations_Annotations'|translate }}</span>
|
||||
</div>
|
||||
|
||||
<div class="annotation-list-range">{{ startDatePretty }}{% if startDate != endDate %} — {{ endDatePretty }}{% endif %}</div>
|
||||
|
||||
<div class="annotation-list">
|
||||
{% include "@Annotations/_annotationList.twig" %}
|
||||
|
||||
<span class="loadingPiwik" style="display:none;"><img src="plugins/Zeitgeist/images/loading-blue.gif"/>{{ 'General_Loading'|translate }}</span>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="annotation-controls">
|
||||
{% if canUserAddNotes %}
|
||||
<a href="#" class="add-annotation" title="{{ 'Annotations_CreateNewAnnotation'|translate }}">{{ 'Annotations_CreateNewAnnotation'|translate }}</a>
|
||||
{% elseif userLogin == 'anonymous' %}
|
||||
<a href="index.php?module=Login">{{ 'Annotations_LoginToAnnotate'|translate }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
14
www/analytics/plugins/Annotations/templates/getEvolutionIcons.twig
Executable file
14
www/analytics/plugins/Annotations/templates/getEvolutionIcons.twig
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
<div class="evolution-annotations">
|
||||
{% for dateCountPair in annotationCounts %}
|
||||
{% set date=dateCountPair[0] %}
|
||||
{% set counts=dateCountPair[1] %}
|
||||
<span data-date="{{ date }}" data-count="{{ counts.count }}" data-starred="{{ counts.starred }}"
|
||||
{% if counts.count == 0 %}title="{{ 'Annotations_AddAnnotationsFor'|translate(date) }}"
|
||||
{% elseif counts.count == 1 %}title="{{ 'Annotations_AnnotationOnDate'|translate(date,
|
||||
counts.note)|raw }}
|
||||
{{ 'Annotations_ClickToEditOrAdd'|translate }}"
|
||||
{% else %}}title="{{ 'Annotations_ViewAndAddAnnotations'|translate(date) }}"{% endif %}>
|
||||
<img src="plugins/Zeitgeist/images/{% if counts.starred > 0 %}annotations_starred.png{% else %}annotations.png{% endif %}" width="16" height="16"/>
|
||||
</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
|
@ -0,0 +1 @@
|
|||
{% include "@Annotations/_annotation.twig" %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue