diff options
Diffstat (limited to 'plugins/Annotations/AnnotationList.php')
-rwxr-xr-x | plugins/Annotations/AnnotationList.php | 824 |
1 files changed, 401 insertions, 423 deletions
diff --git a/plugins/Annotations/AnnotationList.php b/plugins/Annotations/AnnotationList.php index 47d897f2dc..daed59a554 100755 --- a/plugins/Annotations/AnnotationList.php +++ b/plugins/Annotations/AnnotationList.php @@ -12,440 +12,418 @@ /** * This class can be used to query & modify annotations for multiple sites * at once. - * + * * Example use: * $annotations = new Piwik_Annotations_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). - * + * * @package Piwik_Annotations */ class Piwik_Annotations_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 = Piwik_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); - Piwik_SetOption($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]); - } - - /** - * 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. - * @param array The annotation. - * @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 Piwik_Date|false $startDate The start of the date range. - * @param Piwik_Date|false $endDate The end of the date range. - * @param string|int|array|false $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 = Piwik_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 = Piwik_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, Piwik_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 - */ - 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 = Piwik_GetOption($optionName); - - if ($serialized !== false) - { - $result[$id] = unserialize($serialized); - } - 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. - */ - 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. - */ - public static function getAnnotationCollectionOptionName( $idSite ) - { - return $idSite.self::ANNOTATION_COLLECTION_OPTION_SUFFIX; - } + 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 = Piwik_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); + Piwik_SetOption($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]); + } + + /** + * 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. + * @param array The annotation. + * @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 Piwik_Date|false $startDate The start of the date range. + * @param Piwik_Date|false $endDate The end of the date range. + * @param string|int|array|false $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 = Piwik_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 = Piwik_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, Piwik_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 + */ + 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 = Piwik_GetOption($optionName); + + if ($serialized !== false) { + $result[$id] = unserialize($serialized); + } 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. + */ + 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. + */ + public static function getAnnotationCollectionOptionName($idSite) + { + return $idSite . self::ANNOTATION_COLLECTION_OPTION_SUFFIX; + } } |