To participate you must create an account on apostrophenow.org. If you have already done so, click Login.

Show
Ignore:
Timestamp:
08/08/10 12:18:34 (22 months ago)
Author:
tboutell
Message:

Merged the cropping branch back to trunk:

Cropping is now available in the media repository. Every time you select an image for use on the site you have the opportunity to crop it. The image size constraints of the slot are taken into account. This means that you can satisfy a given set of constraints by cropping a wide variety of images that would not have been eligible for selection before.

Crops of existing images appear in the database as separate aMediaItem objects but do not duplicate the original image file. This means that many crops of a single source image can exist without significant overhead.

The slug field of a cropped version of an image contains cropping parameters. When rendering the image, Symfony routes spot these and crop the original file as needed.

By approaching the problem this way we have avoided the need for code that takes advantage of the media repository's image selection capabilities to change in any way in order to take advantage of cropping.

The single-image-select behavior of Apostrophe has changed to accommodate the extra cropping step. Single select for PDFs and videos still uses the selectSingle partial because they cannot be cropped.

Thanks to Spike Broehm for his contributions to the cropping project.

Fixes #227

Location:
plugins/apostrophePlugin/trunk
Files:
27 modified
11 copied

Legend:

Unmodified
Added
Removed
  • plugins/apostrophePlugin/trunk

  • plugins/apostrophePlugin/trunk/config/doctrine/schema.yml

    r1704 r1917  
    373373    Timestampable: ~ 
    374374    Taggable: ~ 
     375 
     376    # For virtual media items that are just croppings of others, we explicitly set slug. 
     377    # If this item is a crop of another item, the slug will 
     378    # look like this: original_slug.cropLeft.cropTop.cropWidth.cropHeight 
     379    # The aMediaBackend/image action then sees these extra 
     380    # parameters in the route and behaves accordingly 
     381    # and we can look directly at that file to finish the job 
     382     
     383    # Otherwise we let the behavior do its job 
     384 
    375385    Sluggable: 
    376386      fields: [title] 
     
    378388      # We unified this 
    379389      builder: aTools::slugify 
     390 
    380391  columns: 
    381392    id: 
     
    404415    format: 
    405416      type: string(10) 
     417       
    406418    # Preferred still image or video dimensions. For a still image 
    407419    # these are the dimensions of the original. For video they are 
     
    411423    height: 
    412424      type: integer(4) 
    413  
     425       
    414426    # If this field is non-null, it contains HTML embed/object code to 
    415427    # be used without alteration (except for replacing _WIDTH_ and _HEIGHT_) 
  • plugins/apostrophePlugin/trunk/lib/action/BaseaMediaActions.class.php

    r1872 r1917  
    3131        } 
    3232 
     33  // Supported for backwards compatibility. See also  
     34  // aMediaSelect::select() 
     35   
    3336  public function executeSelect(sfRequest $request) 
    3437  { 
     
    4548      $selection = array($request->getParameter('aMediaId') + 0); 
    4649    }  
    47     $items = aMediaItemTable::retrieveByIds($selection); 
    48     $ids = array(); 
    49     foreach ($items as $item) 
    50     { 
    51       $ids[] = $item->getId(); 
    52     } 
    5350    $options = array(); 
    5451    $optional = array('type', 'aspect-width', 'aspect-height', 
     
    6158      } 
    6259    } 
    63     aMediaTools::setSelecting($after, $multiple, $ids, $options); 
     60    aMediaTools::setSelecting($after, $multiple, $selection, $options); 
    6461       
    6562    return $this->redirect("aMedia/index"); 
     
    113110    $aspectWidth = floor(aMediaTools::getAttribute('aspect-width')); 
    114111    $aspectHeight = floor(aMediaTools::getAttribute('aspect-height')); 
    115     // TODO: performance of these is not awesome (it's a linear search).  
    116     // It would be more awesome with the right kind of indexing. For the  
    117     // aspect ratio test to be more efficient we'd have to store the lowest  
    118     // common denominator aspect ratio and index that. 
    119     if ($aspectWidth && $aspectHeight) 
    120     { 
    121       $params['aspect-width'] = $aspectWidth; 
    122       $params['aspect-height'] = $aspectHeight; 
    123     } 
    124  
    125     $minimumWidth = floor(aMediaTools::getAttribute('minimum-width')); 
    126     if ($minimumWidth) 
    127     { 
     112 
     113    if ($type === 'image') 
     114    { 
     115      // Now that we provide cropping tools, width and height should only exclude images 
     116      // that are too small to ever be cropped to that size 
     117      $minimumWidth = floor(aMediaTools::getAttribute('minimum-width')); 
     118      $width = floor(aMediaTools::getAttribute('width')); 
     119      $minimumWidth = max($minimumWidth, $width); 
     120      $minimumHeight = floor(aMediaTools::getAttribute('minimum-height')); 
     121      $height = floor(aMediaTools::getAttribute('height')); 
     122      $minimumHeight = max($minimumHeight, $height); 
     123      // Careful, aspect ratio can impose a bound on the other dimension 
     124      if ($minimumWidth && $aspectWidth) 
     125      { 
     126        $minimumHeight = max($minimumHeight, $minimumWidth * $aspectHeight / $aspectWidth); 
     127      } 
     128      if ($minimumHeight && $aspectHeight) 
     129      { 
     130        $minimumWidth = max($minimumWidth, $minimumHeight * $aspectWidth / $aspectHeight); 
     131      } 
     132      // We've updated these with implicit constraints from the aspect ratio, the width and height params, etc. 
     133      aMediaTools::setAttribute('minimum-width', $minimumWidth); 
     134      aMediaTools::setAttribute('minimum-height', $minimumHeight); 
    128135      $params['minimum-width'] = $minimumWidth; 
    129     } 
    130     $minimumHeight = floor(aMediaTools::getAttribute('minimum-height')); 
    131     if ($minimumHeight) 
    132     { 
    133136      $params['minimum-height'] = $minimumHeight; 
    134137    } 
    135     $width = floor(aMediaTools::getAttribute('width')); 
    136     if ($width) 
    137     { 
    138       $params['width'] = $width; 
    139     } 
    140     $height = floor(aMediaTools::getAttribute('height')); 
    141     if ($height) 
    142     { 
    143       $params['height'] = $height; 
    144     } 
    145  
     138    else 
     139    { 
     140      // TODO: performance of these is not awesome (it's a linear search).  
     141      // It would be more awesome with the right kind of indexing. For the  
     142      // aspect ratio test to be more efficient we'd have to store the lowest  
     143      // common denominator aspect ratio and index that. 
     144      if ($aspectWidth && $aspectHeight) 
     145      { 
     146        $params['aspect-width'] = $aspectWidth; 
     147        $params['aspect-height'] = $aspectHeight; 
     148      } 
     149 
     150      $minimumWidth = floor(aMediaTools::getAttribute('minimum-width')); 
     151      if ($minimumWidth) 
     152      { 
     153        $params['minimum-width'] = $minimumWidth; 
     154      } 
     155      $minimumHeight = floor(aMediaTools::getAttribute('minimum-height')); 
     156      if ($minimumHeight) 
     157      { 
     158        $params['minimum-height'] = $minimumHeight; 
     159      } 
     160      $width = floor(aMediaTools::getAttribute('width')); 
     161      if ($width) 
     162      { 
     163        $params['width'] = $width; 
     164      } 
     165      $height = floor(aMediaTools::getAttribute('height')); 
     166      if ($height) 
     167      { 
     168        $params['height'] = $height; 
     169      } 
     170    } 
     171     
    146172    // The media module is now an engine module. There is always a page, and that 
    147173    // page might have a restricted set of categories associated with it 
     
    162188    $this->pager->init(); 
    163189    $this->results = $this->pager->getResults(); 
     190 
    164191    aMediaTools::setSearchParameters( 
    165192      array("tag" => $tag, "type" => $type,  
     
    175202        $this->label = aMediaTools::getAttribute("label"); 
    176203      } 
    177       $this->limitSizes = false; 
    178       if ($aspectWidth || $aspectHeight || $minimumWidth || $minimumHeight || 
    179         $width || $height) 
    180       { 
    181         $this->limitSizes = true; 
    182       } 
     204      $this->limitSizes = ($minimumWidth || $minimumHeight); 
    183205    } 
    184206  } 
     
    215237      $parameters)); 
    216238  } 
    217  
     239   
     240  // Accept and store cropping information for a particular image which must already be part of the selection 
     241  public function executeCrop(sfRequest $request) 
     242  { 
     243    $selection = aMediaTools::getSelection(); 
     244    $id = $request->getParameter('id'); 
     245    $index = array_search($id, $selection); 
     246    if ($index === false) 
     247    { 
     248      error_log("ID is $id and not found in index which is " . implode(',', $selection)); 
     249      $this->forward404(); 
     250    } 
     251    $cropLeft = floor($request->getParameter('cropLeft')); 
     252    $cropTop = floor($request->getParameter('cropTop')); 
     253    $cropWidth = floor($request->getParameter('cropWidth')); 
     254    $cropHeight = floor($request->getParameter('cropHeight')); 
     255    $width = floor($request->getParameter('width')); 
     256    $height = floor($request->getParameter('height')); 
     257    $imageInfo = aMediaTools::getAttribute('imageInfo'); 
     258    $imageInfo[$id]['cropLeft'] = $cropLeft; 
     259    $imageInfo[$id]['cropTop'] = $cropTop; 
     260    $imageInfo[$id]['cropWidth'] = $cropWidth; 
     261    $imageInfo[$id]['cropHeight'] = $cropHeight; 
     262    $imageInfo[$id]['width'] = $width; 
     263    $imageInfo[$id]['height'] = $height; 
     264    aMediaTools::setAttribute('imageInfo', $imageInfo); 
     265  } 
     266   
    218267  public function executeMultipleAdd(sfRequest $request) 
    219268  { 
    220     $this->forward404Unless(aMediaTools::isMultiple()); 
    221269    $id = $request->getParameter('id') + 0; 
    222270    $item = Doctrine::getTable("aMediaItem")->find($id); 
    223271    $this->forward404Unless($item);  
    224272    $selection = aMediaTools::getSelection(); 
    225     $index = array_search($id, $selection); 
    226     // One occurrence each. If this changes we'll have to rethink 
    227     // the way reordering and deletion work (probably go by index). 
    228     if ($index === false) 
    229     { 
    230       $selection[] = $id; 
     273    if (!aMediaTools::isMultiple()) 
     274    { 
     275      $selection = array($id); 
     276    } 
     277    else 
     278    { 
     279      $index = array_search($id, $selection); 
     280      // One occurrence each. If this changes we'll have to rethink 
     281      // the way reordering and deletion work (probably go by index). 
     282      if ($index === false) 
     283      { 
     284        $selection[] = $id; 
     285      } 
    231286    } 
    232287    aMediaTools::setSelection($selection); 
     288    $imageInfo = aMediaTools::getAttribute('imageInfo'); 
     289    // Make no attempt to scrub out a previous crop, which could be handy 
     290    $imageInfo[$id]['width'] = $item->getWidth(); 
     291    $imageInfo[$id]['height'] = $item->getHeight(); 
     292    aMediaTools::setAttribute('imageInfo', $imageInfo); 
     293    // If no previous crop info is set, we must set an intial cropping mask 
     294    // so that the cropped media item id gets linked instead of the original 
     295    // media item. This is a little dangerous because JavaScript computes an 
     296    // intial crop mask on the client side. 
     297    aMediaTools::setDefaultCropDimensions($item); 
     298    if ((!aMediaTools::isMultiple()) && aMediaTools::getAttribute('type') !== 'image') 
     299    { 
     300      return $this->redirect('aMedia/selected'); 
     301    } 
    233302    return $this->renderComponent("aMedia", "multipleList"); 
    234303  } 
     
    236305  public function executeMultipleRemove(sfRequest $request) 
    237306  { 
    238     $this->forward404Unless(aMediaTools::isMultiple()); 
    239307    $id = $request->getParameter('id'); 
    240308    $item = Doctrine::getTable("aMediaItem")->find($id); 
     
    250318  } 
    251319 
     320  public function executeUpdateMultiplePreview(sfRequest $request) 
     321  { 
     322    return $this->renderComponent('aMedia', 'multiplePreview'); 
     323  } 
     324   
    252325  public function executeMultipleOrder(sfRequest $request) 
    253326  { 
    254327    $this->logMessage("*****MULTIPLE ORDER", "info"); 
    255328    $order = $request->getParameter('a-media-selection-list-item'); 
    256     $oldSelection = aMediaTools::getSelection(); 
     329    $oldSelection = aMediaTools::getSelection();     
    257330    $keys = array_flip($oldSelection); 
    258331    $selection = array(); 
     
    269342      $this->logMessage(">>>KEEPING " . $item->getId(), "info"); 
    270343    } 
    271     $this->logMessage(">>>SUCCEEDED: " . implode(", ", $selection), "info"); 
     344    $this->logMessage(">>>SUCCEEDED: " . implode(", ", $selection), "info");     
    272345    aMediaTools::setSelection($selection); 
    273346    return $this->renderComponent("aMedia", "multipleList"); 
    274347  } 
     348   
    275349  public function executeSelected(sfRequest $request) 
    276350  { 
    277     $controller = $this->getController(); 
    278351    $this->forward404Unless(aMediaTools::isSelecting()); 
    279     if (aMediaTools::isMultiple()) 
    280     { 
    281       $selection = aMediaTools::getSelection(); 
    282       // Ooops best to get this before clearing it huh 
    283       $after = aMediaTools::getAfter(); 
    284       // Oops I forgot to call this in the multiple case 
     352    $selection = aMediaTools::getSelection(); 
     353    $imageInfo = aMediaTools::getAttribute('imageInfo'); 
     354    // Get all the items in preparation for possible cropping 
     355    if (count($selection)) 
     356    { 
     357      $items = Doctrine::getTable('aMediaItem')->createQuery('m')->whereIn('m.id', $selection)->execute(); 
     358    } 
     359    else 
     360    { 
     361      $items = array(); 
     362    } 
     363    $items = aArray::listToHashById($items); 
     364    $newSelection = array(); 
     365    foreach ($selection as $id) 
     366    { 
     367      if (isset($imageInfo[$id]['cropLeft'])) 
     368      { 
     369        // We need to make a crop 
     370        $item = $items[$id]; 
     371        $crop = $item->findOrCreateCrop($imageInfo[$id]); 
     372        $crop->save(); 
     373        $newSelection[] = $crop->id; 
     374      } 
     375      else 
     376      { 
     377        $newSelection[] = $id; 
     378      } 
     379    } 
     380    // Ooops best to get this before clearing it huh 
     381    $after = aMediaTools::getAfter(); 
     382     
     383    // addParamsNoDelete never attempts to eliminate a field just because 
     384    // its value is empty. This is how we distinguish between cancellation 
     385    // and selecting zero items 
     386     
     387    if (!aMediaTools::isMultiple()) 
     388    { 
     389      // Call this too soon and you lose isMultiple 
    285390      aMediaTools::clearSelecting(); 
    286       // I thought about submitting this like a multiple select, 
    287       // but there's no clean way to implement that feature in 
    288       // addParam, and it wastes URL space anyway 
    289       // (remember the 1024-byte limit) 
    290        
    291       // addParamsNoDelete never attempts to eliminate a field just because 
    292       // its value is empty. This is how we distinguish between cancellation 
    293       // and selecting zero items 
    294       return $this->redirect( 
    295         aUrl::addParamsNoDelete($after, 
    296         array("aMediaIds" => implode(",", $selection)))); 
    297     } 
    298     // Single select 
    299     $id = $request->getParameter('id'); 
    300     $item = Doctrine::getTable("aMediaItem")->find($id); 
    301     $this->forward404Unless($item);  
    302     $after = aMediaTools::getAfter(); 
    303     $after = aUrl::addParams($after,  
    304       array("aMediaId" => $id)); 
    305     aMediaTools::clearSelecting(); 
    306     return $this->redirect($after); 
     391      if (count($newSelection)) 
     392      { 
     393        $after = aUrl::addParams($after,  
     394          array("aMediaId" => $newSelection[0])); 
     395        return $this->redirect($after); 
     396      } 
     397      else 
     398      { 
     399        $this->forward404(); 
     400      } 
     401    } 
     402    else 
     403    { 
     404      aMediaTools::clearSelecting(); 
     405      $url = aUrl::addParamsNoDelete($after, 
     406      array("aMediaIds" => implode(",", $newSelection))); 
     407      return $this->redirect($url); 
     408    } 
    307409  } 
    308410 
  • plugins/apostrophePlugin/trunk/lib/action/BaseaMediaBackendActions.class.php

    r1255 r1917  
    3939      array_keys(aMediaItemTable::$mimeTypes))); 
    4040    $this->forward404Unless(($resizeType !== 'c') || ($resizeType !== 's')); 
    41     $output = $this->getDirectory() .  
    42       DIRECTORY_SEPARATOR . "$slug.$width.$height.$resizeType.$format"; 
     41    // EDITED FOR ARBITRARY CROPPING 
     42    $cropLeft = $request->getParameter('cropLeft'); 
     43    $cropTop = $request->getParameter('cropTop'); 
     44    $cropWidth = $request->getParameter('cropWidth'); 
     45    $cropHeight = $request->getParameter('cropHeight'); 
     46     
     47    if (!is_null($cropWidth) && !is_null($cropHeight) && !is_null($cropLeft) && !is_null($cropTop)) 
     48    { 
     49      $cropLeft = ceil($cropLeft + 0); 
     50      $cropTop = ceil($cropTop + 0); 
     51      $cropWidth = ceil($cropWidth + 0); 
     52      $cropHeight = ceil($cropHeight + 0); 
     53      // Explicit cropping always preempts any automatic cropping, so there's no difference between c and s, 
     54      // and only the cropOriginal method actually supports cropping parameters, so 
     55      $resizeType = 'c'; 
     56       
     57      $output = $this->getDirectory() .  
     58        DIRECTORY_SEPARATOR . "$slug.$cropLeft.$cropTop.$cropWidth.$cropHeight.$width.$height.$resizeType.$format";       
     59    } 
     60    else 
     61    { 
     62      $cropLeft = null; 
     63      $cropTop = null; 
     64      $cropWidth = null; 
     65      $cropHeight = null; 
     66      $output = $this->getDirectory() .  
     67        DIRECTORY_SEPARATOR . "$slug.$width.$height.$resizeType.$format"; 
     68    } 
     69 
    4370    // If .htaccess has not been set up, or we are not running 
    4471    // from the default front controller, then we may get here 
     
    5784        $method = 'scaleToFit'; 
    5885      } 
    59       $quality = sfConfig::get('app_aMedia_jpeg_quality', 75); 
    6086      aImageConverter::$method( 
    6187        aMediaItemTable::getDirectory() . 
     
    6591        $width, 
    6692        $height, 
    67         sfConfig::get('app_aMedia_jpeg_quality', 75)); 
     93        sfConfig::get('app_aMedia_jpeg_quality', 75), 
     94        $cropLeft, 
     95        $cropTop, 
     96        $cropWidth, 
     97        $cropHeight); 
    6898    } 
    6999    // The FIRST time, we output this here. Later it 
  • plugins/apostrophePlugin/trunk/lib/action/BaseaMediaComponents.class.php

    r448 r1917  
    110110  } 
    111111 
     112  public function executeSelectMultiple($request) 
     113  { 
     114    $this->items = aMediaTools::getSelectedItems(); 
     115  } 
     116 
     117  public function executeSelectSingle($request) 
     118  { 
     119    $this->items = aMediaTools::getSelectedItems(); 
     120  } 
     121 
    112122  public function executeMultipleList($request) 
    113123  { 
    114     if (!aMediaTools::isMultiple()) 
    115     { 
    116       throw new Exception("multiple list component, but multiple is off");  
    117     } 
    118     $selection = aMediaTools::getSelection(); 
    119     if (!is_array($selection)) 
    120     { 
    121       throw new Exception("selection is not an array"); 
    122     } 
    123     // Work around the fact that whereIn doesn't evaluate to AND FALSE 
    124     // when the array is empty (it just does nothing; which is an 
    125     // interesting variation on MySQL giving you an ERROR when the  
    126     // list is empty, sigh) 
    127     if (count($selection)) 
    128     { 
    129       // Work around the unsorted results of whereIn. You can also 
    130       // do that with a FIELD function 
    131       $unsortedItems = Doctrine_Query::create()-> 
    132         from('aMediaItem i')-> 
    133         whereIn('i.id', $selection)-> 
    134         execute(); 
    135       $itemsById = array(); 
    136       foreach ($unsortedItems as $item) 
    137       { 
    138         $itemsById[$item->getId()] = $item; 
    139       } 
    140       $this->items = array(); 
    141       foreach ($selection as $id) 
    142       { 
    143         if (isset($itemsById[$id])) 
    144         { 
    145           $this->items[] = $itemsById[$id]; 
    146         } 
    147       } 
    148     } 
    149     else 
    150     { 
    151       $this->items = array(); 
    152     } 
     124    $this->items = aMediaTools::getSelectedItems(); 
     125  } 
     126   
     127  public function executeMultiplePreview() 
     128  { 
     129    $this->items = aMediaTools::getSelectedItems(); 
    153130  } 
    154131} 
  • plugins/apostrophePlugin/trunk/lib/action/BaseaSlideshowSlotComponents.class.php

    r1449 r1917  
    3232      } 
    3333      $this->itemIds = aArray::getIds($this->items); 
    34       foreach ($this->items as $item) 
    35       { 
    36         $this->itemIds[] = $item->id; 
    37       } 
    3834      if ($this->getOption('random', false)) 
    3935      { 
  • plugins/apostrophePlugin/trunk/lib/model/doctrine/PluginaMediaItem.class.php

    r1701 r1917  
    2020    } 
    2121    // Let the culture be the user's culture 
    22     return aZendSearch::saveInDoctrineAndLucene($this, null, $conn); 
     22    $result = aZendSearch::saveInDoctrineAndLucene($this, null, $conn); 
     23    $crops = $this->getCrops(); 
     24    foreach ($crops as $crop) 
     25    { 
     26      $crop->setTitle($this->getTitle()); 
     27      $crop->setDescription($this->getDescription()); 
     28      $crop->setCredit($this->getCredit()); 
     29      $crop->save(); 
     30    } 
     31    return $result; 
    2332  } 
    2433 
     
    3342    $ret = aZendSearch::deleteFromDoctrineAndLucene($this, null, $conn); 
    3443    $this->clearImageCache(); 
     44     
     45    $this->deleteCrops(); 
     46     
    3547    // Don't even think about trashing the original until we know 
    3648    // it's gone from the db and so forth 
     
    132144    $path = $this->getOriginalPath($this->getFormat()); 
    133145    $result = copy($file, $path); 
     146    // Crops are invalid if you replace the original image 
     147    $this->deleteCrops(); 
    134148    return $result; 
    135149  } 
     
    249263    $options = aDimensions::constrain($this->getWidth(), $this->getHeight(), $this->getFormat(), $options); 
    250264 
    251     return "aMediaBackend/image?" . http_build_query( 
    252       array("slug" => $this->slug, "width" => $options['width'], "height" => $options['height'],  
    253         "resizeType" => $options['resizeType'], "format" => $options['format'])); 
     265    $params = array("slug" => $this->slug, "width" => $options['width'], "height" => $options['height'],  
     266      "resizeType" => $options['resizeType'], "format" => $options['format']); 
     267 
     268    // check for null because 0 is valid 
     269    if (!is_null($options['cropLeft']) && !is_null($options['cropTop']) && !is_null($options['cropWidth']) && !is_null($options['cropHeight'])) 
     270    {       
     271      $params = array_merge( 
     272        $params, 
     273        array("cropLeft" => $options['cropLeft'], "cropTop" => $options['cropTop'], 
     274          "cropWidth" => $options['cropWidth'], "cropHeight" => $options['cropHeight']) 
     275      ); 
     276    } 
     277    return "aMediaBackend/image?" . http_build_query($params); 
     278  } 
     279   
     280  public function getCropThumbnailUrl() 
     281  {     
     282    $selectedConstraints = aMediaTools::getOption('selected_constraints'); 
     283     
     284    if ($aspectRatio = aMediaTools::getAspectRatio()) // this returns 0 if aspect-width and aspect-height were not set 
     285    { 
     286      $selectedConstraints = array_merge( 
     287        $selectedConstraints,  
     288        array('height' => floor($selectedConstraints['width'] / $aspectRatio)) 
     289      ); 
     290    } 
     291     
     292     
     293    $imageInfo = aMediaTools::getAttribute('imageInfo'); 
     294    if (isset($imageInfo[$this->id]['cropLeft']) && 
     295        isset($imageInfo[$this->id]['cropTop']) && isset($imageInfo[$this->id]['cropWidth']) && isset($imageInfo[$this->id]['cropHeight'])) 
     296    { 
     297      $selectedConstraints = array_merge( 
     298        $selectedConstraints,  
     299        array( 
     300          'cropLeft' => $imageInfo[$this->id]['cropLeft'], 
     301          'cropTop' => $imageInfo[$this->id]['cropTop'], 
     302          'cropWidth' => $imageInfo[$this->id]['cropWidth'], 
     303          'cropHeight' => $imageInfo[$this->id]['cropHeight'] 
     304        ) 
     305      ); 
     306    } 
     307       
     308    return $this->getScaledUrl($selectedConstraints); 
     309  } 
     310   
     311  // Crops of other images have periods in the slug. Real slugs are always [\w_]+ (well, the i18n equivalent) 
     312  public function isCrop() 
     313  { 
     314    return (strpos($this->slug, '.') !== false); 
     315  } 
     316   
     317  public function getCrops() 
     318  { 
     319    // This should perform well because there is an index on the slug and 
     320    // indexes are great with prefix queries 
     321    return $this->getTable()->createQuery('m')->where('m.slug LIKE ?', array($this->slug . '.%'))->execute(); 
     322     
     323  } 
     324   
     325  public function deleteCrops() 
     326  { 
     327    $crops = $this->getCrops(); 
     328    // Let's make darn sure the PHP stuff gets called rather than using a delete all trick of some sort 
     329    foreach ($crops as $crop) 
     330    { 
     331      $crop->delete(); 
     332    } 
     333  } 
     334   
     335  public function findOrCreateCrop($info) 
     336  { 
     337    $slug = $this->slug . '.' . $info['cropLeft'] . '.' . $info['cropTop'] . '.' . $info['cropWidth'] . '.' . $info['cropHeight']; 
     338    $crop = $this->getTable()->findOneBySlug($slug); 
     339    if (!$crop) 
     340    { 
     341      $crop = $this->copy(false); 
     342      $crop->slug = $slug; 
     343      $crop->width = $info['cropWidth']; 
     344      $crop->height = $info['cropHeight']; 
     345    } 
     346    return $crop; 
     347  } 
     348   
     349  public function getCroppingInfo() 
     350  { 
     351    $p = preg_split('/\./', $this->slug); 
     352    if (count($p) == 5) 
     353    { 
     354      return array('cropLeft' => $p[1], 'cropTop' => $p[2], 'cropWidth' => $p[3], 'cropHeight' => $p[4]); 
     355    } 
     356    else 
     357    { 
     358      return array(); 
     359    } 
     360  } 
     361   
     362  public function getCropOriginal() 
     363  { 
     364    if (!$this->isCrop()) 
     365    { 
     366      return $this; 
     367    } 
     368    $p = preg_split('/\./', $this->slug); 
     369    return $this->getTable()->findOneBySlug($p[0]); 
    254370  } 
    255371} 
  • plugins/apostrophePlugin/trunk/lib/model/doctrine/PluginaMediaItemTable.class.php

    r846 r1917  
    164164      $query->andWhere('aMediaItem.height = ?', array($params['height'] + 0)); 
    165165    } 
     166    // No crops in the browser please 
     167    $query->andWhere("aMediaItem.slug NOT LIKE '%.%'"); 
     168     
    166169    return $query; 
    167170  } 
  • plugins/apostrophePlugin/trunk/lib/toolkit/aDimensions.class.php

    r9 r1917  
    2424    } 
    2525    $resizeType = $options['resizeType']; 
     26    $cropLeft = isset($options['cropLeft']) ? $options['cropLeft'] : null; 
     27    $cropTop = isset($options['cropTop']) ? $options['cropTop'] : null; 
     28    $cropWidth = isset($options['cropWidth']) ? $options['cropWidth'] : null; 
     29    $cropHeight = isset($options['cropHeight']) ? $options['cropHeight'] : null; 
     30     
     31    if (isset($options['scaleWidth']) && isset($options['scaleHeight']) && !is_null($cropLeft) && !is_null($cropTop) && !is_null($cropWidth) && !is_null($cropHeight)) 
     32    { 
     33      $scalingFactor =  $originalWidth / $options['scaleWidth']; 
     34             
     35      $cropLeft = floor($scalingFactor * $cropLeft); 
     36      $cropTop = floor($scalingFactor * $cropTop); 
     37      $cropWidth = floor($scalingFactor * $cropWidth); 
     38      $cropHeight = floor($scalingFactor * $cropHeight); 
     39    } 
     40     
    2641    if (!(isset($options['forceScale']) && $options['forceScale'])) 
    2742    { 
     
    5469    } 
    5570     
    56     return array("width" => $width, "height" => $height, "format" => $format, "resizeType" => $resizeType); 
     71    return array("width" => $width, "height" => $height, "format" => $format, "resizeType" => $resizeType, 
     72      "cropLeft" => $cropLeft, "cropTop" => $cropTop, "cropWidth" => $cropWidth, "cropHeight" => $cropHeight); 
    5773  } 
    5874} 
  • plugins/apostrophePlugin/trunk/lib/toolkit/aDoctrine.class.php

    r268 r1917  
    1818  // Example:  
    1919  // 
    20   // $q = Doctrine::getTable('aMediaItem')->createQuery('m')->select('m.*'->whereIn('m.id', $ids); 
     20  // $q = Doctrine::getTable('aMediaItem')->createQuery('m')->select('m.*')->whereIn('m.id', $ids); 
    2121  // $mediaItems = aDoctrine::orderByList($q, $ids)->execute(); 
    2222   
  • plugins/apostrophePlugin/trunk/lib/toolkit/aImageConverter.class.php

    r1872 r1917  
    5555  } 
    5656 
    57   static public function cropOriginal($fileIn, $fileOut, $width, $height, $quality = 75) 
    58   { 
     57  // $width and $height are the dimensions of the final rendered image. $quality is the JPEG quality setting (where needed). 
     58  // The $crop parameters, when not null (all four must be null or not null), are used to crop the original before scaling/distorting  
     59  // to the specified width and height and are always in the original image's coordinates. 
     60   
     61  // If cropping coordinates are not specified, the largest possible portion of the center of the original image is scaled to fit into the  
     62  // destination image without distortion 
     63   
     64  static public function cropOriginal($fileIn, $fileOut, $width, $height, $quality = 75, $cropLeft = null, $cropTop = null, $cropWidth = null,  $cropHeight = null) 
     65  { 
     66    // Allow skipping of parameters 
     67    if (is_null($quality)) 
     68    { 
     69      $quality = 75; 
     70    } 
    5971    $width = ceil($width); 
    6072    $height = ceil($height); 
     
    6779    $iratio = $iwidth / $iheight; 
    6880    $ratio = $width / $height; 
     81 
     82     // Spike's contribution: arbitrary cropping 
     83     if (!is_null($cropWidth) && !is_null($cropHeight) && !is_null($cropLeft) && !is_null($cropTop)) 
     84     { 
     85       $cropTop = ceil($cropTop + 0); 
     86       $cropLeft = ceil($cropLeft + 0); 
     87       $cropWidth = ceil($cropWidth + 0); 
     88       $cropHeight = ceil($cropHeight + 0); 
     89        
     90       $scale = array('xysize' => array($width + 0, $height + 0)); 
     91       $crop = array('left' => $cropLeft, 'top' => $cropTop, 'width' => $cropWidth, 'height' => $cropHeight); 
     92       return self::scaleBody($fileIn, $fileOut, $scale, $crop, $quality); 
     93     } 
    6994 
    7095    $scale = array('xysize' => array($width + 0, $height + 0)); 
     
    356381        $width = $scaleParameters['xysize'][0]; 
    357382        $height = $scaleParameters['xysize'][1]; 
    358          
     383        // This was backwards until 05/31/2010, making things bigger rather than smaller if their 
     384        // aspect ratios differed from the original. Be consistent with netpbm which makes things 
     385        // smaller not bigger 
    359386        if (($width / $height) > ($swidth / $sheight)) 
    360387        { 
    361           // Wider than the original. So it will be shorter than requested 
    362           $height = ceil($width * ($sheight / $swidth)); 
     388          // Wider than the original. So it will be narrower than requested 
     389          $width = ceil($height * ($swidth / $sheight)); 
    363390        } 
    364391        else 
    365392        { 
    366           // Taller than the original. So it will be narrower than requested 
    367           $width = ceil($height * ($swidth / $sheight)); 
     393          // Taller than the original. So it will be shorter than requested 
     394          $height = ceil($width * ($sheight / $swidth)); 
    368395        } 
    369396        $out = self::createTrueColorAlpha($width, $height); 
  • plugins/apostrophePlugin/trunk/lib/toolkit/aImageConverterTest.php

    r9 r1917  
    11<?php 
    22 
     3class sfConfig 
     4{ 
     5  static public function get($key, $default) 
     6  { 
     7    return $default; 
     8  } 
     9} 
    310require 'aImageConverter.class.php'; 
    411 
    512// aImageConverter::scaleToFit("testin.jpg", "testoutscaletofit.jpg", 400, 300); 
    613// aImageConverter::scaleByFactor("testin.jpg", "testoutscalebyfactor.jpg", 0.5); 
    7 aImageConverter::scaleToFit("testin.jpg", "testoutscaleoriginalbobbi.jpg", 340, 451); 
     14// aImageConverter::scaleToFit("testin.jpg", "testoutscaleoriginalbobbi.jpg", 340, 451); 
    815// aImageConverter::cropOriginal("testin.jpg", "testoutcroporiginaltall.jpg", 100, 300); 
    916// aImageConverter::scaleToNarrowerAxis("testin.jpg", "testoutscaletonarroweraxis.jpg", 300, 200); 
     17// aImageConverter::cropOriginal("testin.jpg", "testoutcroporiginaltall.jpg", 100, 300); 
     18aImageConverter::cropOriginal("testin.jpg", "testoutcroporiginalcorner.jpg", 100, 100, null, 200, 200, 200, 200); 
  • plugins/apostrophePlugin/trunk/lib/toolkit/aMediaAPI.php

    r9 r1917  
    11<?php 
    22 
    3 // Conveniences for Symfony code that uses the API. 
     3// DEPRECATED: based on the old REST API, which we do not enable by default and found 
     4// to be a serious maintenance hassle as our client projects simply don't call for 
     5// shared media repositories. 
     6 
     7// Conveniences for Symfony code that uses the REST API. 
    48 
    59class aMediaAPI 
  • plugins/apostrophePlugin/trunk/lib/toolkit/aMediaRouting.php

    r883 r1917  
    2626        'action' => 'original' 
    2727      ), array('slug' => '^' . aTools::getSlugRegexpFragment() . '$', 'format' => '^(jpg|png|gif|pdf)$'))); 
    28        
     28 
    2929      $route = new sfRoute('/uploads/media_items/:slug.:width.:height.:resizeType.:format', array( 
    3030        'module' => 'aMediaBackend', 
     
    3838      )); 
    3939      $r->prependRoute('a_media_image', $route); 
     40 
     41      $route = new sfRoute('/uploads/media_items/:slug.:cropLeft.:cropTop.:cropWidth.:cropHeight.:width.:height.:resizeType.:format', array( 
     42        'module' => 'aMediaBackend', 
     43        'action' => 'image' 
     44      ), array( 
     45        'slug' => '^' . aTools::getSlugRegexpFragment() . '$', 
     46        'width' => '^\d+$', 
     47        'height' => '^\d+$', 
     48        'cropLeft' => '^\d+$', 
     49        'cropTop' => '^\d+$', 
     50        'cropWidth' => '^\d+$', 
     51        'cropHeight' => '^\d+$', 
     52        'resizeType' => '^\w$', 
     53        'format' => '^(jpg|png|gif)$' 
     54      )); 
     55      $r->prependRoute('a_media_image_cropped', $route); 
    4056       
    4157      // What we want: 
  • plugins/apostrophePlugin/trunk/lib/toolkit/aMediaTools.php

    r883 r1917  
    33class aMediaTools 
    44{ 
    5   // These are used internally. If you want to select something 
    6   // with the media module please see the README, do not use these  
    7   // methods directly 
     5  // These are used internally. See aMediaSelect for the methods you probably want 
    86 
    97  static public function setSelecting($after, $multiple, $selection,  
    108    $options = array()) 
    119  { 
     10    $items = aMediaItemTable::retrieveByIds($selection); 
     11    $ids = array(); 
     12    $imageInfo = array(); 
     13    $selection = array(); 
     14    foreach ($items as $item) 
     15    { 
     16      $croppingInfo = array(); 
     17      if ($item->isCrop()) 
     18      { 
     19        $croppingInfo = $item->getCroppingInfo(); 
     20        $item = $item->getCropOriginal(); 
     21      } 
     22      $id = $item->id; 
     23      $selection[] = $id; 
     24      $info = array('width' => $item->width, 'height' => $item->height); 
     25      $info = array_merge($info, $croppingInfo); 
     26      $imageInfo[$item->id] = $info; 
     27    } 
     28     
     29    $cropping = isset($options['cropping']) && $options['cropping']; 
     30 
    1231    self::clearSelecting(); 
    1332    self::setAttribute("selecting", true); 
    1433    self::setAttribute("after", $after); 
    1534    self::setAttribute("multiple", $multiple); 
     35    self::setAttribute("cropping", $cropping); 
    1636    self::setAttribute("selection", $selection); 
     37    self::setAttribute("imageInfo", $imageInfo); 
    1738    foreach ($options as $key => $val) 
    1839    { 
     
    140161    "selected_constraints" => array( 
    141162        "width" => 100, 
    142         "height" => 75, 
    143         "resizeType" => "c"), 
     163        "height" => false, 
     164        "resizeType" => "c",), 
    144165    "show_constraints" => array( 
    145166        "width" => 720, 
    146167        "height" => false, 
     168        "resizeType" => "s"), 
     169    "crop_constraints" => array( 
     170        "width" => 679, 
     171        "height" => 400, 
    147172        "resizeType" => "s"), 
    148173    'routes_register' => true, 
     
    188213    return $item; 
    189214  } 
     215   
     216  // refactored this into this static method from executeMultipleList() because it is now needed 
     217  // for executeUpdateMultiplePreview() for cropping slideshow items 
     218  static public function getSelectedItems() 
     219  { 
     220    $selection = self::getSelection(); 
     221    if (!is_array($selection)) 
     222    { 
     223      throw new Exception("selection is not an array"); 
     224    } 
     225    // Work around the fact that whereIn doesn't evaluate to AND FALSE 
     226    // when the array is empty (it just does nothing; which is an 
     227    // interesting variation on MySQL giving you an ERROR when the  
     228    // list is empty, sigh) 
     229    if (count($selection)) 
     230    { 
     231      // Work around the unsorted results of whereIn. You can also 
     232      // do that with a FIELD function 
     233      $unsortedItems = Doctrine_Query::create()-> 
     234        from('aMediaItem i')-> 
     235        whereIn('i.id', $selection)-> 
     236        execute(); 
     237      $itemsById = array(); 
     238      foreach ($unsortedItems as $item) 
     239      { 
     240        $itemsById[$item->getId()] = $item; 
     241      } 
     242      $items = array(); 
     243      foreach ($selection as $id) 
     244      { 
     245        if (isset($itemsById[$id])) 
     246        { 
     247          $items[] = $itemsById[$id]; 
     248        } 
     249      } 
     250    } 
     251    else 
     252    { 
     253      $items = array(); 
     254    } 
     255     
     256    return $items; 
     257  } 
     258   
     259  static public function getAspectRatio() 
     260  { 
     261    if (self::getAttribute('aspect-width') && self::getAttribute('aspect-width')) 
     262    { 
     263      return self::getAttribute('aspect-width') / self::getAttribute('aspect-height'); 
     264    } 
     265    return 0; 
     266  } 
     267   
     268  static public function getSelectedThumbnailHeight() 
     269  { 
     270    $selectedConstraints = self::getOption('selected_constraints'); 
     271    if (false === $selectedConstraints['height']) 
     272    { 
     273      if ($aspectRatio = self::getAspectRatio()) 
     274      { 
     275        return $selectedConstraints['width'] / $aspectRatio; 
     276      } 
     277      return 0; // Let's not divide by zero. 
     278    } 
     279    return $selectedConstraints['height']; 
     280  } 
     281   
     282  /** 
     283   * This mirrors the default size math in aCrop.setAspectMask() in aCrop.js 
     284   */ 
     285  static public function setDefaultCropDimensions($mediaItem) 
     286  { 
     287    $imageInfo = self::getAttribute('imageInfo'); 
     288    $aspectRatio = self::getAspectRatio(); 
     289     
     290    if ($aspectRatio) 
     291    {     
     292      if ($aspectRatio > 1) 
     293      { 
     294        $imageInfo[$mediaItem->id]['cropWidth'] = $mediaItem->getWidth(); 
     295        $imageInfo[$mediaItem->id]['cropHeight'] = floor($mediaItem->getWidth() / $aspectRatio); 
     296      } 
     297      else 
     298      { 
     299        $imageInfo[$mediaItem->id]['cropHeight'] = $mediaItem->getHeight(); 
     300        $imageInfo[$mediaItem->id]['cropWidth'] = floor($mediaItem->getHeight() * $aspectRatio); 
     301      } 
     302    } 
     303    else 
     304    { 
     305      $imageInfo[$mediaItem->id]['cropWidth'] = $mediaItem->getWidth(); 
     306      $imageInfo[$mediaItem->id]['cropHeight'] = $mediaItem->getHeight(); 
     307    } 
     308     
     309    $imageInfo[$mediaItem->id]['cropLeft'] = 0; 
     310    $imageInfo[$mediaItem->id]['cropTop'] = floor(($mediaItem->getHeight() - $imageInfo[$mediaItem->id]['cropHeight']) / 2); 
     311         
     312    self::setAttribute('imageInfo', $imageInfo); 
     313  } 
    190314} 
  • plugins/apostrophePlugin/trunk/modules/aImageSlot/templates/_normalView.php

    r1914 r1917  
    1616  $slug = isset($slug) ? $sf_data->getRaw('slug') : null; 
    1717  $title = isset($title) ? $sf_data->getRaw('title') : null; 
     18  $embed = isset($embed) ? $sf_data->getRaw('embed') : null; 
    1819?> 
    1920<?php use_helper('I18N') ?> 
  • plugins/apostrophePlugin/trunk/modules/aMedia/templates/_browser.php

    r1914 r1917  
    1111?> 
    1212<?php use_helper('I18N') ?> 
     13<?php // Media is now an engine, so there's a page ?> 
     14<?php $page = aTools::getCurrentPage() ?> 
    1315   
    1416<?php // Entire media browser goes into what would otherwise be the regular apostrophe subnav ?> 
    1517<?php slot('a-subnav') ?> 
    16  
    17 <?php // Media is now an engine, so there's a page ?> 
    18 <?php $page = aTools::getCurrentPage() ?> 
    1918 
    2019<?php // For backwards compatibility reasons it is best to implement these as before and after partials ?> 
  • plugins/apostrophePlugin/trunk/modules/aMedia/templates/_describeConstraints.php

    r1655 r1917  
    11<?php // Yes, this is template code, but we use regular PHP syntax because we are building a sentence and the introduction of ?> 
    2 <?php // newlines wrecks the punctuation. ?> 
    3 <?php  
    4 use_helper('I18N'); 
    5 $clauses = array(); 
    6 if (aMediaTools::getAttribute('aspect-width') && aMediaTools::getAttribute('aspect-height')) 
    7 { 
    8   $clauses[] = __('A %w%x%h% aspect ratio', array('%w%' => aMediaTools::getAttribute('aspect-width'), '%h%' => aMediaTools::getAttribute('aspect-height')), 'apostrophe'); 
    9 } 
    10 if (aMediaTools::getAttribute('minimum-width')) 
    11 { 
    12   $clauses[] = __('A minimum width of %mw% pixels', array('%mw%' => aMediaTools::getAttribute('minimum-width')), 'apostrophe'); 
    13 } 
    14 if (aMediaTools::getAttribute('minimum-height')) 
    15 { 
    16   $clauses[] = __('A minimum height of %mh% pixels', array('%mh%' => aMediaTools::getAttribute('minimum-height')), 'apostrophe'); 
    17 } 
    18 if (aMediaTools::getAttribute('width')) 
    19 { 
    20   $clauses[] = __('A width of exactly %w% pixels', array('%w%' => aMediaTools::getAttribute('width')), 'apostrophe'); 
    21 } 
    22 if (aMediaTools::getAttribute('height')) 
    23 { 
    24   $clauses[] = __('A height of exactly %h% pixels', array('%h%' => aMediaTools::getAttribute('height')), 'apostrophe'); 
    25 } 
    26 if (aMediaTools::getAttribute('type')) 
    27 { 
    28   // Internationalize the plural so that can be correct too 
    29   $type = __(aMediaTools::getAttribute('type') . "s", null, 'apostrophe'); 
    30 }  
    31 else 
    32 { 
    33   $type = __("items", null, 'apostrophe'); 
    34 } 
    35 if (count($clauses)) 
    36 { 
    37   // Markup change: for I18N it's better to use a list here rather than 
    38   // trying to create a sentence with commas and 'and' 
    39   echo('<h3 class="a-constraints-description">' . __("Displaying only %t% with:", array('%t%' => $type), 'apostrophe') . '</h3>'); 
    40   echo('<ul class="a-constraints">'); 
    41   foreach ($clauses as $clause) 
     2<?php // newlines wrecks the punctuation. (OK, we're building a ul list now...) ?> 
     3<?php use_helper('I18N') ?> 
     4<?php // Images support cropping, which makes some of the constraints unnecessary to display ?> 
     5<?php // With other media types we must find an item that satisfies all of them. ?> 
     6<?php if (aMediaTools::getAttribute('type') === 'image'): ?> 
     7  <?php // We went with something simpler when cropping is present ?> 
     8        <?php if ($limitSizes): ?> 
     9        <h4 class="a-help"><?php echo __('Some images in your media library may not be large enough to be selected. Only images that can be used are displayed below.', null, 'apostrophe') ?></h4> 
     10        <?php endif ?> 
     11<?php else: ?> 
     12  <?php // No cropping, their only hope is to get proper details from us on what is allowed ?> 
     13  <?php 
     14  $clauses = array(); 
     15  if (aMediaTools::getAttribute('aspect-width') && aMediaTools::getAttribute('aspect-height')) 
    4216  { 
    43     echo('<li>' . $clause . '</li>'); 
     17    $clauses[] = __('A %w%x%h% aspect ratio', array('%w%' => aMediaTools::getAttribute('aspect-width'), '%h%' => aMediaTools::getAttribute('aspect-height')), 'apostrophe'); 
    4418  } 
    45   echo('</ul>'); 
    46 } 
     19  if (aMediaTools::getAttribute('minimum-width')) 
     20  { 
     21    $clauses[] = __('A minimum width of %mw% pixels', array('%mw%' => aMediaTools::getAttribute('minimum-width')), 'apostrophe'); 
     22  } 
     23  if (aMediaTools::getAttribute('minimum-height')) 
     24  { 
     25    $clauses[] = __('A minimum height of %mh% pixels', array('%mh%' => aMediaTools::getAttribute('minimum-height')), 'apostrophe'); 
     26  } 
     27  if (aMediaTools::getAttribute('width')) 
     28  { 
     29    $clauses[] = __('A width of exactly %w% pixels', array('%w%' => aMediaTools::getAttribute('width')), 'apostrophe'); 
     30  } 
     31  if (aMediaTools::getAttribute('height')) 
     32  { 
     33    $clauses[] = __('A height of exactly %h% pixels', array('%h%' => aMediaTools::getAttribute('height')), 'apostrophe'); 
     34  } 
     35  if (aMediaTools::getAttribute('type')) 
     36  { 
     37    // Internationalize the plural so that can be correct too 
     38    $type = __(aMediaTools::getAttribute('type') . "s", null, 'apostrophe'); 
     39  }  
     40  else 
     41  { 
     42    $type = __("items", null, 'apostrophe'); 
     43  } 
     44  if (count($clauses)) 
     45  { 
     46    // Markup change: for I18N it's better to use a list here rather than 
     47    // trying to create a sentence with commas and 'and' 
     48    echo('<h4 class="a-constraints-description">' . __("Displaying only %t% with:", array('%t%' => $type), 'apostrophe') . '</h4>'); 
     49    echo('<ul class="a-constraints">'); 
     50    foreach ($clauses as $clause) 
     51    { 
     52      echo('<li>' . $clause . '</li>'); 
     53    } 
     54    echo('</ul>'); 
     55  } 
     56  ?> 
     57<?php endif ?> 
  • plugins/apostrophePlugin/trunk/modules/aMedia/templates/_mediaItem.php

    r1914 r1917  
    1111<?php if (aMediaTools::isSelecting()): ?> 
    1212 
    13   <?php if (aMediaTools::isMultiple()): ?> 
     13  <?php if (aMediaTools::isMultiple() || ($type === 'image')): ?> 
    1414    <?php $linkAttributes = 'href = "#" onClick="'.  
    1515      jq_remote_function(array( 
    16                                 "update" => "a-media-selection-list", 
    17                                 'complete' => "aUI('a-media-selection-list');",   
     16                        "update" => "a-media-selection-list", 
     17                        'complete' => "aUI('a-media-selection-list'); aMediaUpdatePreview();",   
    1818        "url" => "aMedia/multipleAdd?id=$id")).'; return false;"' ?> 
    19   <?php else: ?> 
    20     <?php $linkAttributes = 'href = "' . url_for("aMedia/selected?id=$id") . '"' ?> 
    21   <?php endif ?> 
     19    <?php else: ?> 
     20      <?php // Non-image single select. The multiple add action is a bit of a misnomer here ?> 
     21      <?php // and redirects to aMedia/selected after adding the media item ?> 
     22      <?php $linkAttributes = 'href = "' . url_for("aMedia/multipleAdd?id=$id") . '"' ?> 
     23    <?php endif ?> 
    2224 
    2325<?php else: ?> 
  • plugins/apostrophePlugin/trunk/modules/aMedia/templates/_multipleList.php

    r1914 r1917  
    55<?php use_helper('I18N', 'jQuery') ?> 
    66 
    7 <?php $ids = array() ?> 
    8  
    97<?php foreach ($items as $item): ?> 
    108<li id="a-media-selection-list-item-<?php echo $item->getId() ?>" class="a-media-selection-list-item"> 
    119        <?php $id = $item->getId() ?> 
    1210  <ul class="a-controls a-media-multiple-list-controls">         
     11                <li> 
     12                        <a href="#crop" onclick="return false;" class="a-btn icon a-crop no-label" title="<?php echo __('Crop', null, 'apostrophe') ?>"><?php echo __('Crop', null, 'apostrophe') ?></a> 
     13                </li> 
    1314          <li><?php echo jq_link_to_remote(__("remove this item", null, 'apostrophe'), 
    1415    array( 
    1516      'url' => 'aMedia/multipleRemove?id='.$id, 
    1617      'update' => 'a-media-selection-list', 
    17                         'complete' => 'aUI("a-media-selection-list"); aMediaDeselectItem('.$id.')',  
     18                        'complete' => 'aUI("a-media-selection-list"); aMediaDeselectItem('.$id.'); aMediaUpdatePreview()',  
    1819    ), array( 
    1920                        'class'=> 'a-btn icon a-delete no-label', 
     
    2223        </ul>    
    2324 
    24         <div class="a-media-selected-item-drag-overlay" title="<?php echo __('Drag &amp; Drop to Order', null, 'apostrophe') ?>"></div> 
     25  <?php if (aMediaTools::isMultiple()): ?> 
     26        <div class="a-media-selected-item-drag-overlay" title="<?php echo __('Drag &amp; Drop to Order', null, 'apostrophe') ?>"></div> 
     27  <?php endif ?> 
    2528        <div class="a-media-selected-item-overlay"></div> 
    26   <img src="<?php echo url_for($item->getScaledUrl(aMediaTools::getOption('selected_constraints'))) ?>" /> 
    27  
    28           <?php $ids[] = $item->getId() ?> 
    29  
     29  <img src="<?php echo url_for($item->getCropThumbnailUrl()) ?>" class="a-thumbnail" /> 
    3030</li> 
    3131<?php endforeach ?> 
     
    3333<script type="text/javascript" charset="utf-8"> 
    3434 
    35         function aMediaItemsIndicateSelected(ids) 
     35        function aMediaItemsIndicateSelected(cropOptions) 
    3636        { 
    37                 $('.a-media-selected-overlay').remove(); 
     37          var ids = cropOptions.ids; 
     38          aCrop.init(cropOptions); 
     39                $('.a-media-selected-overlay').remove();                 
     40                $('.a-media-selected').removeClass('a-media-selected'); 
    3841                 
    3942          var i; 
     
    4750                        } 
    4851                } 
    49          
     52                         
    5053                $('.a-media-item.a-media-selected').each(function(){ 
    5154                        $(this).children('.a-media-item-thumbnail').prepend('<div class="a-media-selected-overlay"></div>'); 
    5255                }); 
     56                 
     57                $('#a-media-selection-list-caption').hide(); 
     58                if (!ids.length) { 
     59      $('#a-media-selection-list-caption').show(); 
     60                } 
    5361 
    5462                $('.a-media-selected-overlay').fadeTo(0, 0.66); 
     63        } 
     64         
     65        function aMediaUpdatePreview() 
     66        { 
     67          $('#a-media-selection-preview').load('<?php echo url_for('aMedia/updateMultiplePreview') ?>', function(){ 
     68          // the preview images are by default set to display:none 
     69            $('#a-media-selection-preview li:first').addClass('current'); 
     70            // set up cropping again; do hard reset to reinstantiate Jcrop 
     71            aCrop.resetCrop(true); 
     72          }); 
    5573        } 
    5674 
     
    6684 
    6785        $(document).ready(function() { // On page ready indicate selected items 
    68                 aMediaItemsIndicateSelected(<?php echo json_encode($ids) ?>)  
     86          var cropOptions = { 
     87      ids: <?php echo json_encode(aMediaTools::getSelection()) ?>, 
     88      aspectRatio: <?php echo aMediaTools::getAspectRatio() ?>, 
     89      minimumSize: [<?php echo aMediaTools::getAttribute('minimum-width') ?>, <?php echo aMediaTools::getAttribute('minimum-height') ?>], 
     90      maximumSize: [<?php echo aMediaTools::getAttribute('maximum-width') ?>, <?php echo aMediaTools::getAttribute('maximum-height') ?>], 
     91      <?php // width height cropLeft cropTop cropWidth cropHeight hashed by image id ?> 
     92      imageInfo: <?php echo json_encode(aMediaTools::getAttribute('imageInfo')) ?> 
     93    }; 
     94           
     95                aMediaItemsIndicateSelected(cropOptions); 
     96                 
    6997                $('.a-media-selected-item-overlay').fadeTo(0,.35); //cross-browser opacity for overlay 
    7098                $('.a-media-selection-list-item').hover(function(){ 
  • plugins/apostrophePlugin/trunk/modules/aMedia/templates/_multiplePreview.php

    r1909 r1917  
     1<?php // Make sure we're compatible with sf_escaping_strategy: true ?> 
     2<?php $items = $sf_data->getRaw('items') ?> 
    13<?php foreach ($items as $item): ?> 
    24  <li id="a-media-selection-preview-<?php echo $item->getId() ?>" class="a-media-selection-preview-item"> 
  • plugins/apostrophePlugin/trunk/modules/aMedia/templates/_selectMultiple.php

    r1914 r1917  
    22  // Compatible with sf_escaping_strategy: true 
    33  $limitSizes = isset($limitSizes) ? $sf_data->getRaw('limitSizes') : null; 
     4  $items = $sf_data->getRaw('items') ? $sf_data->getRaw('items') : null; 
    45?> 
     6<?php // Known as selectMultiple for historic reasons, but we use it for both single and multiple select now that ?> 
     7<?php // we have a need for cropping which requires a pause in both cases anyway. ?> 
    58<?php use_helper('I18N') ?> 
     9<?php use_javascript("/apostrophePlugin/js/plugins/jquery.jCrop.min.js") ?> 
     10<?php use_javascript("/apostrophePlugin/js/aCrop.js") ?> 
     11<?php use_stylesheet("/apostrophePlugin/css/Jcrop/jquery.Jcrop.css") ?> 
    612<div class="a-media-select"> 
    713<?php $type = aMediaTools::getAttribute('type') ?> 
     
    915<?php $type = "media item" ?> 
    1016<?php endif ?> 
    11         <p><?php echo __('Select one or more %typeplural% by clicking on them below. Drag and drop %typeplural%  to reorder them within the list of selected items. Remove %typeplural% by clicking on the trashcan.', array('%typeplural%' => __($type . 's')), 'apostrophe') ?> 
    12   <?php if ($limitSizes): ?> 
    13   <?php echo __('Only appropriately sized %typeplural% are shown.', array('%typeplural%' => __($type . 's')), 'apostrophe') ?> 
    14   <?php endif ?> 
    15   <?php echo __('When you\'re done, click "Save."', null, 'apostrophe') ?></p> 
     17        <h3><?php echo $label ?></h3> 
    1618 
    17         <ul id="a-media-selection-list"> 
    18         <?php include_component("aMedia", "multipleList") ?> 
     19        <div id="a-media-selection-list-caption"> 
     20          <h4><?php echo __("Preview your selected image%plural% here.", array('%plural%' => aMediaTools::isMultiple() ? 's':' '), 'apostrophe') ?></h4> 
     21        </div> 
     22        <div id="a-media-selection-wrapper"> 
     23                <ul id="a-media-selection-list" style="min-height:<?php echo ($thumbHeight = aMediaTools::getSelectedThumbnailHeight()) ? $thumbHeight + 10 : 85 ?>px;"> 
     24                        <?php if($items): ?> 
     25                                <?php include_partial("aMedia/multipleList", array("items" => $items)) ?> 
     26                        <?php else: ?> 
     27                          <?php if (aMediaTools::isMultiple()): ?> 
     28                                <li class="a-media-selection-placeholder"><?php echo __('Add images to your slideshow', null, 'apostrophe') ?></li> 
     29                        <?php else: ?> 
     30                          <li class="a-media-selection-placeholder"><?php echo __('Select an image', null, 'apostrophe') ?></li> 
     31                        <?php endif ?> 
     32                        <?php endif ?> 
     33                </ul> 
     34 
     35                <?php echo jq_sortable_element("#a-media-selection-list", array("url" => "aMedia/multipleOrder")) ?> 
     36                <br class="c"/> 
     37 
     38                <div class="a-crop-workspace"> 
     39                  <ul id="a-media-selection-preview"> 
     40                        <?php include_partial("aMedia/multiplePreview", array("items" => $items)) ?> 
     41                  </ul> 
     42                  <ul class="a-controls a-media-crop-controls"> 
     43                                <li><?php echo jq_link_to_function(__("Crop", null, 'apostrophe'), "aCrop.setCrop('".url_for('aMedia/crop')."')", array("class"=>"a-btn save")) ?></li> 
     44                          <li><?php echo jq_link_to_function(__("Cancel", null, 'apostrophe'), "aCrop.resetCrop()", array("class"=>"a-btn icon a-cancel event-default")) ?></li> 
     45                  </ul> 
     46                </div> 
     47        </div> 
     48        <ul class="a-controls a-media-slideshow-controls"> 
     49                <li><?php echo link_to(aMediaTools::isMultiple() ? __("Save Slideshow", null, 'apostrophe') : __("Save Selection", null, 'apostrophe'), "aMedia/selected", array("class"=>"a-btn save big")) ?></li> 
     50          <li><?php echo link_to(__("Cancel", null, 'apostrophe'), "aMedia/selectCancel", array("class"=>"a-btn icon a-cancel big")) ?></li> 
    1951        </ul> 
    20  
    21         <?php echo jq_sortable_element("#a-media-selection-list", array("url" => "aMedia/multipleOrder")) ?> 
    22  
    23         <br class="c"/> 
    24  
    25         <ul class="a-controls a-media-slideshow-controls"> 
    26                 <li><?php echo link_to(__("Save", null, 'apostrophe'), "aMedia/selected", array("class"=>"a-btn save")) ?></li> 
    27           <li><?php echo link_to(__("Cancel", null, 'apostrophe'), "aMedia/selectCancel", array("class"=>"a-btn icon a-cancel")) ?></li> 
    28         </ul> 
    29          
    3052</div> 
    31         <br class="c"/> 
     53</div> 
     54<br class="c"/> 
  • plugins/apostrophePlugin/trunk/modules/aMedia/templates/_selectSingle.php

    r1914 r1917  
    1717  <?php endif ?> 
    1818  </p> 
    19         <?php  
    20                 //removing this cancel link for now, it exists above in the header 
    21                 // echo link_to("cancel", "aMedia/selectCancel", array("class"=>"a-cancel"))  
    22         ?> 
     19  <ul class="a-controls"> 
     20    <li><?php echo link_to("cancel", "aMedia/selectCancel", array("class"=>"a-cancel")) ?></li> 
     21  </ul> 
    2322</div> 
  • plugins/apostrophePlugin/trunk/modules/aMedia/templates/indexSuccess.php

    r1914 r1917  
    77  $results = isset($results) ? $sf_data->getRaw('results') : null; 
    88?> 
    9 <?php use_helper('I18N') ?> 
     9<?php use_helper('I18N','jQuery') ?> 
    1010<?php slot('body_class') ?>a-media<?php end_slot() ?> 
    11  
    12 <?php use_helper('jQuery') ?> 
     11<?php $type = aMediaTools::getAttribute('type') ?> 
     12<?php $selecting = aMediaTools::isSelecting() ?> 
    1313 
    1414<div id="a-media-plugin"> 
     
    2020        <?php if (aMediaTools::isSelecting()): ?> 
    2121 
    22     <?php if (isset($label)): ?> 
    23       <h3><?php echo htmlspecialchars($label) ?> or <?php echo link_to(__("Cancel", null, 'apostrophe'), "aMedia/selectCancel", array("class"=>"a-btn a-cancel text-only")) ?></h3> 
     22    <?php if (($type === 'image') || (aMediaTools::isMultiple())): ?> 
     23      <?php include_component('aMedia', 'selectMultiple', array('limitSizes' => $limitSizes, 'label' => (isset($label)?$label:null))) ?> 
     24    <?php else: ?> 
     25      <?php include_component('aMedia', 'selectSingle', array('limitSizes' => $limitSizes, 'label' => (isset($label)?$label:null))) ?> 
    2426    <?php endif ?> 
    25  
    26     <?php include_partial('aMedia/describeConstraints') ?> 
    27  
    28           <?php if (aMediaTools::isMultiple()): ?> 
    29             <?php include_partial('aMedia/selectMultiple', array('limitSizes' => $limitSizes)) ?> 
    30           <?php else: ?> 
    31             <?php include_partial('aMedia/selectSingle', array('limitSizes' => $limitSizes)) ?> 
    32           <?php endif ?> 
    33  
    3427        <?php endif ?> 
    3528 
    36   <?php if (aMediaTools::userHasUploadPrivilege()): ?> 
    37  
    38    <ul class="a-controls a-media-controls"> 
    39      <?php $selecting = aMediaTools::isSelecting() ?> 
    40      <?php $type = aMediaTools::getAttribute('type') ?> 
    41  
    42      <?php if (!($selecting && $type && ($type !== 'image'))): ?> 
    43      <li><a href="<?php echo url_for("aMedia/uploadImages") ?>" class="a-btn icon a-add"><?php echo __('Add Images', null, 'apostrophe') ?></a></li> 
    44      <?php endif ?> 
    45  
    46      <?php if (!($selecting && $type && ($type !== 'video'))): ?> 
    47      <li><a href="<?php echo url_for("aMedia/newVideo") ?>" class="a-btn icon a-add"><?php echo __('Add Video', null, 'apostrophe') ?></a></li> 
    48      <?php endif ?> 
    49  
    50      <?php if (!($selecting && $type && ($type !== 'pdf'))): ?> 
    51      <li><a href="<?php echo url_for("aMedia/editPdf") ?>" class="a-btn icon a-add"><?php echo __('Add PDF', null, 'apostrophe') ?></a></li> 
    52      <?php endif ?> 
    53  
    54    </ul> 
    55  
    56   <?php endif ?> 
    5729</div> 
    5830 
     
    6032 
    6133<div class="a-media-library"> 
     34         
     35        <h3>Media Library</h3> 
     36         
     37        <?php include_partial('aMedia/describeConstraints', array('limitSizes' => $limitSizes)) ?> 
     38         
     39        <?php if (aMediaTools::userHasUploadPrivilege()): ?> 
     40   <ul class="a-controls a-media-controls"> 
     41 
     42     <?php if (!($selecting && $type && ($type !== 'image'))): ?> 
     43     <li><a href="<?php echo url_for("aMedia/uploadImages") ?>" class="a-btn icon big a-add"><?php echo __('Add Images', null, 'apostrophe') ?></a></li> 
     44     <?php endif ?> 
     45 
     46     <?php if (!($selecting && $type && ($type !== 'video'))): ?> 
     47     <li><a href="<?php echo url_for("aMedia/newVideo") ?>" class="a-btn icon big a-add"><?php echo __('Add Video', null, 'apostrophe') ?></a></li> 
     48     <?php endif ?> 
     49 
     50     <?php if (!($selecting && $type && ($type !== 'pdf'))): ?> 
     51     <li><a href="<?php echo url_for("aMedia/editPdf") ?>" class="a-btn icon big a-add"><?php echo __('Add PDF', null, 'apostrophe') ?></a></li> 
     52     <?php endif ?> 
     53   </ul> 
     54  <?php endif ?> 
     55         
    6256 <?php for ($n = 0; ($n < count($results)); $n += 2): ?> 
    6357   <div class="a-media-row"> 
  • plugins/apostrophePlugin/trunk/modules/aSlideshowSlot/templates/_normalView.php

    r1914 r1917  
    3333              "aMediaIds" => implode(",", $itemIds), 
    3434              "type" => "image", 
    35               "label" => __("Create a Slideshow", null, 'apostrophe'), 
     35              "label" => __("You are creating a slideshow of images.", null, 'apostrophe'), 
    3636              "after" => url_for("aSlideshowSlot/edit") . "?" .  
    3737                http_build_query( 
  • plugins/apostrophePlugin/trunk/modules/aVideoSlot/templates/_normalView.php

    r1914 r1917  
    1414  $slug = isset($slug) ? $sf_data->getRaw('slug') : null; 
    1515  $title = isset($title) ? $sf_data->getRaw('title') : null; 
     16  $embed = isset($embed) ? $sf_data->getRaw('embed') : null; 
    1617?> 
    1718<?php use_helper('I18N') ?> 
  • plugins/apostrophePlugin/trunk/web/css/a.css

    r1879 r1917  
    721721{ 
    722722padding: 0 !important; 
     723} 
     724 
     725.icon.a-crop 
     726{ 
     727background-image: url(/apostrophePlugin/images/a-icon-crop.png); 
    723728} 
    724729 
     
    37253730width: 720px; 
    37263731float: right; 
    3727 margin-bottom: 20px; 
     3732/*margin-bottom: 20px;*/ 
    37283733/*overflow: hidden;*/ 
    37293734} 
     
    37313736.a-media-toolbar 
    37323737{ 
    3733 padding-bottom: 20px; 
    3734 border-bottom: 1px solid #ddd; 
     3738padding-bottom: 15px; 
     3739background:url("/apostropheBlogPlugin/images/a-blog-td-bg.png") repeat-x scroll 0 bottom transparent; 
    37353740} 
    37363741 
     
    37423747} 
    37433748 
    3744 .a-media-toolbar .a-media-controls, 
    37453749.a-media-toolbar .a-media-slideshow-controls 
    37463750{ 
    37473751clear: both; 
    37483752} 
     3753 
     3754.a-media-library .a-controls.a-media-controls 
     3755{ 
     3756        clear:both; 
     3757        float:left; 
     3758        display:inline; 
     3759        margin:15px 0 30px 0!important; 
     3760} 
     3761 
     3762.a-media-library .a-controls.a-media-controls li 
     3763{ 
     3764        margin:0 !important; 
     3765} 
     3766 
     3767 
    37493768 
    37503769.a-media-toolbar h3 
     
    37533772        /*float: left;*/ 
    37543773        margin: 0 0 10px 0; 
     3774        color:#333; 
    37553775} 
    37563776 
     
    38263846} 
    38273847 
    3828 .a-media-item.even 
    3829 { 
    3830 /*margin-right: 20px;*/ 
    3831 } 
    3832  
    38333848.a-media-item-title h3 a 
    38343849{ 
     
    38463861{ 
    38473862margin-bottom: 10px; 
    3848 float: left; 
    3849 display: inline; 
    38503863padding-bottom:10px; 
     3864padding: 10px; 
     3865margin: 0 0 20px -1px; 
     3866background-color: #efefef; 
     3867-moz-border-radius: 4px; 
     3868-webkit-border-radius: 4px; 
     3869border-radius: 4px; 
     3870border: 1px solid #ccc; 
     3871overflow: auto; 
     3872} 
     3873 
     3874#a-media-selection-wrapper 
     3875{ 
     3876        display: inline; 
     3877        position: relative; 
     3878        float: left; 
     3879        width:680px; 
     3880        padding:10px; 
     3881        margin-bottom: 20px; 
     3882        background-color: #222; 
     3883        -moz-border-radius: 4px; 
     3884        -webkit-border-radius: 4px; 
     3885        border-radius: 4px; 
     3886        box-shadow:inset 0 0 10px #000000; 
     3887  -moz-box-shadow:inset 0 0 10px #000000; 
     3888  -webkit-box-shadow:inset 0 0 10px #000000; 
    38513889} 
    38523890 
    38533891#a-media-selection-list 
    38543892{ 
    3855 float: left; 
    3856 margin: 10px 0; 
     3893display: block; 
     3894overflow: hidden; 
     3895position: relative; 
     3896width:690px; 
     3897left:-10px; 
     3898/*border: 1px dashed #999;*/ 
     3899-moz-border-radius: 4px; 
     3900-webkit-border-radius: 4px; 
     3901border-radius: 4px; 
     3902/*background-color: #e6e6e6;*/ 
     3903} 
     3904 
     3905.a-media-selection-placeholder 
     3906{ 
     3907        margin-left: 10px; 
     3908        float: left; 
     3909        width: 80px; 
     3910        height: 50px; 
     3911        border: 2px dashed #ddd; 
     3912        padding: 10px; 
     3913        color: #fff; 
    38573914} 
    38583915 
     
    38603917{ 
    38613918float: left; 
    3862 margin: 0 16px 16px 0; 
     3919margin: 5px 0px 5px 10px; 
    38633920cursor: move; 
    38643921position: relative; 
     3922background: #fff url(/apostrophePlugin/images/a-icon-loader.gif) center center no-repeat; 
     3923border: 2px solid #222; 
     3924} 
     3925 
     3926.cropping-now 
     3927{ 
     3928        border: 2px solid #ddd; 
    38653929} 
    38663930 
     
    38913955{ 
    38923956float: left; 
     3957} 
     3958 
     3959#a-media-selection-list-caption { 
     3960  display: none; 
     3961  position: absolute; 
     3962  padding: 45px; 
     3963  z-index: 1; 
     3964} 
     3965#a-media-selection-list-caption h4 { 
     3966  font-size: 1.2em; 
     3967  color: #999; 
     3968} 
     3969 
     3970#a-media-selection-preview 
     3971{ 
     3972        display:inline; 
     3973        float:left; 
     3974        width:100%; 
     3975        margin:10px 0 0; 
     3976} 
     3977 
     3978li.a-media-selection-preview-item { 
     3979  display:none; 
     3980        position:relative; 
     3981        width:auto; 
     3982        height:auto; 
     3983} 
     3984li.a-media-selection-preview-item.current { 
     3985  display: block !important; 
     3986} 
     3987 
     3988li.a-media-selection-preview-item .jcrop-holder { 
     3989  margin-bottom:28px; 
     3990} 
     3991 
     3992ul.a-media-crop-controls 
     3993{ 
     3994        position: absolute; 
     3995        display: none; 
     3996        bottom: -29px; 
     3997} 
     3998 
     3999.a-crop-workspace 
     4000{ 
     4001        overflow: hidden; 
     4002        display: none; 
     4003/*margin: 0 0 10px; 
     4004        padding: 10px; 
     4005        background-color: #222; 
     4006        -moz-border-radius: 4px; 
     4007        -webkit-border-radius: 4px; 
     4008        border-radius: 4px; 
     4009        box-shadow:inset 0 0 10px #000000; 
     4010  -moz-box-shadow:inset 0 0 10px #000000; 
     4011  -webkit-box-shadow:inset 0 0 10px #000000;*/ 
     4012} 
     4013 
     4014 
     4015.a-crop-workspace h4 
     4016{ 
     4017        color:#ddd; 
     4018        margin-bottom:10px; 
    38934019} 
    38944020 
  • plugins/apostrophePlugin/trunk/web/images