549 lines
17 KiB
PHP
549 lines
17 KiB
PHP
<?php
|
|
defined( 'WPINC' ) OR exit;
|
|
|
|
include_once DG_PATH . 'inc/class-gallery-sanitization.php';
|
|
|
|
/**
|
|
* Holds data specific to a given document gallery.
|
|
*
|
|
* @author drossiter
|
|
*/
|
|
class DG_Gallery {
|
|
|
|
/*==========================================================================
|
|
* PRIVATE FIELDS
|
|
*=========================================================================*/
|
|
|
|
private $atts, $taxa;
|
|
|
|
/**
|
|
* @var DG_Document[] The documents in this gallery.
|
|
*/
|
|
private $docs = array();
|
|
|
|
/**
|
|
* @var string[] The errors in this gallery.
|
|
*/
|
|
private $errs = array();
|
|
|
|
/**
|
|
* @var int The instance number across all galleries on this page.
|
|
*/
|
|
private $instance;
|
|
|
|
private $pg_count = 1, $cur_pg = 1;
|
|
|
|
/**
|
|
* @var DG_Gallery[] All gallery instances on this page.
|
|
*/
|
|
private static $galleries = array();
|
|
|
|
// templates for HTML output
|
|
private static $no_docs, $comment, $defaults;
|
|
|
|
/*==========================================================================
|
|
* PUBLIC FUNCTIONS
|
|
*=========================================================================*/
|
|
|
|
/**
|
|
* @return bool Whether to link to attachment pg.
|
|
*/
|
|
public function linkToAttachmentPg() {
|
|
return $this->atts['attachment_pg'];
|
|
}
|
|
|
|
/**
|
|
* @return bool Whether to open thumb links in new window.
|
|
*/
|
|
public function openLinkInNewWindow() {
|
|
return $this->atts['new_window'];
|
|
}
|
|
|
|
/**
|
|
* @return bool Whether to use "fancy" thumbnails.
|
|
*/
|
|
public function useFancyThumbs() {
|
|
return $this->atts['fancy'];
|
|
}
|
|
|
|
/**
|
|
* @return bool Whether descriptions should be included in output.
|
|
*/
|
|
public function useDescriptions() {
|
|
return $this->atts['descriptions'];
|
|
}
|
|
|
|
/**
|
|
* @return DG_Document[] The documents in this gallery.
|
|
*/
|
|
public function getDocuments() {
|
|
return $this->docs;
|
|
}
|
|
|
|
/*==========================================================================
|
|
* PUBLIC CLASS METHODS
|
|
*=========================================================================*/
|
|
|
|
/**
|
|
* @param int $blog The blog we're retrieving options for (null => current blog).
|
|
*
|
|
* @return mixed[] Gets gallery branch of DG options array.
|
|
*/
|
|
public static function getOptions( $blog = null ) {
|
|
$options = DocumentGallery::getOptions( $blog );
|
|
|
|
return $options['gallery'];
|
|
}
|
|
|
|
/**
|
|
* @param mixed[] $options New value for gallery branch of DG options array.
|
|
* @param int $blog The blog we're retrieving options for (null => current blog).
|
|
*/
|
|
public static function setOptions( $options, $blog = null ) {
|
|
$dg_options = DocumentGallery::getOptions( $blog );
|
|
$dg_options['gallery'] = $options;
|
|
DocumentGallery::setOptions( $dg_options, $blog );
|
|
}
|
|
|
|
/**
|
|
* Cleans up user input, making sure we don't pass crap on to WP core.
|
|
*
|
|
* @param mixed[] $old_defaults The previous set of defaults.
|
|
* @param mixed[] $defaults The defaults array to sanitize.
|
|
* @param string[] &$errs The array of errors, which will be appended with any errors found.
|
|
*
|
|
* @return mixed[] The sanitized defaults.
|
|
*/
|
|
public static function sanitizeDefaults( $old_defaults, $defaults, &$errs ) {
|
|
if ( is_null( $old_defaults ) ) {
|
|
$old_defaults = self::getOptions();
|
|
}
|
|
|
|
// remove invalid keys
|
|
$sanitized = is_array( $defaults )
|
|
? array_intersect_key( $defaults, $old_defaults )
|
|
: array();
|
|
|
|
// add any missing keys & sanitize each new value
|
|
foreach ( $old_defaults as $k => $v ) {
|
|
if ( ! isset( $sanitized[ $k ] ) ) {
|
|
if ( is_bool( $v ) ) {
|
|
// checkbox
|
|
$sanitized[ $k ] = false;
|
|
} else {
|
|
// missing value
|
|
$sanitized[ $k ] = $v;
|
|
}
|
|
} else if ( $sanitized[ $k ] !== $v ) { //Sometimes we get boolean in the string form for checkboxes
|
|
// sanitize value if different from old value
|
|
$sanitized[ $k ] = DG_GallerySanitization::sanitizeParameter( $k, $sanitized[ $k ], $errs );
|
|
}
|
|
}
|
|
|
|
return $sanitized;
|
|
}
|
|
|
|
/**
|
|
* @return DG_Gallery[] The gallery instances on this page.
|
|
*/
|
|
public static function getGalleries() {
|
|
return self::$galleries;
|
|
}
|
|
|
|
/*==========================================================================
|
|
* INIT GALLERY
|
|
*=========================================================================*/
|
|
|
|
/**
|
|
* Initializes static values for this class.
|
|
*/
|
|
public static function init() {
|
|
if ( ! isset( self::$comment ) ) {
|
|
self::$comment =
|
|
PHP_EOL . '<!-- ' . __( 'Generated using Document Gallery. Get yours here: ', 'document-gallery' ) .
|
|
'http://wordpress.org/extend/plugins/document-gallery -->' . PHP_EOL;
|
|
self::$no_docs = '<!-- ' . __( 'No attachments to display. How boring! :(', 'document-gallery' ) . ' -->';
|
|
self::$defaults = array_merge( array( 'include' => '', 'exclude' => '' ), self::getOptions() );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Builds a gallery object with attributes passed.
|
|
*
|
|
* @param mixed[] $atts Array of attributes used in shortcode.
|
|
*/
|
|
public function __construct( $atts ) {
|
|
self::$galleries[] = $this;
|
|
static $instance = 0;
|
|
$this->instance = ++$instance;
|
|
|
|
include_once DG_PATH . 'inc/class-document.php';
|
|
|
|
// empty string is passed when no arguments are given, but constructor expects an array
|
|
$atts = empty( $atts ) ? array() : $atts;
|
|
|
|
// get_post will return null during AJAX requests
|
|
$post = get_post();
|
|
$post_id = !is_null( $post ) ? $post->ID : -1;
|
|
|
|
if ( ! empty( $atts['ids'] ) ) {
|
|
// 'ids' is explicitly ordered, unless you specify otherwise.
|
|
if ( empty( $atts['orderby'] ) ) {
|
|
$atts['orderby'] = 'post__in';
|
|
}
|
|
|
|
$atts['include'] = $atts['ids'];
|
|
unset( $atts['ids'] );
|
|
}
|
|
|
|
// allow abbreviated columns attribute
|
|
if ( ! empty( $atts['cols'] ) ) {
|
|
$atts['columns'] = $atts['cols'];
|
|
unset( $atts['cols'] );
|
|
}
|
|
|
|
if ( ! empty( $atts['images'] ) ) {
|
|
if ( DG_Util::toBool( $atts['images'], false ) ) {
|
|
$options = self::getOptions();
|
|
$mimes = trim( isset( $atts['mime_types'] ) ? $atts['mime_types'] : $options['mime_types'] );
|
|
if ( ! preg_match( '/[,^]image[,$]/', $mimes ) ) {
|
|
$atts['mime_types'] = empty( $mimes ) ? 'image' : ( $mimes . ',image' );
|
|
}
|
|
}
|
|
|
|
unset( $atts['images'] );
|
|
}
|
|
|
|
/**
|
|
* @deprecated localpost will be removed at some point.
|
|
*/
|
|
if ( ! empty( $atts['localpost'] ) ) {
|
|
$atts['id'] = -1;
|
|
unset( $atts['localpost'] );
|
|
}
|
|
|
|
$defaults = array_merge( array( 'id' => $post_id ), self::$defaults );
|
|
|
|
// values used to construct tax query (may be empty)
|
|
$this->taxa = array_diff_key( $atts, $defaults );
|
|
|
|
// all recognized attributes go here
|
|
$this->atts = shortcode_atts( $defaults, $atts );
|
|
|
|
// goes through all values in atts, setting errs as needed
|
|
$this->atts = self::sanitizeDefaults( $defaults, $this->atts, $this->errs );
|
|
|
|
// query DB for all documents requested
|
|
try {
|
|
foreach ( $this->getAttachments() as $doc ) {
|
|
$this->docs[] = new DG_Document( $doc, $this );
|
|
}
|
|
} catch ( InvalidArgumentException $e ) {
|
|
// errors will be printed in __toString()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets all valid Documents based on the attributes passed by the user.
|
|
* NOTE: Keys in returned array are arbitrary and will vary. They should be ignored.
|
|
* @return WP_Post[] Contains all documents matching the query.
|
|
* @throws InvalidArgumentException Thrown when $this->errs is not empty.
|
|
*/
|
|
private function getAttachments() {
|
|
$query = array(
|
|
'posts_per_page' => $this->atts['limit'],
|
|
'offset' => $this->atts['skip'],
|
|
'orderby' => $this->atts['orderby'],
|
|
'order' => $this->atts['order'],
|
|
'post_status' => $this->atts['post_status'],
|
|
'post_type' => $this->atts['post_type'],
|
|
'post_mime_type' => $this->atts['mime_types'],
|
|
'suppress_filters' => true
|
|
);
|
|
|
|
$this->setTaxa( $query );
|
|
|
|
if ( ! empty( $this->errs ) ) {
|
|
throw new InvalidArgumentException();
|
|
}
|
|
|
|
if ( ! empty( $this->atts['include'] ) ) {
|
|
$query['post__in'] = wp_parse_id_list( $this->atts['include'] );
|
|
} else {
|
|
// id == 0 => all attachments w/o a parent
|
|
// id == null => all matched attachments
|
|
$query['post_parent'] = $this->atts['id'];
|
|
if ( ! empty( $this->atts['exclude'] ) ) {
|
|
$query['post__not_in'] = wp_parse_id_list( $this->atts['exclude'] );
|
|
}
|
|
}
|
|
|
|
$wpq = new WP_Query();
|
|
$attachments = $wpq->query( $query );
|
|
$this->pg_count = $wpq->max_num_pages;
|
|
if ( $this->atts['skip'] >= 0 && $this->atts['limit'] > 0 ) {
|
|
$this->cur_pg = min( $this->atts['skip'] / $this->atts['limit'] + 1, $wpq->max_num_pages );
|
|
}
|
|
|
|
return $attachments;
|
|
}
|
|
|
|
/**
|
|
* Function loops through all attributes passed that did not match
|
|
* self::$defaults. If they are the name of a taxonomy, they are plugged
|
|
* into the query, otherwise $this->errs is appended with an error string.
|
|
*
|
|
* @param mixed[] $query Query to insert tax query into.
|
|
*/
|
|
private function setTaxa( &$query ) {
|
|
if ( ! empty( $this->taxa ) ) {
|
|
static $op_regex = '/(.+)_(?:relation|operator)$/i';
|
|
static $inc_child_regex = '/(.+)_include_children$/i';
|
|
$taxa = array( 'relation' => $this->atts['relation'] );
|
|
$operator = array();
|
|
$include_children = array();
|
|
|
|
// find any taxa-specific settings
|
|
$excluded_keys = array();
|
|
foreach ( $this->taxa as $key => $value ) {
|
|
if ( preg_match( $op_regex, $key, $matches ) ) {
|
|
$base = $matches[1];
|
|
if ( isset( $this->taxa[$base] ) ) {
|
|
$operator[$base] = DG_GallerySanitization::sanitizeParameter( 'operator', $value, $this->errs );
|
|
$excluded_keys[] = $key;
|
|
}
|
|
} elseif ( preg_match( $inc_child_regex, $key, $matches ) ) {
|
|
$base = $matches[1];
|
|
if ( isset( $this->taxa[$base] ) ) {
|
|
$include_children[$base] = DG_GallerySanitization::sanitizeParameter( 'include_children', $value, $this->errs );
|
|
$excluded_keys[] = $key;
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: Need to refactor this whole method to get this filter out of setTaxa
|
|
// implement with add_action with signature: function dg_query(&$query, $taxa, &$excluded_keys, &$errs)
|
|
do_action_ref_array( 'dg_query', array( &$query, $this->taxa, &$excluded_keys, &$this->errs ) );
|
|
|
|
// build tax query
|
|
foreach ( $this->taxa as $taxon => $terms ) {
|
|
if ( in_array( $taxon, $excluded_keys ) ) continue;
|
|
|
|
$terms = $this->getTermIdsByNames( $taxon, explode( ',', $terms ) );
|
|
|
|
$taxa[] = array(
|
|
'taxonomy' => $taxon,
|
|
'field' => 'id',
|
|
'terms' => $terms,
|
|
'include_children' => isset( $include_children[ $taxon ] ) ? $include_children[$taxon] : true,
|
|
'operator' => isset( $operator[ $taxon ] ) ? $operator[ $taxon ] : 'IN'
|
|
);
|
|
}
|
|
|
|
// create nested structure
|
|
$query['tax_query'] = $taxa;
|
|
}
|
|
}
|
|
|
|
/*==========================================================================
|
|
* HELPER FUNCTIONS
|
|
*=========================================================================*/
|
|
|
|
/**
|
|
* Returns an array of term ids when provided with a list of term names.
|
|
* Also appends an entry onto $errs if any invalid names are found.
|
|
*
|
|
* @param string $taxon The taxon these terms are a member of.
|
|
* @param string[] $term_names Terms to retrieve.
|
|
*
|
|
* @return WP_Term[] All matched terms.
|
|
*/
|
|
private function getTermIdsByNames( $taxon, $term_names ) {
|
|
return $this->getTermXByNames( 'term_id', $taxon, $term_names );
|
|
}
|
|
|
|
/**
|
|
* Returns an array of term slugs when provided with a list of term names.
|
|
* Also appends an entry onto $errs if any invalid names are found.
|
|
*
|
|
* @param string $taxon The taxon these terms are a member of.
|
|
* @param string[] $term_names Terms to retrieve.
|
|
*
|
|
* @return WP_Term[] All matched terms.
|
|
*/
|
|
private function getTermSlugsByNames( $taxon, $term_names ) {
|
|
return $this->getTermXByNames( 'slug', $taxon, $term_names );
|
|
}
|
|
|
|
/**
|
|
* Returns a list of x, where x may be any of the fields within a
|
|
* term object, when provided with a list of term names (not slugs).
|
|
* (http://codex.wordpress.org/Function_Reference/get_term_by#Return_Values)
|
|
*
|
|
* Also appends an entry onto $errs if any invalid names are found.
|
|
*
|
|
* @param string $x Field to retrieve from matched term.
|
|
* @param string $taxon The taxon these terms are a member of.
|
|
* @param string[] $term_idents Terms to retrieve, identified by either slug or name.
|
|
*
|
|
* @return WP_Term[] All matched terms.
|
|
*/
|
|
private function getTermXByNames( $x, $taxon, $term_idents ) {
|
|
$ret = array();
|
|
$valid = true;
|
|
|
|
// taxons may optionally be prefixed by 'tax_' --
|
|
// this is only useful when avoiding collisions with other attributes
|
|
if ( ! taxonomy_exists( $taxon ) ) {
|
|
if ( DG_Util::startsWith( $taxon, 'tax_' ) && ( $tmp = substr( $taxon, 4 ) ) && taxonomy_exists( $tmp ) ) {
|
|
$taxon = $tmp;
|
|
} else {
|
|
$this->errs[] = sprintf( DG_GallerySanitization::getUnaryErr(), 'taxon', $taxon );
|
|
$valid = false;
|
|
}
|
|
}
|
|
|
|
// only check terms if we first have a valid taxon
|
|
if ( $valid ) {
|
|
foreach ( $term_idents as $ident ) {
|
|
$term = get_term_by( 'slug', $ident, $taxon );
|
|
if ( ! $term ) {
|
|
$term = get_term_by( 'name', $ident, $taxon );
|
|
}
|
|
|
|
if ( $term ) {
|
|
$ret[] = $term->{$x};
|
|
} else {
|
|
$this->errs[] = sprintf( __( '%s is not a valid term slug/name in %s.',
|
|
'document-gallery' ), $ident, $taxon );
|
|
}
|
|
}
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Function returns false for positive ints, true otherwise.
|
|
*
|
|
* @param string $var could be anything.
|
|
*
|
|
* @return boolean indicating whether $var is not a positive int.
|
|
*/
|
|
private static function negativeInt( $var ) {
|
|
return ! is_numeric( $var ) // isn't numeric
|
|
|| (int) $var != $var // isn't int
|
|
|| (int) $var < 0; // isn't positive
|
|
}
|
|
|
|
/*==========================================================================
|
|
* OUTPUT HTML STRING
|
|
*=========================================================================*/
|
|
|
|
/**
|
|
* @return mixed[] The data to be used in the data-shortcode attribute.
|
|
*/
|
|
private function getShortcodeData() {
|
|
$ret = array_merge( $this->atts, $this->taxa );
|
|
|
|
// need to undo nulling of -1 ID for version sent out to JSON
|
|
if ( is_null( $ret['id'] ) ) {
|
|
$ret['id'] = -1;
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* @filter dg_gallery_template Allows the user to filter anything content surrounding the generated gallery.
|
|
* @filter dg_row_template Filters the outer DG wrapper HTML. Passes a single
|
|
* bool value indicating whether the gallery is using descriptions or not.
|
|
* @return string HTML representing this Gallery.
|
|
*/
|
|
public function __toString() {
|
|
if ( ! empty( $this->errs ) ) {
|
|
return '<p>' . implode( '</p><p>', $this->errs ) . '</p>';
|
|
}
|
|
|
|
if ( empty( $this->docs ) ) {
|
|
return self::$no_docs;
|
|
}
|
|
|
|
$icon_find = array( '%class%', '%icons%' );
|
|
$icon_repl = array();
|
|
$icon_classes = array( 'document-icon-row' );
|
|
$gallery_find = array( '%id%', '%data%', '%rows%', '%class%' );
|
|
$gallery_repl = array( "document-gallery-$this->instance", ( "data-shortcode='" . wp_json_encode( self::getShortcodeData() ) . "'" ), '' );
|
|
$gallery_classes = array( 'document-gallery' );
|
|
|
|
if ( $this->useDescriptions() ) {
|
|
$icon_classes[] = 'descriptions';
|
|
}
|
|
|
|
$icon_repl[] = implode( ' ', $icon_classes );
|
|
|
|
$icon_wrapper = apply_filters(
|
|
'dg_row_template',
|
|
"<div class='%class%'>" . PHP_EOL . '%icons%' . PHP_EOL . '</div>' . PHP_EOL,
|
|
$this->useDescriptions() );
|
|
|
|
if ( $this->useDescriptions() ) {
|
|
foreach ( $this->docs as $doc ) {
|
|
$icon_repl[1] = $doc;
|
|
$gallery_repl[2] .= str_replace( $icon_find, $icon_repl, $icon_wrapper );
|
|
}
|
|
} else {
|
|
$count = count( $this->docs );
|
|
$cols = !is_null( $this->atts['columns'] ) ? $this->atts['columns'] : $count;
|
|
|
|
if ( apply_filters( 'dg_use_default_gallery_style', true ) ) {
|
|
$itemwidth = $cols > 0 ? ( floor( 100 / $cols ) - 1 ) : 100;
|
|
$gallery_repl[1] .= " data-icon-width='$itemwidth'";
|
|
}
|
|
|
|
for ( $i = 0; $i < $count; $i += $cols ) {
|
|
$icon_repl[1] = '';
|
|
|
|
$min = min( $i + $cols, $count );
|
|
for ( $x = $i; $x < $min; $x++ ) {
|
|
$icon_repl[1] .= $this->docs[ $x ];
|
|
}
|
|
|
|
$gallery_repl[2] .= str_replace( $icon_find, $icon_repl, $icon_wrapper );
|
|
}
|
|
}
|
|
|
|
// allow user to filter gallery wrapper
|
|
$gallery = apply_filters( 'dg_gallery_template', '<div id="%id%" class="%class%" %data%>' . PHP_EOL . '%rows%</div>', $this->useDescriptions() );
|
|
|
|
// build pagination section
|
|
if ( $this->atts['paginate'] && $this->atts['limit'] > 0 && $this->pg_count > 1 ) {
|
|
$args = array(
|
|
'base' => '#%_%',
|
|
'format' => 'dg_page=%#%',
|
|
'total' => $this->pg_count,
|
|
'current' => $this->cur_pg,
|
|
'prev_text' => __( '«' ),
|
|
'next_text' => __( '»' )
|
|
);
|
|
$gallery_repl[2] .= '<div class="paginate">' . paginate_links( $args ) . '</div>';
|
|
$gallery_classes[] = 'dg-paginate-wrapper';
|
|
}
|
|
|
|
$gallery_repl[] = implode( ' ', $gallery_classes );
|
|
|
|
$comment = self::$comment;
|
|
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
|
$ids = array();
|
|
foreach ( $this->docs as $doc ) {
|
|
$ids[] = $doc->getId();
|
|
}
|
|
|
|
$comment .= '<!-- Attachment IDs: ' . implode( ', ', $ids ) . ' -->' . PHP_EOL;
|
|
}
|
|
|
|
return $comment . str_replace( $gallery_find, $gallery_repl, $gallery );
|
|
}
|
|
}
|
|
|
|
DG_Gallery::init(); |