Merging development branch into master

This commit is contained in:
Jaisen Mathai 2012-02-23 16:00:37 -08:00
commit 23f57d36fe
65 changed files with 1366 additions and 234 deletions

View file

@ -0,0 +1,173 @@
Get Activities
=======================
----------------------------------------
1. [Purpose][purpose]
1. [Endpoint][endpoint]
1. [Parameters][parameters]
1. [Examples][examples]
* [Command line][example-cli]
* [PHP][example-php]
1. [Response][response]
* [Sample][sample]
----------------------------------------
<a name="purpose"></a>
### Purpose of the Get Activities API
Use this API to get a user's activity feed.
----------------------------------------
<a name="endpoint"></a>
### Endpoint
_Authentication: optional_
GET /activities/list.json
<a name="parameters"></a>
### Parameters
1. groupBy (optional), Time period to group activities by
----------------------------------------
<a name="examples"></a>
### Examples
<a name="example-cli"></a>
#### Command Line (using [openphoto-php][openphoto-php])
./openphoto -p -h current.openphoto.me -e /activities/list.json
<a name="example-php"></a>
#### PHP (using [openphoto-php][openphoto-php])
$client = new OpenPhotoOAuth($host, $consumerKey, $consumerSecret, $oauthToken, $oauthTokenSecret);
$response = $client->get("/tags/activities.json");
----------------------------------------
<a name="response"></a>
### Response
The response is in a standard [response envelope](http://theopenphotoproject.org/documentation/api/Envelope).
* _message_, A string describing the result. Don't use this for anything but reading.
* _code_, _200_ on success
* _result_, An array of activities
<a name="sample"></a>
#### Sample without `groupBy`
{
"message" : "User's list of activities",
"code" : 200,
"result" : [
{ // Photo object
"id" : "l", // activity id, not photo id. See photo object for photo id
"owner" : "jaisen+test@jmathai.com",
"appId" : "openphoto-frontend",
"type" : "photo-upload",
"data" : {
// Photo object
}
},
{ // comment
"id" : "p", // activity id, not photo id. See photo object for photo id
"owner" : "jaisen+test@jmathai.com",
"appId" : "openphoto-frontend",
"type" : "action-create",
"data" : {
"targetType" : "photo",
"target" : {
// Photo object
},
"action" : {
// Action object
}
},
"permission" : "1",
"dateCreated" : "1328851975"
}
]
}
#### Sample with `groupBy`
{
"message" : "User's list of activities",
"code" : 200,
"result" : {
"2012020921-photo-upload" : [ // photo uploads
{
"id" : "l", // activity id, not photo id. See photo object for photo id
"type" : "photo-upload",
"data" : {
// Photo object
},
"permission" : "1",
"dateCreated" : "1328851361"
},
{
"id" : "m", // activity id, not photo id. See photo object for photo id
"type" : "photo-upload",
"data" : {
// Photo object
},
"permission" : "1",
"dateCreated" : "1328851363"
}
],
"2012020921-action-create" : [
{
"id" : "p", // activity id, not photo id. See photo object for photo id
"type" : "action-create",
"data" : {
"targetType" : "photo",
"target" : {
// Photo object
},
"action" : {
// Action object
}
},
"permission" : "1",
"dateCreated" : "1328851975"
},
{
"id" : "q", // activity id, not photo id. See photo object for photo id
"owner" : "jaisen+test@jmathai.com",
"appId" : "openphoto-frontend",
"type" : "action-create",
"data" : {
"targetType" : "photo",
"target" : {
// Photo object
},
"action" : {
// Action object
}
},
"permission" : "1",
"dateCreated" : "1328852131"
}
]
}
}
[purpose]: #purpose
[endpoint]: #endpoint
[parameters]: #parameters
[examples]: #examples
[example-cli]: #example-cli
[example-php]: #example-php
[response]: #response
[sample]: #sample
[openphoto-php]: https://github.com/openphoto/openphoto-php

View file

@ -1,4 +1,4 @@
OpenPhoto / Installation for Ubuntu
OpenPhoto / Installation for Ubuntu + Apache
=======================
#### OpenPhoto, a photo service for the masses
@ -26,7 +26,7 @@ Once you've confirmed that your cloud account is setup you can get started on yo
apt-get update
apt-get upgrade
apt-get install apache2 php5 libapache2-mod-php5 php5-curl php5-gd php5-mcrypt php-apc
apt-get install apache2 php5 libapache2-mod-php5 php5-curl php5-gd php5-mcrypt php-apc build-essential libpcre3-dev
a2enmod rewrite
There are also a few optional but recommended packages and modules.

View file

@ -1,11 +1,12 @@
OpenPhoto / Installation for Ubuntu
OpenPhoto / Installation for Ubuntu + Cherokee
=======================
#### OpenPhoto, a photo service for the masses
## OS: Linux Ubuntu Server 10.04+
This guide instructs you on how to install OpenPhoto on Cherokee Web Server on an Ubuntu server.
To have a recent version of Cherokee, I advice to add the ppa maintained with latest version. You can add it with the command
To have a recent version of Cherokee, I advice to add the ppa maintained with latest version. You can add it with the command below.
add-apt-repository ppa:cherokee-webserver/ppa
----------------------------------------
@ -28,7 +29,7 @@ Once you've confirmed that your cloud account is setup you can get started on yo
apt-get update
apt-get upgrade
apt-get install cherokee php5-fpm php5-curl php5-mcrypt php-apc
apt-get install cherokee php5-fpm php5-curl php5-mcrypt php-apc build-essential libpcre3-dev
There are also a few optional but recommended packages and modules.

View file

@ -5,7 +5,9 @@ currentCodeVersion=1.3.2
[site]
mode=prod
allowOriginalDownload=0
[epi]
cache=EpiCache_File
session=EpiSession_Php
config=EpiConfig_File

View file

@ -1,11 +1,44 @@
<?php
$status = true;
$sql = <<<SQL
CREATE TABLE IF NOT EXISTS `{$this->mySqlTablePrefix}activity` (
`id` varchar(6) NOT NULL,
`owner` varchar(255) NOT NULL,
`appId` varchar(255) NOT NULL,
`type` varchar(32) NOT NULL,
`data` text NOT NULL,
`permission` int(11) DEFAULT NULL,
`dateCreated` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`,`owner`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
SQL;
$status = $status && mysql_1_4_0($sql);
$sql = <<<SQL
ALTER TABLE `{$this->mySqlTablePrefix}elementTag` ADD INDEX ( `element` )
SQL;
$status = $status && mysql_1_4_0($sql);
$sql = <<<SQL
ALTER TABLE `{$this->mySqlTablePrefix}photo` ADD `filenameOriginal` VARCHAR( 255 ) NULL AFTER `dateUploadedYear`
SQL;
$status = $status && mysql_1_4_0($sql);
$sql = <<<SQL
SELECT `id`, `owner`, `pathOriginal` from `{$this->mySqlTablePrefix}photo`
SQL;
$photos = getDatabase()->all($sql);
foreach($photos as $photo)
{
$filename = basename($photo['pathOriginal']);
$filenameOriginal = substr($filename, strpos($filename, '-')+1);
$sql = <<<SQL
UPDATE `{$this->mySqlTablePrefix}photo` SET `filenameOriginal`=:filenameOriginal WHERE `owner`=:owner AND `id`=:id
SQL;
getDatabase()->execute($sql, array(':filenameOriginal' => $filenameOriginal, ':owner' => $photo['owner'], ':id' => $photo['id']));
}
$sql = <<<SQL
UPDATE `{$this->mySqlTablePrefix}admin` SET `value`=:version WHERE `key`=:key
SQL;

View file

@ -3,7 +3,7 @@ $domains = $this->db->get_domain_list("/^{$this->domainPhoto}(Action|Credential|
if(count($domains) == 7)
return true;
$domainsToCreate = array($this->domainAction, $this->domainCredential, $this->domainGroup,
$domainsToCreate = array($this->domainAction, $this->domainActivity, $this->domainCredential, $this->domainGroup,
$this->domainPhoto, $this->domainTag, $this->domainUser, $this->domainWebhook);
$queue = new CFBatchRequest();

View file

@ -23,6 +23,7 @@ javascript="jquery"
[frontApis]
photos="GET /photos/list.json?pageSize=20&returnSizes=800x450xCR"
#activities="GET /activities/list.json?pageSize=10&groupBy=hour"
[pagination]
pagesToDisplay=10

View file

@ -42,31 +42,32 @@ this.isShown=false;f.call(this);this.$element.trigger("hide").removeClass("in");
b(document).ready(function(){b("body").delegate("[data-controls-modal]","click",function(a){a.preventDefault();a=b(this).data("show",true);b("#"+a.attr("data-controls-modal")).modal(a.data())})})})(window.jQuery||window.ender);
var opTheme=function(){var b=null,h=function(a){console!==void 0&&console.log!==void 0&&console.log(a)},l=function(a){var b="";arguments.length>1&&(arguments[1]=="error"?b="error":arguments[1]=="confirm"&&(b="success"));return'<div class="alert-message block-message '+b+'"><a class="modal-close-click close" href="#">x</a>'+a+"</div>"},a=function(a){var b=$(document.activeElement).val(),a=a.result,e=[];for(i in a)a.hasOwnProperty(i)&&e.push({id:a[i].id,name:a[i].id+" ("+a[i].count+")"});e.push({id:b,
name:b});return e},g=function(a){return"<li>"+a.name+"</li>"},f=void 0;return{callback:{keyboard:function(){h("keyboard!!!!")},actionDelete:function(a){a.preventDefault();var a=$(a.target),b=a.attr("href")+".json",e=a.attr("data-id");OP.Util.makeRequest(b,a.parent().serializeArray(),function(a){a.code===200?$(".action-container-"+e).hide("medium",function(){$(this).remove()}):opTheme.message.error("Could not delete the photo.")});return false},batchAdd:function(a){$(".id-"+a.id).removeClass("unpinned").addClass("pinned");
opTheme.ui.batchMessage();h("Adding photo "+a.id)},batchClear:function(){var a=$("#batch-message").parent();$(".pinned").removeClass("pinned").addClass("unpinned").children().filter(".pin").fadeOut();a.slideUp("fast",function(){$(this).remove()})},batchField:function(a){var a=$(a.target).val(),b=$("form#batch-edit .form-fields");switch(a){case "permission":b.html(opTheme.ui.batchFormFields.permission());break;case "tagsAdd":case "tagsRemove":b.html(opTheme.ui.batchFormFields.tags()),OP.Util.fire("callback:tags-autocomplete")}},
batchModal:function(){var a=$("#modal"),b='<form id="batch-edit"> <div class="clearfix"> <label>Property</label> <div class="input"> <select id="batch-key" class="batch-field-change" name="property"> <option value="tagsAdd">Add Tags</option> <option value="tagsRemove">Remove Tags</option> <option value="permission">Permission</option> </select> </div> </div> <div class="form-fields">'+opTheme.ui.batchFormFields.tags()+"</div></form>";a.html('<div class="modal-header"> <a href="#" class="close">&times;</a> <h3>Batch edit your pinned photos</h3></div><div class="modal-body"> <p>'+
b+'</p></div><div class="modal-footer"><a href="#" class="btn photo-update-batch-click">Update</a></div>');OP.Util.fire("callback:tags-autocomplete")},batchRemove:function(a){$(".id-"+a).addClass("unpinned").removeClass("pinned");opTheme.ui.batchMessage();h("Removing photo "+a)},commentJump:function(a){a.preventDefault();$.scrollTo($("div.comment-form"),200);return false},credentailDelete:function(a){a.preventDefault();var b=$(a.target),a=b.attr("href")+".json";OP.Util.makeRequest(a,{},function(a){a.code===
200?(b.parent().remove(),opTheme.message.confirm("Credential successfully deleted.")):opTheme.message.error("Could not delete credential.")});return false},groupCheckbox:function(a){a=$(a.target);a.hasClass("none")&&a.is(":checked")?$("input.group-checkbox:not(.none)").removeAttr("checked"):a.is(":checked")&&$("input.group-checkbox.none").removeAttr("checked")},groupPost:function(a){a.preventDefault();var a=$(a.target).parent().parent(),b=a.attr("action")+".json",e=b.search("create")>-1;OP.Util.makeRequest(b,
a.serializeArray(),function(a){a.code===200?e?location.href=location.href:opTheme.message.confirm("Group updated successfully."):opTheme.message.error("Could not update group.")});return false},login:function(a){a=$(a.target);a.hasClass("browserid")?navigator.id.getVerifiedEmail(function(a){a?opTheme.user.browserid.loginSuccess(a):opTheme.user.browserid.loginFailure(a)}):a.hasClass("facebook")&&FB.login(function(a){a.authResponse?(h("User logged in, posting to openphoto host."),OP.Util.makeRequest("/user/facebook/login.json",
opTheme.user.base.loginProcessed)):h("User cancelled login or did not fully authorize.")},{scope:"email"})},modalClose:function(a){a.preventDefault();$(a.target).parent().slideUp("fast",function(){$(this).remove()})},photoDelete:function(a){a.preventDefault();var b=$(a.target),a=b.parent().attr("action")+".json";OP.Util.makeRequest(a,b.parent().serializeArray(),function(a){a.code===200?(b.html("This photo has been deleted"),opTheme.message.confirm("This photo has been deleted.")):opTheme.message.error("Could not delete the photo.")});
return false},photoEdit:function(a){a.preventDefault();a=$(a.target).attr("href")+".json";$("div.owner-edit").length==1?$.scrollTo($("div.owner-edit"),200):OP.Util.makeRequest(a,{},function(a){a.code===200?($("#main").append(a.result.markup),$.scrollTo($("div.owner-edit"),200),OP.Util.fire("callback:tags-autocomplete")):opTheme.message.error("Could not load the form to edit this photo.")},"json","get");return false},photoUpdateBatch:function(a){a.preventDefault();$(a.target);var a=$("#batch-key").val(),
j=$("form#batch-edit").find("*[name='value']"),j=j.length==1?j.val():$("form#batch-edit").find("*[name='value']:checked").val();params={crumb:b};params[a]=j;params.ids=OP.Batch.collection.getIds().join(",");h(params);OP.Util.makeRequest("/photos/update.json",params,opTheme.callback.photoUpdateBatchCb,"json","post")},photoUpdateBatchCb:function(a){a.code==200?opTheme.message.append(l("Your photos were successfully updated.","confirm")):opTheme.message.append(l("There was a problem updating your photos.",
"error"));$("#modal").modal("hide")},pinClick:function(a){var a=$(a.target),b=a.attr("data-id");a.parent().hasClass("unpinned")?OP.Batch.add(b):OP.Batch.remove(b)},pinClearClick:function(a){a.preventDefault();OP.Batch.clear()},pinOver:function(a){$(a.target).parent().prev().fadeIn("fast")},pinOut:function(a){$(a.target).filter('[class~="unpinned"]').children().filter(".pin").filter(":visible").fadeOut("fast")},pluginStatus:function(a){a.preventDefault();a=$(a.target).attr("href")+".json";OP.Util.makeRequest(a,
{},function(a){a.code===200?window.location.reload():opTheme.message.error("Could not update the status of this plugin.")},"json","post");return false},pluginUpdate:function(a){a.preventDefault();var a=$(a.target).parent(),b=a.attr("action")+".json";OP.Util.makeRequest(b,a.serializeArray(),function(a){a.code===200?opTheme.message.confirm("Your plugin was successfully updated."):opTheme.message.error("Could not update the status of this plugin.")},"json","post");return false},searchByTags:function(a){a.preventDefault();
var b=$(a.target).parent(),a=$(b.find("input[name=tags]")[0]).val(),b=$(b).attr("action");location.href=a.length>0?b.replace("/list","")+"/tags-"+a+"/list":b;return false},settings:function(){$("ul#settingsbar").slideToggle("medium");$("li#nav-signin").toggleClass("active");return false},keyBrowseNext:function(){var a;if(a=$(".image-pagination .next a").attr("href"))location.href=a},keyBrowsePrevious:function(){var a;if(a=$(".image-pagination .previous a").attr("href"))location.href=a},webhookDelete:function(a){a.preventDefault();
var b=$(a.target),a=b.attr("href")+".json";OP.Util.makeRequest(a,{},function(a){a.code===200?(b.parent().remove(),opTheme.message.confirm("Credential successfully deleted.")):opTheme.message.error("Could not delete credential.")});return false}},formHandlers:{hasErrors:function(a,b){var e=[];a.children("input, textarea").each(function(){var c=$(this);c.prev().removeClass("error");var f=c.attr(b);if(f!=void 0)for(var f=f.split(" "),g=0;g<f.length;g++){if(f[g]=="date"&&!opTheme.formHandlers.passesDate(c)){var h=
c.prev().html()+" is not a valid date";e.push([c,h])}f[g]=="email"&&!opTheme.formHandlers.passesEmail(c)&&(h=c.prev().html()+" is not a valid email address",e.push([c,h]));f[g]=="ifexists"&&c.val()!=""&&c.val()!=void 0&&$.merge(e,opTheme.formHandlers.hasErrors(a,"data-ifexists"));f[g]=="integer"&&!opTheme.formHandlers.passesInteger(c)&&(h=c.prev().html()+" is not a number",e.push([c,h]));f[g]=="match"&&(h=c.attr("data-match"),opTheme.formHandlers.passesMatch(c,h)||(h=c.prev().html()+" does not match "+
$("#"+h).prev().html(),e.push([c,h])));f[g]=="required"&&!opTheme.formHandlers.passesRequired(c)&&(h=c.prev().html()+" is required",e.push([c,h]));f[g]=="alphanumeric"&&!opTheme.formHandlers.passesAlphaNumeric(c)&&(h=c.prev().html()+" can only contain alpha-numeric characters",e.push([c,h]))}});return e},init:function(){$(this).submit(opTheme.submitHandlers.siteForm);opTheme.formHandlers.showPlaceholders();$("input[data-placeholder]").live("focus",opTheme.formHandlers.placeholderFocus);$("input[data-placeholder]").live("blur",
opTheme.formHandlers.placeholderBlur)},passesAlphaNumeric:function(a){return/^[a-zA-Z0-9]+$/.test(a.val())},passesDate:function(a){return/^\d{1,2}\/\d{1,2}\/\d{4}$/.test(a.val())},passesEmail:function(a){return/^([\w-\.+]+@([\w-]+\.)+[\w-]{2,4})?$/.test(a.val())},passesInteger:function(a){return/^\d+$/.test(a.val())},passesMatch:function(a,b){return a.val()==$("#"+b).val()},passesRequired:function(a){return a.is("textarea")||a.is("input")&&(a.attr("type")=="text"||a.attr("type")=="password")?a.val()!=
""&&a.val()!=void 0:a.is("checkbox")?a.is(":checked"):true},placeholderBlur:function(){var a=$(this);a.val()==""&&(a.val(a.attr("data-placeholder")),a.addClass("placeholder"))},placeholderFocus:function(){var a=$(this);a.val()==a.attr("data-placeholder")&&(a.val(""),a.removeClass("placeholder"))},removePlaceholders:function(){$("input[data-placeholder]").each(function(){var a=$(this);a.val()==a.attr("data-placeholder")&&(a.val(""),a.removeClass("placeholder"))})},showPlaceholders:function(){$("input[data-placeholder]").each(function(){var a=
$(this);a.val()==""&&(a.val(a.attr("data-placeholder")),a.addClass("placeholder"))})}},front:{init:function(a){a.length>0&&a.cycle({fx:"fade"}).find("img").click(function(a){location.href=$(a.target).attr("data-origin")})}},init:{load:function(a){b=a;$("section#slideshow").length>0&&$(window).load(function(){$(".flexslider").flexslider({animation:"slide",controlsContainer:".flex-container",controlNav:true,pausePlay:false,directionNav:true,nextText:"<span title='Next'>Next</span>",prevText:"<span title='Previous'>Previous</span>"})});
$("#modal").modal({keyboard:true})},attach:function(){OP.Util.on("click:action-delete",opTheme.callback.actionDelete);OP.Util.on("click:action-jump",opTheme.callback.commentJump);OP.Util.on("click:batch-modal",opTheme.callback.batchModal);OP.Util.on("click:credential-delete",opTheme.callback.credentailDelete);OP.Util.on("click:group-checkbox",opTheme.callback.groupCheckbox);OP.Util.on("click:group-update",opTheme.callback.groupPost);OP.Util.on("click:login",opTheme.callback.login);OP.Util.on("click:modal-close",
opTheme.callback.modalClose);OP.Util.on("click:photo-delete",opTheme.callback.photoDelete);OP.Util.on("click:photo-edit",opTheme.callback.photoEdit);OP.Util.on("click:photo-update-batch",opTheme.callback.photoUpdateBatch);OP.Util.on("click:plugin-status",opTheme.callback.pluginStatus);OP.Util.on("click:plugin-update",opTheme.callback.pluginUpdate);OP.Util.on("click:nav-item",opTheme.callback.searchBarToggle);OP.Util.on("click:search",opTheme.callback.searchByTags);OP.Util.on("click:settings",opTheme.callback.settings);
OP.Util.on("click:webhook-delete",opTheme.callback.webhookDelete);OP.Util.on("click:pin",opTheme.callback.pinClick);OP.Util.on("click:pin-clear",opTheme.callback.pinClearClick);OP.Util.on("keydown:browse-next",opTheme.callback.keyBrowseNext);OP.Util.on("keydown:browse-previous",opTheme.callback.keyBrowsePrevious);OP.Util.on("mouseover:pin",opTheme.callback.pinOver);OP.Util.on("mouseout:pin",opTheme.callback.pinOut);OP.Util.on("change:batch-field",opTheme.callback.batchField);OP.Util.on("callback:tags-autocomplete",
opTheme.init.tags.autocomplete);OP.Util.on("callback:batch-add",opTheme.callback.batchAdd);OP.Util.on("callback:batch-remove",opTheme.callback.batchRemove);OP.Util.on("callback:batch-clear",opTheme.callback.batchClear);OP.Util.fire("callback:tags-autocomplete");typeof OPU==="object"&&OPU.init();$("form.validate").each(opTheme.formHandlers.init)},photos:function(){var a=OP.Batch.collection.getAll(),b=OP.Batch.collection.getLength(),e=$(".unpinned"),c,f;b>0&&opTheme.ui.batchMessage();e.each(function(b,
e){e=$(e);c=e.attr("class");f=c.match(/ id-([a-z0-9]+)/);f.length==2&&a[f[1]]!==void 0&&e.removeClass("unpinned").addClass("pinned")})},tags:{autocomplete:function(){var b={queryParam:"search",propertyToSearch:"id",preventDuplicates:true};b.onResult=a;b.resultsFormatter=g;$("input[class~='tags-autocomplete']").each(function(a,e){var e=$(e),c=e.attr("value");if(e.css("display")!="none"){if(c!=""){for(var c=c.split(","),f=[],a=0;a<c.length;a++)f.push({id:c[a],name:c[a]});b.prePopulate=f}$(e).tokenInput("/tags/list.json",
b)}})}}},message:{append:function(a){$("#message").append(a).slideDown()},close:function(){f!=void 0&&(clearTimeout(f),f=void 0,$("#message-box").animate({height:"toggle"},500,function(){$("#message-box").remove()}))},confirm:function(a){opTheme.message.show(a,"confirm")},error:function(a){opTheme.message.show(a,"error")},show:function(a,b){var e=b=="error"?"confirm":"error";f!=void 0?(clearTimeout(f),f=void 0,$("#message-box").removeClass(e).addClass(b).html('<div><a class="message-close">close</a>'+
a+"</div>"),f=setTimeout(function(){$("#message-box").animate({height:"toggle"},500,function(){$("#message-box").remove();f=void 0})},7E3)):($("html").append('<section id="message-box" style="display:none;"><div><a class="message-close">close</a>'+a+"</div></section>"),$("#message-box").removeClass(e).addClass(b).animate({height:"toggle"},500,function(){f=setTimeout(function(){$("#message-box").animate({height:"toggle"},500,function(){$("#message-box").remove();f=void 0})},7E3)}));$("a.message-close").click(opTheme.message.close)}},
submitHandlers:{siteForm:function(a){var b=$(this);a.preventDefault();opTheme.formHandlers.removePlaceholders();a=opTheme.formHandlers.hasErrors(b,"data-validation");opTheme.formHandlers.showPlaceholders();if(a.length==0)this.submit();else{for(var b="<ul>",e=0;e<a.length;e++)a[e][0].prev().addClass("error"),b+="<li>"+a[e][1]+"</li>";b+="</ul>";$("html").animate({scrollTop:a[0][0].offset().top-30},500);a[0][0].focus();opTheme.message.error(b)}}},ui:{batchFormFields:{permission:function(){return' <div class="clearfix"> <label>Value</label> <div class="input"> <ul class="inputs-list"> <li> <label> <input type="radio" name="value" value="1" checked="checked"> <span>Public</span> </label> </li> <li> <label> <input type="radio" name="value" value="0"> <span>Private</span> </label> </li> </div> </div>'},
opTheme.ui.batchMessage();h("Adding photo "+a.id)},batchClear:function(){var a=$("#batch-message").parent();$(".pinned").removeClass("pinned").addClass("unpinned").children().filter(".pin").fadeOut();a.slideUp("fast",function(){$(this).remove()})},batchField:function(a){var a=$(a.target).val(),b=$("form#batch-edit .form-fields");switch(a){case "delete":b.html(opTheme.ui.batchFormFields.empty());break;case "permission":b.html(opTheme.ui.batchFormFields.permission());break;case "tagsAdd":case "tagsRemove":b.html(opTheme.ui.batchFormFields.tags()),
OP.Util.fire("callback:tags-autocomplete")}},batchModal:function(){var a=$("#modal"),b='<form id="batch-edit"> <div class="clearfix"> <label>Property</label> <div class="input"> <select id="batch-key" class="batch-field-change" name="property"> <option value="tagsAdd">Add Tags</option> <option value="tagsRemove">Remove Tags</option> <option value="permission">Permission</option> <option value="delete">Delete</option> </select> </div> </div> <div class="form-fields">'+
opTheme.ui.batchFormFields.tags()+"</div></form>";a.html('<div class="modal-header"> <a href="#" class="close">&times;</a> <h3>Batch edit your pinned photos</h3></div><div class="modal-body"> <p>'+b+'</p></div><div class="modal-footer"><a href="#" class="btn photo-update-batch-click">Submit</a></div>');OP.Util.fire("callback:tags-autocomplete")},batchRemove:function(a){$(".id-"+a).addClass("unpinned").removeClass("pinned");opTheme.ui.batchMessage();h("Removing photo "+a)},commentJump:function(a){a.preventDefault();
$.scrollTo($("div.comment-form"),200);return false},credentailDelete:function(a){a.preventDefault();var b=$(a.target),a=b.attr("href")+".json";OP.Util.makeRequest(a,{},function(a){a.code===200?(b.parent().remove(),opTheme.message.confirm("Credential successfully deleted.")):opTheme.message.error("Could not delete credential.")});return false},groupCheckbox:function(a){a=$(a.target);a.hasClass("none")&&a.is(":checked")?$("input.group-checkbox:not(.none)").removeAttr("checked"):a.is(":checked")&&$("input.group-checkbox.none").removeAttr("checked")},
groupPost:function(a){a.preventDefault();var a=$(a.target).parent().parent(),b=a.attr("action")+".json",e=b.search("create")>-1;OP.Util.makeRequest(b,a.serializeArray(),function(a){a.code===200?e?location.href=location.href:opTheme.message.confirm("Group updated successfully."):opTheme.message.error("Could not update group.")});return false},login:function(a){a=$(a.target);a.hasClass("browserid")?navigator.id.getVerifiedEmail(function(a){a?opTheme.user.browserid.loginSuccess(a):opTheme.user.browserid.loginFailure(a)}):
a.hasClass("facebook")&&FB.login(function(a){a.authResponse?(h("User logged in, posting to openphoto host."),OP.Util.makeRequest("/user/facebook/login.json",opTheme.user.base.loginProcessed)):h("User cancelled login or did not fully authorize.")},{scope:"email"})},modalClose:function(a){a.preventDefault();$(a.target).parent().slideUp("fast",function(){$(this).remove()})},photoDelete:function(a){a.preventDefault();var b=$(a.target),a=b.parent().attr("action")+".json";OP.Util.makeRequest(a,b.parent().serializeArray(),
function(a){a.code===200?(b.html("This photo has been deleted"),opTheme.message.confirm("This photo has been deleted.")):opTheme.message.error("Could not delete the photo.")});return false},photoEdit:function(a){a.preventDefault();a=$(a.target).attr("href")+".json";$("div.owner-edit").length==1?$.scrollTo($("div.owner-edit"),200):OP.Util.makeRequest(a,{},function(a){a.code===200?($("#main").append(a.result.markup),$.scrollTo($("div.owner-edit"),200),OP.Util.fire("callback:tags-autocomplete")):opTheme.message.error("Could not load the form to edit this photo.")},
"json","get");return false},photoUpdateBatch:function(a){a.preventDefault();$(a.target);var a=$("#batch-key").val(),j=$("form#batch-edit").find("*[name='value']"),j=j.length==1?j.val():$("form#batch-edit").find("*[name='value']:checked").val();params={crumb:b};params[a]=j;params.ids=OP.Batch.collection.getIds().join(",");a!=="delete"?OP.Util.makeRequest("/photos/update.json",params,opTheme.callback.photoUpdateBatchCb,"json","post"):OP.Util.makeRequest("/photos/delete.json",params,opTheme.callback.photoUpdateBatchCb,
"json","post")},photoUpdateBatchCb:function(a){a.code==200?opTheme.message.append(l("Your photos were successfully updated.","confirm")):a.code==204?(OP.Batch.clear(),opTheme.message.append(l("Your photos were successfully deleted.","confirm"))):opTheme.message.append(l("There was a problem updating your photos.","error"));$("#modal").modal("hide")},pinClick:function(a){var a=$(a.target),b=a.attr("data-id");a.parent().hasClass("unpinned")?OP.Batch.add(b):OP.Batch.remove(b)},pinClearClick:function(a){a.preventDefault();
OP.Batch.clear()},pinOver:function(a){$(a.target).parent().prev().fadeIn("fast")},pinOut:function(a){$(a.target).filter('[class~="unpinned"]').children().filter(".pin").filter(":visible").fadeOut("fast")},pluginStatus:function(a){a.preventDefault();a=$(a.target).attr("href")+".json";OP.Util.makeRequest(a,{},function(a){a.code===200?window.location.reload():opTheme.message.error("Could not update the status of this plugin.")},"json","post");return false},pluginUpdate:function(a){a.preventDefault();
var a=$(a.target).parent(),b=a.attr("action")+".json";OP.Util.makeRequest(b,a.serializeArray(),function(a){a.code===200?opTheme.message.confirm("Your plugin was successfully updated."):opTheme.message.error("Could not update the status of this plugin.")},"json","post");return false},searchByTags:function(a){a.preventDefault();var b=$(a.target).parent(),a=$(b.find("input[name=tags]")[0]).val(),b=$(b).attr("action");location.href=a.length>0?b.replace("/list","")+"/tags-"+a+"/list":b;return false},settings:function(){$("ul#settingsbar").slideToggle("medium");
$("li#nav-signin").toggleClass("active");return false},keyBrowseNext:function(){var a;if(a=$(".image-pagination .next a").attr("href"))location.href=a},keyBrowsePrevious:function(){var a;if(a=$(".image-pagination .previous a").attr("href"))location.href=a},webhookDelete:function(a){a.preventDefault();var b=$(a.target),a=b.attr("href")+".json";OP.Util.makeRequest(a,{},function(a){a.code===200?(b.parent().remove(),opTheme.message.confirm("Credential successfully deleted.")):opTheme.message.error("Could not delete credential.")});
return false}},formHandlers:{hasErrors:function(a,b){var e=[];a.children("input, textarea").each(function(){var c=$(this);c.prev().removeClass("error");var f=c.attr(b);if(f!=void 0)for(var f=f.split(" "),g=0;g<f.length;g++){if(f[g]=="date"&&!opTheme.formHandlers.passesDate(c)){var h=c.prev().html()+" is not a valid date";e.push([c,h])}f[g]=="email"&&!opTheme.formHandlers.passesEmail(c)&&(h=c.prev().html()+" is not a valid email address",e.push([c,h]));f[g]=="ifexists"&&c.val()!=""&&c.val()!=void 0&&
$.merge(e,opTheme.formHandlers.hasErrors(a,"data-ifexists"));f[g]=="integer"&&!opTheme.formHandlers.passesInteger(c)&&(h=c.prev().html()+" is not a number",e.push([c,h]));f[g]=="match"&&(h=c.attr("data-match"),opTheme.formHandlers.passesMatch(c,h)||(h=c.prev().html()+" does not match "+$("#"+h).prev().html(),e.push([c,h])));f[g]=="required"&&!opTheme.formHandlers.passesRequired(c)&&(h=c.prev().html()+" is required",e.push([c,h]));f[g]=="alphanumeric"&&!opTheme.formHandlers.passesAlphaNumeric(c)&&
(h=c.prev().html()+" can only contain alpha-numeric characters",e.push([c,h]))}});return e},init:function(){$(this).submit(opTheme.submitHandlers.siteForm);opTheme.formHandlers.showPlaceholders();$("input[data-placeholder]").live("focus",opTheme.formHandlers.placeholderFocus);$("input[data-placeholder]").live("blur",opTheme.formHandlers.placeholderBlur)},passesAlphaNumeric:function(a){return/^[a-zA-Z0-9]+$/.test(a.val())},passesDate:function(a){return/^\d{1,2}\/\d{1,2}\/\d{4}$/.test(a.val())},passesEmail:function(a){return/^([\w-\.+]+@([\w-]+\.)+[\w-]{2,4})?$/.test(a.val())},
passesInteger:function(a){return/^\d+$/.test(a.val())},passesMatch:function(a,b){return a.val()==$("#"+b).val()},passesRequired:function(a){return a.is("textarea")||a.is("input")&&(a.attr("type")=="text"||a.attr("type")=="password")?a.val()!=""&&a.val()!=void 0:a.is("checkbox")?a.is(":checked"):true},placeholderBlur:function(){var a=$(this);a.val()==""&&(a.val(a.attr("data-placeholder")),a.addClass("placeholder"))},placeholderFocus:function(){var a=$(this);a.val()==a.attr("data-placeholder")&&(a.val(""),
a.removeClass("placeholder"))},removePlaceholders:function(){$("input[data-placeholder]").each(function(){var a=$(this);a.val()==a.attr("data-placeholder")&&(a.val(""),a.removeClass("placeholder"))})},showPlaceholders:function(){$("input[data-placeholder]").each(function(){var a=$(this);a.val()==""&&(a.val(a.attr("data-placeholder")),a.addClass("placeholder"))})}},front:{init:function(a){a.length>0&&a.cycle({fx:"fade"}).find("img").click(function(a){location.href=$(a.target).attr("data-origin")})}},
init:{load:function(a){b=a;$("section#slideshow").length>0&&$(window).load(function(){$(".flexslider").flexslider({animation:"slide",controlsContainer:".flex-container",controlNav:true,pausePlay:false,directionNav:true,nextText:"<span title='Next'>Next</span>",prevText:"<span title='Previous'>Previous</span>"})});$("#modal").modal({keyboard:true})},attach:function(){OP.Util.on("click:action-delete",opTheme.callback.actionDelete);OP.Util.on("click:action-jump",opTheme.callback.commentJump);OP.Util.on("click:batch-modal",
opTheme.callback.batchModal);OP.Util.on("click:credential-delete",opTheme.callback.credentailDelete);OP.Util.on("click:group-checkbox",opTheme.callback.groupCheckbox);OP.Util.on("click:group-update",opTheme.callback.groupPost);OP.Util.on("click:login",opTheme.callback.login);OP.Util.on("click:modal-close",opTheme.callback.modalClose);OP.Util.on("click:photo-delete",opTheme.callback.photoDelete);OP.Util.on("click:photo-edit",opTheme.callback.photoEdit);OP.Util.on("click:photo-update-batch",opTheme.callback.photoUpdateBatch);
OP.Util.on("click:plugin-status",opTheme.callback.pluginStatus);OP.Util.on("click:plugin-update",opTheme.callback.pluginUpdate);OP.Util.on("click:nav-item",opTheme.callback.searchBarToggle);OP.Util.on("click:search",opTheme.callback.searchByTags);OP.Util.on("click:settings",opTheme.callback.settings);OP.Util.on("click:webhook-delete",opTheme.callback.webhookDelete);OP.Util.on("click:pin",opTheme.callback.pinClick);OP.Util.on("click:pin-clear",opTheme.callback.pinClearClick);OP.Util.on("keydown:browse-next",
opTheme.callback.keyBrowseNext);OP.Util.on("keydown:browse-previous",opTheme.callback.keyBrowsePrevious);OP.Util.on("mouseover:pin",opTheme.callback.pinOver);OP.Util.on("mouseout:pin",opTheme.callback.pinOut);OP.Util.on("change:batch-field",opTheme.callback.batchField);OP.Util.on("callback:tags-autocomplete",opTheme.init.tags.autocomplete);OP.Util.on("callback:batch-add",opTheme.callback.batchAdd);OP.Util.on("callback:batch-remove",opTheme.callback.batchRemove);OP.Util.on("callback:batch-clear",opTheme.callback.batchClear);
OP.Util.fire("callback:tags-autocomplete");typeof OPU==="object"&&OPU.init();$("form.validate").each(opTheme.formHandlers.init)},photos:function(){var a=OP.Batch.collection.getAll(),b=OP.Batch.collection.getLength(),e=$(".unpinned"),c,f;b>0&&opTheme.ui.batchMessage();e.each(function(b,e){e=$(e);c=e.attr("class");f=c.match(/ id-([a-z0-9]+)/);f.length==2&&a[f[1]]!==void 0&&e.removeClass("unpinned").addClass("pinned")})},tags:{autocomplete:function(){var b={queryParam:"search",propertyToSearch:"id",
preventDuplicates:true};b.onResult=a;b.resultsFormatter=g;$("input[class~='tags-autocomplete']").each(function(a,e){var e=$(e),c=e.attr("value");if(e.css("display")!="none"){if(c!=""){for(var c=c.split(","),f=[],a=0;a<c.length;a++)f.push({id:c[a],name:c[a]});b.prePopulate=f}$(e).tokenInput("/tags/list.json",b)}})}}},message:{append:function(a){$("#message").append(a).slideDown()},close:function(){f!=void 0&&(clearTimeout(f),f=void 0,$("#message-box").animate({height:"toggle"},500,function(){$("#message-box").remove()}))},
confirm:function(a){opTheme.message.show(a,"confirm")},error:function(a){opTheme.message.show(a,"error")},show:function(a,b){var e=b=="error"?"confirm":"error";f!=void 0?(clearTimeout(f),f=void 0,$("#message-box").removeClass(e).addClass(b).html('<div><a class="message-close">close</a>'+a+"</div>"),f=setTimeout(function(){$("#message-box").animate({height:"toggle"},500,function(){$("#message-box").remove();f=void 0})},7E3)):($("html").append('<section id="message-box" style="display:none;"><div><a class="message-close">close</a>'+
a+"</div></section>"),$("#message-box").removeClass(e).addClass(b).animate({height:"toggle"},500,function(){f=setTimeout(function(){$("#message-box").animate({height:"toggle"},500,function(){$("#message-box").remove();f=void 0})},7E3)}));$("a.message-close").click(opTheme.message.close)}},submitHandlers:{siteForm:function(a){var b=$(this);a.preventDefault();opTheme.formHandlers.removePlaceholders();a=opTheme.formHandlers.hasErrors(b,"data-validation");opTheme.formHandlers.showPlaceholders();if(a.length==
0)this.submit();else{for(var b="<ul>",e=0;e<a.length;e++)a[e][0].prev().addClass("error"),b+="<li>"+a[e][1]+"</li>";b+="</ul>";$("html").animate({scrollTop:a[0][0].offset().top-30},500);a[0][0].focus();opTheme.message.error(b)}}},ui:{batchFormFields:{empty:function(){return""},permission:function(){return' <div class="clearfix"> <label>Value</label> <div class="input"> <ul class="inputs-list"> <li> <label> <input type="radio" name="value" value="1" checked="checked"> <span>Public</span> </label> </li> <li> <label> <input type="radio" name="value" value="0"> <span>Private</span> </label> </li> </div> </div>'},
tags:function(){return' <div class="clearfix"> <label>Tags</label> <div class="input"> <input type="text" name="value" class="tags-autocomplete" placeholder="A comma separated list of tags" value=""> </div> </div>'}},batchMessage:function(){var a=OP.Batch.collection.getLength();$("#batch-message").length>0&&a>0?$("#batch-count").html(a):a==0?$("#batch-message").parent().slideUp("fast",function(){$(this).remove()}):opTheme.message.append(l(' <a id="batch-message"></a>You have <span id="batch-count">'+
a+'</span> photos pinned. <div class="alert-actions"><a class="btn small info batch-modal-click" data-controls-modal="modal" data-backdrop="static">Batch edit</a><a href="#" class="btn small pin-clear-click">Or clear pins</a></div>'))}},user:{base:{loginProcessed:function(a){a.code!=200?h("processing of login failed"):(h("login processing succeeded"),window.location.reload())}},browserid:{loginFailure:function(){h("login failed")},loginSuccess:function(a){OP.Util.makeRequest("/user/browserid/login.json",
{assertion:a},opTheme.user.base.loginProcessed)}}}}}();

View file

@ -73,7 +73,7 @@ var opTheme = (function() {
opTheme.ui.batchMessage();
log("Adding photo " + photo.id);
},
batchClear: function(photo) {
batchClear: function() {
var el = $("#batch-message").parent();
$(".pinned").removeClass("pinned").addClass("unpinned").children().filter(".pin").fadeOut();
el.slideUp('fast', function(){ $(this).remove(); });
@ -83,6 +83,9 @@ var opTheme = (function() {
val = el.val(),
tgt = $("form#batch-edit .form-fields");
switch(val) {
case 'delete':
tgt.html(opTheme.ui.batchFormFields.empty());
break;
case 'permission':
tgt.html(opTheme.ui.batchFormFields.permission());
break;
@ -106,12 +109,13 @@ var opTheme = (function() {
' <option value="tagsAdd">Add Tags</option>' +
' <option value="tagsRemove">Remove Tags</option>' +
' <option value="permission">Permission</option>' +
' <option value="delete">Delete</option>' +
' </select>' +
' </div>' +
' </div>' +
' <div class="form-fields">'+opTheme.ui.batchFormFields.tags()+'</div>' +
'</form>',
'<a href="#" class="btn photo-update-batch-click">Update</a>'
'<a href="#" class="btn photo-update-batch-click">Submit</a>'
);
el.html(html);
OP.Util.fire('callback:tags-autocomplete');
@ -246,12 +250,18 @@ var opTheme = (function() {
params = {'crumb':crumb};
params[key] = value;
params['ids'] = OP.Batch.collection.getIds().join(',');
log(params);
OP.Util.makeRequest('/photos/update.json', params, opTheme.callback.photoUpdateBatchCb, 'json', 'post');
if(key !== 'delete') {
OP.Util.makeRequest('/photos/update.json', params, opTheme.callback.photoUpdateBatchCb, 'json', 'post');
} else {
OP.Util.makeRequest('/photos/delete.json', params, opTheme.callback.photoUpdateBatchCb, 'json', 'post');
}
},
photoUpdateBatchCb: function(response) {
if(response.code == 200) {
opTheme.message.append(messageMarkup('Your photos were successfully updated.', 'confirm'));
} else if(response.code == 204) {
OP.Batch.clear();
opTheme.message.append(messageMarkup('Your photos were successfully deleted.', 'confirm'));
} else {
opTheme.message.append(messageMarkup('There was a problem updating your photos.', 'error'));
}
@ -687,6 +697,9 @@ var opTheme = (function() {
},
ui: {
batchFormFields: {
empty: function() {
return '';
},
permission: function() {
return ' <div class="clearfix">' +
' <label>Value</label>' +

View file

@ -1609,7 +1609,7 @@ aside.sidebar .meta li span {
top:2px;
left:0;
position:absolute;
width:15px;
width:20px;
}
aside.sidebar .meta li.date span {
@ -1631,6 +1631,9 @@ aside.sidebar .meta li.license span {
aside.sidebar .meta li.location span {
background:transparent url("../images/icons.png") 0 -626px no-repeat;
}
aside.sidebar .meta li.original span {
background:transparent url("../images/icons.png") 0px 0px no-repeat;
}
aside.sidebar .meta li.location img {
-webkit-border-radius: 2px;

View file

@ -1,3 +1,17 @@
<?php /*
<?php if(count($activities) > 0) { ?>
<ul>
<?php foreach($activities as $key => $activityGrp) { ?>
<li>
<?php if(preg_match('/photo-upload|photo-update|action-create/', $key)) { ?>
<?php $this->theme->display(sprintf('partials/feed-%s.php', $activityGrp[0]['type']), array('activities' => $activityGrp)); ?>
<?php } ?>
</li>
<?php } ?>
</ul>
<?php } ?>
<?php */ ?>
<?php if($photos[0]['totalRows'] == 0) { ?>
<?php if($this->user->isOwner()) { ?>
<a href="<?php $this->url->photoUpload(); ?>" class="link" title="Start uploading now!"><img src="<?php $this->theme->asset('image', 'front.png'); ?>" class="front" /></a>

View file

@ -0,0 +1,6 @@
<?php foreach($activities as $activity) { ?>
<?php if($activity['data']['action']['type'] == 'comment') {?>
<?php $this->utility->safe($activity['data']['action']['value']); ?>
<img src="<?php $this->url->photoUrl($activity['data']['target'], '100x100xCR'); ?>" width="50" height="50">
<?php } ?>
<?php } ?>

View file

@ -0,0 +1,4 @@
The following photos were updated.
<?php foreach($activities as $activity) { ?>
<img src="<?php $this->url->photoUrl($activity['data'], '100x100xCR'); ?>" width="50" height="50">
<?php } ?>

View file

@ -0,0 +1,4 @@
The following photos were uploaded.
<?php foreach($activities as $activity) { ?>
<a href="<?php $this->url->photoView($activity['data']['id']); ?>"><img src="<?php $this->url->photoUrl($activity['data'], '100x100xCR'); ?>" width="50" height="50"></a>
<?php } ?>

View file

@ -132,15 +132,17 @@
<?php if(!empty($photo[$key])) { ?>
<li><?php printf($value, $this->utility->safe($photo[$key], false)); ?></li>
<?php } ?>
<?php } ?>
</ul>
</li>
</ul>
</li>
<?php } ?>
<?php if(isset($photo['pathOriginal'])) { ?>
<li class="original"><span></span><a href="<?php $this->utility->safe($photo['pathOriginal']); ?>">Download original</a></li>
<?php } ?>
<?php if($this->user->isOwner()) { ?>
<li class="edit">
<span></span>
<a href="<?php $this->url->photoEdit($photo['id']); ?>" class="button photo-edit-click">Edit this photo</a>
</li>
<li class="edit">
<span></span>
<a href="<?php $this->url->photoEdit($photo['id']); ?>" class="button photo-edit-click">Edit this photo</a>
</li>
<?php } ?>
</ul>
</aside>

View file

@ -19,7 +19,7 @@
<link rel="stylesheet" href="<?php echo getAssetPipeline(true)->addCss($this->theme->asset('stylesheet', 'bootstrap.min.css', false))->
addCss("/assets/stylesheets/upload.css")->
addCss($this->theme->asset('stylesheet', 'main.css', false))->
getUrl(AssetPipeline::css, 'd'); ?>">
getUrl(AssetPipeline::css, 'e'); ?>">
<?php } ?>
<?php $this->plugin->invoke('onHead', array('page' => $page)); ?>
@ -130,7 +130,7 @@
<?php } else { ?>
'<?php echo getAssetPipeline(true)->addJs('/assets/javascripts/openphoto-batch.min.js')->
addJs($this->theme->asset('javascript', 'openphoto-theme-full-min.js', false))->
getUrl(AssetPipeline::js, 'd'); ?>'
getUrl(AssetPipeline::js, 'e'); ?>'
<?php } ?>
],
onComplete: function(){

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -4,21 +4,41 @@ var opTheme = (function() {
console.log(msg);
};
return{
init: {
attach: function(PhotoSwipe) {
$('div.gallery-page').live('pageshow', function(e){
var
currentPage = $(e.target),
photoSwipeInstanceId = parseInt(Math.random()*10000),
photoSwipeInstance = PhotoSwipe.getInstance(photoSwipeInstanceId)
options = {};
if ($("ul.gallery a").length > 0 && (typeof photoSwipeInstance === "undefined" || photoSwipeInstance === null)) {
photoSwipeInstance = $("ul.gallery a", e.target).photoSwipe(options, photoSwipeInstanceId);
}
return true;
callback: {
login: function(el) {
navigator.id.getVerifiedEmail(function(assertion) {
if (assertion) {
opTheme.user.loginSuccess(assertion);
} else {
opTheme.user.loginFailure(assertion);
}
});
}
},
init: {
attach: function() {
OP.Util.on('click:login', opTheme.callback.login);
}
},
user: {
loginFailure: function(assertion) {
log('login failed');
// TODO something here to handle failed login
},
loginProcessed: function(response) {
if(response.code != 200) {
log('processing of login failed');
// TODO do something here to handle failed login
return;
}
log('login processing succeeded');
window.location.reload();
},
loginSuccess: function(assertion) {
var params = {assertion: assertion};
OP.Util.makeRequest('/user/browserid/login.json', params, opTheme.user.loginProcessed, 'json');
}
}
};
}());

View file

@ -472,6 +472,6 @@ var opTheme = (function() {
var params = {assertion: assertion};
OP.Util.makeRequest('/user/browserid/login.json', params, opTheme.user.loginProcessed, 'json');
}
},
}
};
}());

File diff suppressed because one or more lines are too long

View file

@ -116,6 +116,10 @@
background: url(../images/icon-globe.png) left 2px no-repeat;
}
.photo-details .original {
background: url(../images/header-navigation-photos.png) center left no-repeat;
}
.photo-details .location img {
display: block;
}

View file

@ -8,6 +8,16 @@
</div>
</form>
<ul data-role="listview" data-inset="true">
<?php if($this->user->isLoggedIn()) { ?>
<li data-icon="delete"><a href="/user/logout" rel="external"><img
src="<?php echo $this->user->getAvatarFromEmail(16, $this->user->getEmailAddress()); ?>"
class="ui-li-icon ui-corner-none"><?php $this->utility->safe($this->user->getEmailAddress()); ?></a></li>
<li></li>
<?php } else { ?>
<li><a href="#" class="login-click"><img
src="<?php $this->theme->asset('image', 'header-navigation-login.png'); ?>" alt="my photos"
class="ui-li-icon">Login</a></li>
<?php } ?>
<li><a href="/photos/list"><img
src="<?php $this->theme->asset('image', 'header-navigation-photos.png'); ?>" alt="my photos"
class="ui-li-icon">Photos</a></li>

View file

@ -26,6 +26,9 @@
<li class="date"><?php $this->utility->dateLong($photo['dateTaken']); ?></li>
<li class="heart"><?php echo count($photo['actions']); ?> favorites &amp; comments - <a href="#comments" class="action-jump-click">see all</a></li>
<li class="tags"><?php $this->url->tagsAsLinks($photo['tags']); ?></li>
<?php if(isset($photo['pathOriginal'])) { ?>
<li class="original"><span></span><a href="<?php $this->utility->safe($photo['pathOriginal']); ?>">Download original</a></li>
<?php } ?>
<?php if($this->utility->licenseLink($photo['license'], false)) { ?>
<a rel="license" href="<?php $this->utility->licenseLink($photo['license']); ?>">
<?php $this->utility->licenseLong($photo['license']); ?>

View file

@ -17,25 +17,55 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="<?php $this->theme->asset('image', 'favicon.ico'); ?>">
<link rel="apple-touch-icon" href="<?php $this->theme->asset('image', 'apple-touch-icon.png'); ?>">
<link rel="stylesheet" href="<?php $this->theme->asset('stylesheet', 'jquery.mobile.css'); ?>">
<link rel="stylesheet" href="<?php $this->theme->asset('stylesheet', 'photoswipe.css'); ?>">
<link rel="stylesheet" href="<?php $this->theme->asset('stylesheet', 'pagination.css'); ?>">
<link rel="stylesheet" href="<?php $this->theme->asset('stylesheet', 'mobile.css'); ?>">
<?php if($this->config->site->mode === 'dev') { ?>
<link rel="stylesheet" href="<?php $this->theme->asset('stylesheet', 'jquery.mobile.css'); ?>">
<link rel="stylesheet" href="<?php $this->theme->asset('stylesheet', 'photoswipe.css'); ?>">
<link rel="stylesheet" href="<?php $this->theme->asset('stylesheet', 'pagination.css'); ?>">
<link rel="stylesheet" href="<?php $this->theme->asset('stylesheet', 'mobile.css'); ?>">
<?php } else { ?>
<link rel="stylesheet" href="<?php echo getAssetPipeline(true)->addCss($this->theme->asset('stylesheet', 'jquery.mobile.css', false))->
addCss($this->theme->asset('stylesheet', 'photoswipe.css', false))->
addCss($this->theme->asset('stylesheet', 'pagination.css', false))->
addCss($this->theme->asset('stylesheet', 'mobile.css', false))->
getUrl(AssetPipeline::css, 'a'); ?>">
<?php } ?>
<?php $this->plugin->invoke('onHead', array('page' => $page)); ?>
</head>
<body class="<?php echo $page; ?>">
<?php $this->plugin->invoke('onBodyBegin', array('page' => $page)); ?>
<?php echo $body; ?>
<script type="text/javascript" src="<?php $this->theme->asset($this->config->dependencies->javascript); ?>"></script>
<script type="text/javascript" src="<?php $this->theme->asset('util'); ?>"></script>
<?php if($this->config->site->mode === 'dev') { ?>
<script type="text/javascript" src="<?php $this->theme->asset($this->config->dependencies->javascript); ?>"></script>
<script type="text/javascript" src="<?php $this->theme->asset('util'); ?>"></script>
<?php } else { ?>
<script type="text/javascript" src="<?php echo getAssetPipeline(true)->setMode(AssetPipeline::combined)->
addJs($this->theme->asset('util', null, false))->
addJs($this->theme->asset($this->config->dependencies->javascript, null, false))->
getUrl(AssetPipeline::js, 'e'); ?>"></script>
<?php } ?>
<script>
OP.Util.init(jQuery, {
eventMap: {
click: {
'login-click':'click:login'
}
},
js: {
assets: [
<?php if($this->config->site->mode === 'dev') { ?>
'<?php $this->theme->asset('javascript', 'jquery.mobile.js'); ?>',
'<?php $this->theme->asset('javascript', 'openphoto-theme-mobile.js'); ?>'
]
}
<?php } else { ?>
'<?php echo getAssetPipeline(true)->setMode(AssetPipeline::combined)->
addJs($this->theme->asset('javascript', 'jquery.mobile.js', false))->
addJs($this->theme->asset('javascript', 'openphoto-theme-mobile.js', false))->
getUrl(AssetPipeline::js, 'b'); ?>'
<?php } ?>
],
onComplete: function(){ opTheme.init.attach(); }
},
});
</script>
<?php $this->plugin->invoke('onBodyEnd', array('page' => $page)); ?>

View file

@ -20,6 +20,8 @@ interface DatabaseInterface
public function deleteWebhook($id);
// get methods read
public function getAction($id);
public function getActivities();
public function getActivity($id);
public function getCredential($id);
public function getCredentialByUserToken($userToken);
public function getCredentials();
@ -48,6 +50,7 @@ interface DatabaseInterface
// put methods create but do not update
public function putGroup($id, $params);
public function putAction($id, $params);
public function putActivity($id, $params);
public function putCredential($id, $params);
public function putPhoto($id, $params);
public function putUser($params);

View file

@ -163,6 +163,41 @@ class DatabaseMySql implements DatabaseInterface
return include $file;
}
/**
* Retrieves activity
*
* @return mixed Array on success, FALSE on failure
*/
public function getActivity($id)
{
$activity = $this->db->all("SELECT * FROM `{$this->mySqlTablePrefix}activity` WHERE `id`=:id AND `owner`=:owner",
array(':id' => $id, ':owner' => $this->owner));
if($activity === false)
return false;
$activity = $this->normalizeActivity($activity);
return $activity;
}
/**
* Retrieves activities
*
* @return mixed Array on success, FALSE on failure
*/
public function getActivities()
{
$activities = $this->db->all("SELECT * FROM `{$this->mySqlTablePrefix}activity` WHERE `owner`=:owner",
array(':owner' => $this->owner));
if($activities === false)
return false;
foreach($activities as $key => $activity)
$activities[$key] = $this->normalizeActivity($activity);
return $activities;
}
/**
* Retrieve an action with $id
*
@ -174,10 +209,9 @@ class DatabaseMySql implements DatabaseInterface
$action = $this->db->one("SELECT * FROM `{$this->mySqlTablePrefix}action` WHERE `id`=:id AND owner=:owner",
array(':id' => $id, ':owner' => $this->owner));
if(empty($action))
{
return false;
}
return $this->normalizeCredential($action);
return $this->normalizeAction($action);
}
/**
@ -364,7 +398,7 @@ class DatabaseMySql implements DatabaseInterface
if(!empty($actions))
{
foreach($actions as $action)
$photo['actions'][] = $action;
$photo['actions'][] = $this->normalizeAction($action);
}
}
return $photo;
@ -773,6 +807,21 @@ class DatabaseMySql implements DatabaseInterface
return ($res == 1);
}
/**
* Add a new activity to the database
* This method does not overwrite existing values present in $params - hence "new action".
*
* @param string $id ID of the action to update which is always 1.
* @param array $params Attributes to update.
* @return boolean
*/
public function putActivity($id, $params)
{
$stmt = $this->sqlInsertExplode($this->prepareActivity($params));
$result = $this->db->execute("INSERT INTO `{$this->mySqlTablePrefix}activity` (id,{$stmt['cols']}) VALUES (:id,{$stmt['vals']})", array(':id' => $id));
return ($result !== false);
}
/**
* Add a new action to the database
* This method does not overwrite existing values present in $params - hence "new action".
@ -1149,6 +1198,18 @@ class DatabaseMySql implements DatabaseInterface
return $versions;
}
/**
* Normalizes data from MySql into schema definition
*
* @param SimpleXMLObject $raw An action from SimpleDb in SimpleXML.
* @return array
*/
private function normalizeActivity($raw)
{
$raw['data'] = json_decode($raw['data'], 1);
return $raw;
}
/**
* Normalizes data from MySql into schema definition
*
@ -1157,7 +1218,7 @@ class DatabaseMySql implements DatabaseInterface
*/
private function normalizeAction($raw)
{
// TODO shouldn't we require and use this method?
return $raw;
}
/**
@ -1249,6 +1310,16 @@ class DatabaseMySql implements DatabaseInterface
return $raw;
}
/** Prepare activity to store in the database
*/
private function prepareActivity($params)
{
if(isset($params['data']))
$params['data'] = json_encode($params['data']);
return $params;
}
/** Prepare credential to store in the database
*/
private function prepareCredential($params)

View file

@ -11,7 +11,7 @@ class DatabaseSimpleDb implements DatabaseInterface
* Member variables holding the names to the SimpleDb domains needed and the database object itself.
* @access private
*/
private $config, $db, $domainAction, $domainCredential, $domainPhoto,
private $config, $db, $domainAction, $domainActivity, $domainCredential, $domainPhoto,
$domainTag, $domainUser, $domainWebhook, $errors = array(), $owner;
/**
@ -32,6 +32,7 @@ class DatabaseSimpleDb implements DatabaseInterface
$this->domainPhoto = $this->config->aws->simpleDbDomain;
$this->domainAction = $this->config->aws->simpleDbDomain.'Action';
$this->domainActivity = $this->config->aws->simpleDbDomain.'Activity';
$this->domainCredential = $this->config->aws->simpleDbDomain.'Credential';
$this->domainGroup = $this->config->aws->simpleDbDomain.'Group';
$this->domainUser = $this->config->aws->simpleDbDomain.'User';
@ -200,6 +201,36 @@ class DatabaseSimpleDb implements DatabaseInterface
return false;
}
/**
* Retrieves activities
*
* @return mixed Array on success, FALSE on failure
*/
public function getActivities()
{
$res = $this->db->select("SELECT * FROM `{$this->domainActivities}`", array('ConsistentRead' => 'true'));
$this->logErrors($res);
if(isset($res->body->SelectResult->Item))
return self::normalizeActivity($res->body->SelectResult->Item);
else
return false;
}
/**
* Retrieves activity
*
* @return mixed Array on success, FALSE on failure
*/
public function getActivity($id)
{
$res = $this->db->select("SELECT * FROM `{$this->domainActivities}` WHERE itemName()='{$id}'", array('ConsistentRead' => 'true'));
$this->logErrors($res);
if(isset($res->body->SelectResult->Item))
return self::normalizeActivity($res->body->SelectResult->Item);
else
return false;
}
/**
* Retrieve a credential with $id
*
@ -709,6 +740,20 @@ class DatabaseSimpleDb implements DatabaseInterface
$this->logErrors($res);
return $res->isOK();
}
/**
* Add a new activity to the database
* This method does not overwrite existing values present in $params - hence "new action".
*
* @param string $id ID of the action to update which is always 1.
* @param array $params Attributes to update.
* @return boolean
*/
public function putActivity($id, $params)
{
$res = $this->db->put_attributes($this->domainActivity, $id, $params);
$this->logErrors($res);
return $res->isOK();
}
/**
* Add a new credential to the database
@ -948,6 +993,26 @@ class DatabaseSimpleDb implements DatabaseInterface
return $action;
}
/**
* Normalizes data from simpleDb into schema definition
*
* @param SimpleXMLObject $raw An action from SimpleDb in SimpleXML.
* @return array
*/
private function normalizeActivity($raw)
{
$activity = array();
$activity['id'] = strval($raw->Name);
$activity['appId'] = $this->config->application->appId;
foreach($raw->Attribute as $item)
{
$name = (string)$item->Name;
$value = (string)$item->Value;
$activity[$name] = $value;
}
return $activity;
}
/**
* Normalizes data from simpleDb into schema definition
*

View file

@ -0,0 +1,28 @@
<?php
// parse_ini_string is >= 5.3
if(!function_exists('parse_ini_string')){
function parse_ini_string($str, $ProcessSections=false){
$lines = explode("\n", $str);
$return = Array();
$inSect = false;
foreach($lines as $line){
$line = trim($line);
if(!$line || $line[0] == "#" || $line[0] == ";")
continue;
if($line[0] == "[" && $endIdx = strpos($line, "]")){
$inSect = substr($line, 1, $endIdx-1);
continue;
}
if(!strpos($line, '=')) // (We don't use "=== false" because value 0 is not valid as well)
continue;
$tmp = explode("=", $line, 2);
$tmp[1] = preg_replace(array('/^"/', '/"$/'), '', $tmp[1]);
if($ProcessSections && $inSect)
$return[$inSect][trim($tmp[0])] = ltrim($tmp[1]);
else
$return[trim($tmp[0])] = ltrim($tmp[1]);
}
return $return;
}
}

View file

@ -14,6 +14,7 @@ class ActionController extends BaseController
public function __construct()
{
parent::__construct();
$this->authentication = getAuthentication();
}
/**
@ -26,8 +27,9 @@ class ActionController extends BaseController
*/
public function create($targetId, $targetType)
{
getAuthentication()->requireAuthentication(false);
getAuthentication()->requireCrumb($_POST['crumb']);
// does not need to be owner, anyone can comment
$this->authentication->requireAuthentication(false);
$this->authentication->requireCrumb($_POST['crumb']);
$res = $this->api->invoke(sprintf('%s.json', $this->url->actionCreate($targetId, $targetType, false)), EpiRoute::httpPost);
$result = $res ? '1' : '0';
// TODO: standardize messaging parameter

View file

@ -40,6 +40,11 @@ class ApiActionController extends ApiBaseController
{
$action = $this->action->view($id);
getPlugin()->invoke('onAction', $action);
// get the target element for the action
$apiResp = $this->api->invoke("/{$targetType}/{$targetId}/view.json", EpiRoute::httpGet, array('_GET' => array('returnSizes' => '100x100xCR')));
$target = $apiResp['result'];
$activityParams = array('type' => 'action-create', 'data' => array('targetType' => $targetType, 'target' => $target, 'action' => $action), 'permission' => $target['permission']);
$this->api->invoke('/activity/create.json', EpiRoute::httpPost, array('_POST' => $activityParams));
return $this->success("Action {$id} created on {$targetType} {$targetId}", $action);
}

View file

@ -0,0 +1,74 @@
<?php
/**
* Activity controller for API endpoints
*
* This controller handles all activity endpoints
* @author Jaisen Mathai <jaisen@jmathai.com>
*/
class ApiActivityController extends ApiBaseController
{
/**
* Call the parent constructor
*
* @return void
*/
public function __construct()
{
parent::__construct();
$this->activity = new Activity;
}
public function create()
{
$status = $this->activity->create($_POST);
if($status !== false)
return $this->success('Created activity for user', true);
else
return $this->error('Could not create activities', false);
}
public function list_()
{
$activities = $this->activity->list_();
if(isset($_GET['groupBy']))
$activities = $this->groupActivities($activities, $_GET['groupBy']);
if($activities !== false)
return $this->success("User's list of activities", $activities);
else
return $this->error('Could not get activities', false);
}
public function view($id)
{
$activity = $this->activity->view($id);
if($activity !== false)
return $this->success("Activity {$id}", $activity);
else
return $this->error('Could not get activity', false);
}
protected function groupActivities($activities, $groupBy)
{
switch($groupBy)
{
case 'hour':
$fmt = 'YmdH';
break;
case 'day':
default:
$fmt = 'Ymd';
break;
}
$return = array();
foreach($activities as $activity)
{
$grp = sprintf('%s-%s', date($fmt, $activity['dateCreated']), $activity['type']);
$return[$grp][] = $activity;
}
return $return;
}
}

View file

@ -12,6 +12,7 @@ class ApiBaseController
const statusError = 500;
const statusSuccess = 200;
const statusCreated = 201;
const statusNoContent = 204;
const statusForbidden = 403;
const statusNotFound = 404;
@ -28,7 +29,7 @@ class ApiBaseController
}
/**
* Created, HTTP 202
* Created, HTTP 201
*
* @param string $message A friendly message to describe the operation
* @param mixed $result The result with values needed by the caller to take action.
@ -36,7 +37,19 @@ class ApiBaseController
*/
public function created($message, $result = null)
{
return self::json($message, self::statusCreated, $result);
return $this->json($message, self::statusCreated, $result);
}
/**
* No content, HTTP 204
*
* @param string $message A friendly message to describe the operation
* @param mixed $result The result with values needed by the caller to take action.
* @return string Standard JSON envelope
*/
public function noContent($message, $result = null)
{
return $this->json($message, self::statusNoContent, $result);
}
/**
@ -48,7 +61,7 @@ class ApiBaseController
*/
public function error($message, $result = null)
{
return self::json($message, self::statusError, $result);
return $this->json($message, self::statusError, $result);
}
/**
@ -60,7 +73,7 @@ class ApiBaseController
*/
public function success($message, $result = null)
{
return self::json($message, self::statusSuccess, $result);
return $this->json($message, self::statusSuccess, $result);
}
/**
@ -72,7 +85,7 @@ class ApiBaseController
*/
public function forbidden($message, $result = null)
{
return self::json($message, self::statusForbidden, $result);
return $this->json($message, self::statusForbidden, $result);
}
/**
@ -84,7 +97,7 @@ class ApiBaseController
*/
public function notFound($message, $result = null)
{
return self::json($message, self::statusNotFound, $result);
return $this->json($message, self::statusNotFound, $result);
}
/**

View file

@ -21,7 +21,7 @@ class ApiOAuthController extends ApiBaseController
getAuthentication()->requireAuthentication();
$res = getDb()->deleteCredential($id);
if($res)
return $this->success('Oauth credential deleted', true);
return $this->noContent('Oauth credential deleted', true);
else
return $this->error('Could not delete credential', false);
}

View file

@ -35,7 +35,7 @@ class ApiPhotoController extends ApiBaseController
if($status)
{
$this->tag->updateTagCounts($res['result']['tags'], array(), 1, 1);
return $this->success('Photo deleted successfully', true);
return $this->noContent('Photo deleted successfully', true);
}
else
{
@ -43,6 +43,35 @@ class ApiPhotoController extends ApiBaseController
}
}
/**
* Delete multiple photos
*
* @return string Standard JSON envelope
*/
public function deleteBatch()
{
getAuthentication()->requireAuthentication();
getAuthentication()->requireCrumb();
if(!isset($_POST['ids']) || empty($_POST['ids']))
return $this->error('This API requires an ids parameter.', false);
$ids = (array)explode(',', $_POST['ids']);
$params = $_POST;
unset($params['ids']);
$retval = true;
foreach($ids as $id)
{
$response = $this->api->invoke("/photo/{$id}/delete.json", EpiRoute::httpPost, array('_POST' => $params));
$retval = $retval && $response['result'] !== false;
}
if($retval)
return $this->noContent(sprintf('%d photos deleted', count($ids)), true);
else
return $this->error('Error deleting one or more photos', false);
}
/**
* Get a form to edit a photo specified by the ID.
*
@ -260,11 +289,33 @@ class ApiPhotoController extends ApiBaseController
}
elseif(isset($_POST['photo']))
{
unset($attributes['photo']);
// if a filename is passed in we use it else it's the random temp name
$localFile = tempnam($this->config->paths->temp, 'opme');
$name = basename($localFile).'.jpg';
file_put_contents($localFile, base64_decode($_POST['photo']));
$photoId = $this->photo->upload($localFile, $name, $attributes);
if(isset($_POST['filename']))
$name = $_POST['filename'];
else
$name = basename($localFile).'.jpg';
// if we have a path to a photo we download it
// else we base64_decode it
if(preg_match('#https?://#', $_POST['photo']))
{
unset($attributes['photo']);
$fp = fopen($localFile, 'w');
$ch = curl_init($_POST['photo']);
curl_setopt($ch, CURLOPT_FILE, $fp);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$data = curl_exec($ch);
curl_close($ch);
fclose($fp);
$photoId = $this->photo->upload($localFile, $name, $attributes);
}
else
{
unset($attributes['photo']);
file_put_contents($localFile, base64_decode($_POST['photo']));
$photoId = $this->photo->upload($localFile, $name, $attributes);
}
}
if($photoId)
@ -272,23 +323,28 @@ class ApiPhotoController extends ApiBaseController
if(isset($returnSizes))
{
$sizes = (array)explode(',', $returnSizes);
foreach($sizes as $size)
{
$options = $this->photo->generateFragmentReverse($size);
$hash = $this->photo->generateHash($photoId, $options['width'], $options['height'], $options['options']);
$this->photo->generate($photoId, $hash, $options['width'], $options['height'], $options['options']);
}
if(!in_array('100x100xCR', $sizes))
$sizes[] = '100x100xCR';
}
else
{
$sizes = array('100x100xCR');
}
$params = array();
if(isset($returnSizes))
$params = array('returnSizes' => $returnSizes);
$photo = $this->api->invoke("/photo/{$photoId}/view.json", EpiRoute::httpGet, array('_GET' => $params));
foreach($sizes as $size)
{
$options = $this->photo->generateFragmentReverse($size);
$hash = $this->photo->generateHash($photoId, $options['width'], $options['height'], $options['options']);
$this->photo->generate($photoId, $hash, $options['width'], $options['height'], $options['options']);
}
$apiResp = $this->api->invoke("/photo/{$photoId}/view.json", EpiRoute::httpGet, array('_GET' => array('returnSizes' => implode(',', $sizes))));
$photo = $apiResp['result'];
$webhookApi = $this->api->invoke('/webhooks/photo.upload/list.json', EpiRoute::httpGet);
if(!empty($webhookApi['result']) && is_array($webhookApi['result']))
{
$photoAsArgs = $photo['result'];
$photoAsArgs = $photo;
$photoAsArgs['tags'] = implode(',', $photoAsArgs['tags']);
foreach($webhookApi['result'] as $key => $hook)
{
@ -296,7 +352,14 @@ class ApiPhotoController extends ApiBaseController
$this->logger->info(sprintf('Webhook callback executing for photo.upload: %s', $hook['callback']));
}
}
return $this->created("Photo {$photoId} uploaded successfully", $photo['result']);
$permission = isset($attributes['permission']) ? $attributes['permission'] : 0;
$this->api->invoke(
'/activity/create.json',
EpiRoute::httpPost,
array('_POST' => array('type' => 'photo-upload', 'data' => $photo, 'permission' => $permission))
);
return $this->created("Photo {$photoId} uploaded successfully", $photo);
}
return $this->error('File upload failure', false);
@ -358,8 +421,10 @@ class ApiPhotoController extends ApiBaseController
if($photoUpdatedId)
{
$photo = $this->api->invoke("/photo/{$id}/view.json", EpiRoute::httpGet);
return $this->success("photo {$id} updated", $photo['result']);
$apiResp = $this->api->invoke("/photo/{$id}/view.json", EpiRoute::httpGet, array('_GET' => array('returnSizes' => '100x100xCR', 'generate' => 'true')));
$photo = $apiResp['result'];
$this->api->invoke('/activity/create.json', EpiRoute::httpPost, array('_POST' => array('type' => 'photo-update', 'data' => $photo, 'permission' => $params['permission'])));
return $this->success("photo {$id} updated", $photo);
}
return $this->error("photo {$id} could not be updated", false);
@ -370,7 +435,6 @@ class ApiPhotoController extends ApiBaseController
* Parameters to be updated are in _POST
* This method also manages updating tag counts
*
* @param string $id ID of the photo to be updated.
* @return string Standard JSON envelope
*/
public function updateBatch()

View file

@ -30,16 +30,29 @@ class PhotoController extends BaseController
*/
public function create($id, $hash, $width, $height, $options = null)
{
$args = func_get_args();
// TODO, this should call a method in the API
$photo = $this->photo->generate($id, $hash, $width, $height, $options);
// TODO return 404 graphic
if($photo)
$fragment = $this->photo->generateFragment($width, $height, $options);
$apiResp = $this->api->invoke("/photo/{$id}/view.json", EpiRoute::httpGet, array('_GET' => array('returnSizes' => $fragment)));
if($apiResp['code'] === 200);
{
header('Content-Type: image/jpeg');
readfile($photo);
unlink($photo);
return;
// check if this size exists
if(stristr($apiResp['result']["path{$fragment}"], "/{$hash}/") === false)
{
$this->route->redirect($apiResp['result']["path{$fragment}"], 301, true);
return;
}
else
{
// TODO, this should call a method in the API
$photo = $this->photo->generate($id, $hash, $width, $height, $options);
// TODO return 404 graphic
if($photo)
{
header('Content-Type: image/jpeg');
readfile($photo);
unlink($photo);
return;
}
}
}
$this->route->run('/error/500');
}

View file

@ -792,7 +792,7 @@ class SetupController extends BaseController
file_get_contents("{$configDir}/template.ini")
);
$iniWritten = file_put_contents(sprintf("%s/userdata/configs/%s.ini", $baseDir, getenv('HTTP_HOST')), $generatedIni);
$iniWritten = getConfig()->write(sprintf("%s/userdata/configs/%s.ini", $baseDir, getenv('HTTP_HOST')), $generatedIni);
if(!$iniWritten)
return false;

View file

@ -40,20 +40,21 @@ class UpgradeController extends BaseController
{
getAuthentication()->requireAuthentication();
getUpgrade()->performUpgrade();
$configObj = getConfig();
// Backwards compatibility
// TODO remove in 2.0
$basePath = dirname(Epi::getPath('config'));
$configFile = sprintf('%s/userdata/configs/%s.ini', $basePath, getenv('HTTP_HOST'));
if(!file_exists($configFile))
$configFile = sprintf('%s/generated/%s.ini', Epi::getPath('config'), getenv('HTTP_HOST'));
$config = file_get_contents($configFile);
$config = $configObj->getString($configFile);
// Backwards compatibility
// TODO remove in 2.0
if(strstr($config, 'lastCodeVersion="') !== false)
$config = preg_replace('/lastCodeVersion="\d+\.\d+\.\d+"/', sprintf('lastCodeVersion="%s"', getUpgrade()->getCurrentVersion()), $config);
else // Before the upgrade code the lastCodeVersion was not in the config template
$config = sprintf("[site]\nlastCodeVersion=\"%s\"\n\n", getUpgrade()->getCurrentVersion()) . $config;
file_put_contents($configFile, $config);
$configObj->write($configFile, $config);
$this->route->redirect('/');
}
}

View file

@ -13,6 +13,7 @@ require $pathsObj->controllers . '/ApiController.php';
require $pathsObj->controllers . '/GeneralController.php';
require $pathsObj->controllers . '/AssetsController.php';
require $pathsObj->controllers . '/ApiActionController.php';
require $pathsObj->controllers . '/ApiActivityController.php';
require $pathsObj->controllers . '/ActionController.php';
require $pathsObj->controllers . '/ApiGroupController.php';
require $pathsObj->controllers . '/GroupController.php';
@ -64,6 +65,7 @@ require $pathsObj->models . '/Url.php';
require $pathsObj->models . '/Authentication.php';
require $pathsObj->models . '/Credential.php';
require $pathsObj->models . '/Action.php';
require $pathsObj->models . '/Activity.php';
require $pathsObj->models . '/Group.php';
require $pathsObj->models . '/Photo.php';
require $pathsObj->models . '/Tag.php';

View file

@ -5,13 +5,14 @@ class OPException extends Exception
{
getLogger()->warn($exception->getMessage());
$class = get_class($exception);
$baseController = new BaseController;
switch($class)
{
case 'OPAuthorizationException':
case 'OPAuthorizationSessionException':
if(isset($_GET['__route__']) && substr($_GET['__route__'], -5) == '.json')
{
echo json_encode(BaseController::forbidden('You do not have sufficient permissions to access this page.'));
echo json_encode($baseController->forbidden('You do not have sufficient permissions to access this page.'));
}
else
{
@ -20,7 +21,7 @@ class OPException extends Exception
die();
break;
case 'OPAuthorizationOAuthException':
echo json_encode(BaseController::forbidden($exception->getMessage()));
echo json_encode($baseController->forbidden($exception->getMessage()));
die();
break;
default:
@ -38,11 +39,12 @@ class OPInvalidImageException extends OPException{}
function op_exception_handler($exception)
{
static $handled;
$baseController = new BaseController;
if(!$handled)
{
getLogger()->warn(sprintf('Uncaught exception (%s:%s): %s', $exception->getFile(), $exception->getLine(), $exception->getMessage()));
if(isset($_GET['__route__']) && substr($_GET['__route__'], -5) == '.json')
echo json_encode(BaseController::error('An unknown error occurred.'));
echo json_encode($baseController->error('An unknown error occurred.'));
else
getRoute()->run('/error/500', EpiRoute::httpGet);

View file

@ -21,7 +21,9 @@ class Epi
'cache-apc' => array('base', 'EpiCache.php', 'EpiCache_Apc.php'),
'cache-file' => array('base', 'EpiCache.php', 'EpiCache_File.php'),
'cache-memcached' => array('base', 'EpiCache.php', 'EpiCache_Memcached.php'),
'config' => array('base', 'EpiConfig.php'),
'config' => array('base', 'EpiConfig.php', 'config-file', 'config-mysql'),
'config-file' => array('base', 'EpiConfig.php', 'EpiConfig_File.php'),
'config-mysql' => array('base', 'database', 'EpiConfig.php', 'EpiConfig_MySql.php'),
'form' => array('EpiForm.php'),
'logger' => array('EpiLogger.php'),
'session' => array('base', 'EpiSession.php', 'session-php', 'session-apc', 'session-memcached'),

View file

@ -96,7 +96,7 @@ class EpiApi
EpiException::raise(new EpiException('Could not call ' . json_encode($def) . " for route {$regex}"));
}
}
EpiException::raise(new EpiException("Could not find route {$this->route} from {$_SERVER['REQUEST_URI']}"));
EpiException::raise(new EpiException("Could not find route ({$route}) from ({$_SERVER['REQUEST_URI']})"));
}
/**

View file

@ -21,9 +21,8 @@ class EpiCache
$type = array_shift($params);
if(!file_exists($file = dirname(__FILE__) . "/{$type}.php"))
EpiException::raise(EpiCacheTypeDoesNotExistException("EpiCache type does not exist: ({$type}). Tried loading {$file}", 404));
EpiException::raise(new EpiCacheTypeDoesNotExistException("EpiCache type does not exist: ({$type}). Tried loading {$file}", 404));
require_once $file;
self::$instances[$hash] = new $type($params);
self::$instances[$hash]->hash = $hash;
return self::$instances[$hash];

View file

@ -1,46 +1,16 @@
<?php
class EpiConfig
{
private static $instance;
private $config;
const FILE = 'EpiConfig_File';
const MYSQL = 'EpiConfig_MySql';
private static $employ;
protected static $instances;
protected $config;
public function __construct()
{
$this->config = new stdClass;
}
public function load(/*$file, $file, $file, $file...*/)
{
$args = func_get_args();
foreach($args as $file)
{
// Prepend config directory if the path doesn't start with . or /
if($file[0] != '.' && $file[0] != '/')
$file = Epi::getPath('config') . "/{$file}";
if(!file_exists($file))
{
EpiException::raise(new EpiConfigException("Config file ({$file}) does not exist"));
break; // need to simulate same behavior if exceptions are turned off
}
$parsed_array = parse_ini_file($file, true);
foreach($parsed_array as $key => $value)
{
if(!is_array($value))
{
$this->config->$key = $value;
}
else
{
if(!isset($this->config->$key))
$this->config->$key = new stdClass;
foreach($value as $innerKey => $innerValue)
$this->config->$key->$innerKey = $innerValue;
}
}
}
}
public function get($key = null)
{
if(!empty($key))
@ -57,22 +27,74 @@ class EpiConfig
$this->config->$key = $val;
}
/*
* @param $const
* @params optional
*/
public static function employ()
{
if(func_num_args() === 1)
self::$employ = func_get_arg(0);
return self::$employ;
}
public function loadString($iniAsString)
{
$config = parse_ini_string($iniAsString, true);
$this->mergeConfig($config);
}
protected function mergeConfig($config)
{
foreach($config as $key => $value)
{
if(!is_array($value))
{
$this->config->$key = $value;
}
else
{
if(!isset($this->config->$key))
$this->config->$key = new stdClass;
foreach($value as $innerKey => $innerValue)
$this->config->$key->$innerKey = $innerValue;
}
}
}
/*
* EpiConfig::getInstance
*/
public static function getInstance()
{
if(self::$instance)
return self::$instance;
$params = func_get_args();
$hash = md5(json_encode($params));
if(isset(self::$instances[$hash]))
return self::$instances[$hash];
self::$instance = new EpiConfig;
return self::$instance;
$type = $params[0];
if(!file_exists($file = dirname(__FILE__) . "/{$type}.php"))
EpiException::raise(new EpiConfigTypeDoesNotExistException("EpiConfig type does not exist: ({$type}). Tried loading {$file}", 404));
self::$instances[$hash] = new $type($params[1]);
self::$instances[$hash]->hash = $hash;
return self::$instances[$hash];
}
}
function getConfig()
{
return EpiConfig::getInstance();
$employ = EpiConfig::employ();
$class = array_shift($employ);
if($class && class_exists($class))
return EpiConfig::getInstance($class, $employ);
elseif(class_exists(EpiConfig::FILE))
return EpiConfig::getInstance(EpiConfig::FILE, $employ);
elseif(class_exists(EpiConfig::MYSQL))
return EpiConfig::getInstance(EpiConfig::MYSQL, $employ);
else
EpiException::raise(new EpiConfigTypeDoesNotExistException('Could not determine which cache handler to load', 404));
}
class EpiConfigException extends EpiException {}

View file

@ -0,0 +1,63 @@
<?php
class EpiConfig_File extends EpiConfig
{
public function __construct()
{
parent::__construct();
}
public function getString($file)
{
$file = $this->getFilePath($file);
if(!file_exists($file))
{
EpiException::raise(new EpiConfigException("Config file ({$file}) does not exist"));
return; // need to simulate same behavior if exceptions are turned off
}
return file_get_contents($file);
}
public function exists($file)
{
$file = $this->getFilePath($file);
return file_exists($file);
}
public function load(/*$file, $file, $file, $file...*/)
{
$args = func_get_args();
foreach($args as $file)
{
$confAsIni = $this->getString($file);
$config = parse_ini_string($confAsIni, true);
$this->mergeConfig($config);
}
}
public function write($file, $string)
{
$this->createDirectoryIfNotExists(dirname($file));
$created = @file_put_contents($file, $string);
return $created != false;
}
private function createDirectoryIfNotExists($dir)
{
// if directory exists and it's writable return true
if(is_dir($dir) && is_writable($dir))
return true;
// try to do a recursive write
return mkdir($dir, 0600, true);
}
private function getFilePath($file)
{
// Prepend config directory if the path doesn't start with . or /
if($file[0] != '.' && $file[0] != '/')
$file = Epi::getPath('config') . "/{$file}";
return $file;
}
}

View file

@ -0,0 +1,102 @@
<?php
class EpiConfig_MySql extends EpiConfig
{
private $db, $table;
public function __construct($params)
{
parent::__construct();
$this->db = EpiDatabase::getInstance('mysql', $params['database'], $params['host'], $params['username'], $params['password']);
$this->table = $params['table'];
}
public function getRecord($file)
{
$file = $this->getFilePath($file);
$res = $this->db->one("SELECT * FROM `{$this->table}` WHERE `id`=:file OR `aliasOf`=:aliasOf", array(':file' => $file, ':aliasOf' => $file));
if(!$res)
{
EpiException::raise(new EpiConfigException("Config file ({$file}) does not exist in db"));
return; // need to simulate same behavior if exceptions are turned off
}
return $res;
}
public function getString($file)
{
$res = $this->getRecord($file);
return $res['value'];
}
public function exists($file)
{
$file = $this->getFilePath($file);
$res = $this->db->one("SELECT * FROM `{$this->table}` WHERE `id`=:file OR `aliasOf`=:aliasOf", array(':file' => $file, ':aliasOf' => $file));
return $res !== false;
}
public function load(/*$file, $file, $file, $file...*/)
{
$args = func_get_args();
foreach($args as $file)
{
$confAsIni = $this->getString($file);
$config = parse_ini_string($confAsIni, true);
$this->mergeConfig($config);
}
}
public function search($term, $field = null)
{
$res = $this->db->all($sql = "SELECT * FROM `{$this->table}` WHERE `value` LIKE :term", array(':term' => "%{$term}%"));
foreach($res as $r)
{
$cfg = parse_ini_string($r['value'], true);
$cfg['__id__'] = $r['id'];
if($field !== null)
{
if(is_array($field))
{
list($k, $v) = each($field);
if(isset($cfg[$k][$v]) && $cfg[$k][$v] == $term)
return $cfg;
}
else
{
if(isset($cfg[$field]))
return $cfg;
}
}
}
return false;
}
public function write($file, $string, $aliasOf = null)
{
$exists = $this->exists($file);
$file = $this->getFilePath($file);
if($exists)
{
$params = array(':value' => $string);
$sql = "UPDATE `{$this->table}` SET `value`=:value ";
if($aliasOf !== null)
{
$sql .= ", `aliasOf`=:aliasOf ";
$params[':aliasOf'] = $this->getFilePath($aliasOf);
}
$params[':file'] = $file;
$sql .= " WHERE `id`=:file";
$res = $this->db->execute($sql, $params);
}
else
{
$res = $this->db->execute("INSERT INTO `{$this->table}` (`id`, `value`, `aliasOf`) VALUES(:file, :value, :aliasOf)", array(':file' => $file, ':value' => $string, ':aliasOf' => $aliasOf));
}
return $res !== false;
}
private function getFilePath($file)
{
return basename($file);
}
}

View file

@ -17,12 +17,14 @@ class EpiSession
public static function getInstance()
{
$params = func_get_args();
$hash = md5(implode('.', $params));
$hash = md5(json_encode($params));
if(isset(self::$instances[$hash]))
return self::$instances[$hash];
$type = array_shift($params);
self::$instances[$hash] = new $type($params);
$type = $params[0];
if(!isset($params[1]))
$params[1] = array();
self::$instances[$hash] = new $type($params[1]);
self::$instances[$hash]->hash = $hash;
return self::$instances[$hash];
}
@ -34,9 +36,9 @@ class EpiSession
public static function employ(/*$const*/)
{
if(func_num_args() === 1)
{
self::$employ = func_get_arg(0);
}
elseif(func_num_args() > 1)
self::$employ = func_get_args();
return self::$employ;
}
@ -53,8 +55,9 @@ if(!function_exists('getSession'))
function getSession()
{
$employ = EpiSession::employ();
if($employ && class_exists($employ))
return EpiSession::getInstance($employ);
$class = array_shift($employ);
if($employ && class_exists($class))
return EpiSession::getInstance($class, $employ);
elseif(class_exists(EpiSession::PHP))
return EpiSession::getInstance(EpiSession::PHP);
elseif(class_exists(EpiSession::APC))

View file

@ -8,6 +8,7 @@ if(isset($_GET['__route__']) && strstr($_GET['__route__'], '.json'))
$basePath = dirname(dirname(__FILE__));
$epiPath = "{$basePath}/libraries/external/epi";
require "{$epiPath}/Epi.php";
require "{$basePath}/libraries/compatability.php";
require "{$basePath}/libraries/models/UserConfig.php";
Epi::setSetting('exceptions', true);
@ -20,10 +21,18 @@ Epi::init('api','cache','config','curl','form','logger','route','session','templ
$userConfigObj = new UserConfig;
$hasConfig = $userConfigObj->load();
EpiCache::employ(getConfig()->get('epi')->cache);
EpiSession::employ(getConfig()->get('epi')->session);
$configObj = getConfig();
EpiCache::employ($configObj->get('epi')->cache);
$sessionParams = array($configObj->get('epi')->session);
if($configObj->get('epiSessionParams'))
$sessionParams = array_merge($sessionParams, (array)$configObj->get('epiSessionParams'));
EpiSession::employ($sessionParams);
getSession();
// load theme after everything is initialized
// this initializes user which extends BaseModel and gets session, config and cache objects
$userConfigObj->loadTheme();
// determine if this is a login endpoint
$loginEndpoint = $assetEndpoint = false;
if(isset($_GET['__route__']) && preg_match('#/user/(.*)(login|logout)#', $_GET['__route__']))
@ -45,7 +54,7 @@ if($hasConfig && !$runSetup)
$runUpgrade = false;
if(!getUpgrade()->isCurrent())
$runUpgrade = true;
require getConfig()->get('paths')->libraries . '/routes.php';
require $configObj->get('paths')->libraries . '/routes.php';
// initializes plugins
getPlugin()->load();
@ -68,15 +77,15 @@ else
$paths->models = "{$baseDir}/libraries/models";
$paths->templates = "{$baseDir}/templates";
$paths->themes = "{$baseDir}/html/assets/themes";
getConfig()->set('paths', $paths);
$configObj->set('paths', $paths);
if(!$hasConfig)
require getConfig()->get('paths')->libraries . '/dependencies.php';
require $configObj->get('paths')->libraries . '/dependencies.php';
require getConfig()->get('paths')->libraries . '/routes-setup.php';
require getConfig()->get('paths')->libraries . '/routes-error.php';
require getConfig()->get('paths')->controllers . '/SetupController.php';
getConfig()->load(sprintf('%s/html/assets/themes/%s/config/settings.ini', dirname(dirname(__FILE__)), getTheme()->getThemeName()));
require $configObj->get('paths')->libraries . '/routes-setup.php';
require $configObj->get('paths')->libraries . '/routes-error.php';
require $configObj->get('paths')->controllers . '/SetupController.php';
$configObj->loadString(file_get_contents(sprintf('%s/html/assets/themes/%s/config/settings.ini', dirname(dirname(__FILE__)), getTheme()->getThemeName())));
// Before we run the setup in edit mode, we need to validate ownership
$userObj = new User;

View file

@ -0,0 +1,89 @@
<?php
/**
* Activity model.
*
* This handles adding and retrieving activity
* @author Jaisen Mathai <jaisen@jmathai.com>
*/
class Activity extends BaseModel
{
/*
* Constructor
*/
public function __construct($params = null)
{
parent::__construct();
if(isset($params['user']))
$this->user = $params['user'];
else
$this->user = new User;
}
public function create($attributes)
{
getAuthentication()->requireAuthentication();
$attributes = array_merge($this->getDefaultAttributes(), $attributes);
$attributes = $this->whitelistParams($attributes);
if(!$this->validateParams($attributes))
{
$this->logger->warn('Not all required paramaters were passed to create an activity');
return false;
}
$id = $this->user->getNextId('activity');
if($id === false)
{
$this->logger->warn('Could not fetch the next activity id');
return false;
}
return $this->db->putActivity($id, $attributes);
}
public function list_()
{
return $this->db->getActivities();
}
public function view($id)
{
return $this->db->getActivity($id);
}
private function getDefaultAttributes()
{
return array(
'appId' => $this->config->application->appId,
'owner' => $this->config->user->email,
'dateCreated' => time()
);
}
private function validateParams($attributes)
{
if(!isset($attributes['owner']) || !isset($attributes['type']) || !isset($attributes['permission']))
return false;
return true;
}
private function whitelistParams($attributes)
{
$returnAttrs = array();
$matches = array('id' => 1, 'owner' => 1, 'appId' => 1, 'type' => 1, 'data' => 1, 'permission' => 1, 'dateCreated' => 1);
foreach($attributes as $key => $val)
{
if(isset($matches[$key]))
{
$returnAttrs[$key] = $val;
continue;
}
$returnAttrs[$key] = $val;
}
return $returnAttrs;
}
}

View file

@ -84,6 +84,12 @@ class AssetPipeline
return $url;
}
public function setMode($mode)
{
$this->mode = $mode;
return $this;
}
private function addAsset($src, $type)
{
// verify this file exists

View file

@ -9,8 +9,8 @@ class Credential extends BaseModel
const statusActive = '1';
const nonceCacheKey = 'oauthTimestamps';
public $consumer, $oauthException, $oauthParams, $provider, $sendHeadersOnError = true;
private static $requestStatus;
public $oauthException, $oauthParams, $provider, $sendHeadersOnError = true;
private static $consumer = null, $requestStatus = null;
/**
* Constructor
@ -84,7 +84,7 @@ class Credential extends BaseModel
public function checkRequest()
{
if(isset(self::$requestStatus))
if(self::$requestStatus !== null)
return self::$requestStatus;
if(!class_exists('OAuthProvider'))
@ -206,18 +206,18 @@ class Credential extends BaseModel
public function getConsumer($consumerKey)
{
if(!$this->consumer)
$this->consumer = $this->db->getCredential($consumerKey);
if(!self::$consumer)
self::$consumer = $this->db->getCredential($consumerKey);
return $this->consumer;
return self::$consumer;
}
public function getEmailFromOAuth()
{
if(!$this->consumer)
if(!self::$consumer)
return false;
return $this->consumer['owner'];
return self::$consumer['owner'];
}
public function getErrorAsString()
@ -267,6 +267,12 @@ class Credential extends BaseModel
// oauth_token and oauth_callback can be passed in for authenticated endpoints to obtain a credential
return (count($params) > 2);
}
public function reset()
{
self::$consumer = null;
self::$requestStatus = null;
}
}
if(!function_exists('getCredential'))

View file

@ -30,6 +30,9 @@ class Photo extends BaseModel
$this->user = $params['user'];
else
$this->user = new User;
if(isset($params['config']))
$this->config = $params['config'];
}
/**
@ -64,11 +67,15 @@ class Photo extends BaseModel
}
}
// we never need the Base
// we never need the base
if(isset($photo['pathBase']))
unset($photo['pathBase']);
$photo['pathOriginal'] = $this->generateUrlOriginal($photo);
// the original needs to be conditionally included
if($this->config->site->allowOriginalDownload == 1 || $this->user->isOwner())
$photo['pathOriginal'] = $this->generateUrlOriginal($photo);
elseif(isset($photo['pathOriginal']))
unset($photo['pathOriginal']);
$photo['url'] = $this->getPhotoViewUrl($photo);
return $photo;
@ -453,6 +460,7 @@ class Photo extends BaseModel
}
$tagObj = new Tag;
$attributes = $this->whitelistParams($attributes);
$filenameOriginal = $name;
$paths = $this->generatePaths($name);
$exiftran = $this->config->modules->exiftran;
if(is_executable($exiftran))
@ -536,6 +544,7 @@ class Photo extends BaseModel
'exifExposureTime' => @$exif['exposureTime'],
'exifISOSpeed' => @$exif['ISO'],
'exifFocalLength' => @$exif['focalLength'],
'filenameOriginal' => $filenameOriginal,
'width' => @$exif['width'],
'height' => @$exif['height'],
'dateTaken' => $dateTaken,
@ -712,7 +721,7 @@ class Photo extends BaseModel
$returnAttrs = array();
$matches = array('id' => 1,'host' => 1,'appId' => 1,'title' => 1,'description' => 1,'key' => 1,'hash' => 1,'tags' => 1,'size' => 1,'width' => 1,'photo'=>1,
'height' => 1,'altitude' => 1, 'latitude' => 1,'longitude' => 1,'views' => 1,'status' => 1,'permission' => 1,'groups' => 1,'license' => 1,
'dateTaken' => 1, 'dateUploaded' => 1);
'dateTaken' => 1, 'dateUploaded' => 1, 'filenameOriginal' => 1 /* TODO remove in 1.5.0, only used for upgrade */);
$patterns = array('exif.*' => 1,'date.*' => 1,'path.*' => 1);
foreach($attributes as $key => $val)
{

View file

@ -59,6 +59,11 @@ class Plugin extends BaseModel
return $plugins;
}
public function getConfigObj()
{
return getConfig();
}
public function invoke($action, $params = null)
{
$output = '';
@ -97,7 +102,8 @@ class Plugin extends BaseModel
$conf = $inst->defineConf();
if(file_exists($confPath = sprintf('%s/plugins/%s.%s.ini', $this->config->paths->userdata, $_SERVER['HTTP_HOST'], $plugin)))
{
$parsedConf = parse_ini_file($confPath);
$configObj = $this->getConfigObj();
$parsedConf = parse_ini_string($configObj->getString($confPath));
foreach($conf as $name => $tmp)
{
if(isset($parsedConf[$name]))
@ -110,16 +116,13 @@ class Plugin extends BaseModel
public function writeConf($plugin, $string)
{
$configObj = $this->getConfigObj();
$pluginDir = sprintf('%s/plugins', $this->config->paths->userdata);
if(!is_dir($pluginDir))
{
if(!@mkdir($pluginDir))
$this->logger->warn(sprintf('Could not create directory at %s', $pluginDir));
}
if($string !== false)
{
$fileCreated = @file_put_contents($pluginConfFile = sprintf('%s/%s.%s.ini', $pluginDir, $_SERVER['HTTP_HOST'], $plugin), $string) !== false;
$pluginConfFile = sprintf('%s/%s.%s.ini', $pluginDir, $_SERVER['HTTP_HOST'], $plugin);
$fileCreated = $configObj->write($pluginConfFile, $string) !== false;
if(!$fileCreated)
$this->logger->warn(sprintf('Could not create file at %s', $pluginConfFile));

View file

@ -6,11 +6,15 @@
*/
class PluginBase extends BaseModel
{
private $pluginName, $pluginConf = null;
public function __construct()
private $plugin, $pluginName, $pluginConf = null;
public function __construct($params = null)
{
parent::__construct();
$this->pluginName = preg_replace('/Plugin$/', '', get_class($this));
if(isset($params['plugin']))
$this->plugin = $params['plugin'];
else
$this->plugin = getPlugin();
}
public function defineConf()
@ -24,7 +28,7 @@ class PluginBase extends BaseModel
return $this->pluginConf;
$this->pluginConf = new stdClass;
$conf = getPlugin()->loadConf($this->pluginName);
$conf = $this->plugin->loadConf($this->pluginName);
foreach($conf as $name => $value)
$this->pluginConf->$name = $value;
@ -33,31 +37,25 @@ class PluginBase extends BaseModel
public function onAction($params)
{
$this->logger->info('Plugin onAction called');
}
public function onBodyBegin($params = null)
{
$this->logger->info('Plugin onBodyBegin called');
}
public function onBodyEnd($params = null)
{
$this->logger->info('Plugin onBodyEnd called');
}
public function onHead($params = null)
{
$this->logger->info('Plugin onHead called');
}
public function onLoad($params = null)
{
$this->logger->info('Plugin onLoad called');
}
public function onView($params)
{
$this->logger->info('Plugin onView called');
}
}

View file

@ -21,7 +21,7 @@ class Theme
{
$this->theme = self::themeDefault;
if(file_exists($mobileSettings = sprintf('%s/%s/config/settings-mobile.ini', getConfig()->get('paths')->themes, $this->getThemeName())))
getConfig()->load($mobileSettings);
getConfig()->loadString(file_get_contents($mobileSettings));
}
elseif($themeConfig !== null)
{

View file

@ -243,7 +243,7 @@ class Upgrade extends BaseModel
{
foreach($versions as $file)
{
getLogger()->info(sprintf('Calling executeScript on base file %s', $file));
$this->logger->info(sprintf('Calling executeScript on base file %s', $file));
$this->executeScript($file);
}
}
@ -257,8 +257,8 @@ class Upgrade extends BaseModel
{
foreach($version as $file)
{
getLogger()->info(sprintf('Calling executeScript on %s file %s', $database, $file));
getDb()->executeScript($file, $database);
$this->logger->info(sprintf('Calling executeScript on %s file %s', $database, $file));
$this->db->executeScript($file, $database);
}
}
}
@ -272,8 +272,8 @@ class Upgrade extends BaseModel
{
foreach($version as $file)
{
getLogger()->info(sprintf('Calling executeScript on %s file %s', $filesystem, $file));
getFs()->executeScript($file, $filesystem);
$this->logger->info(sprintf('Calling executeScript on %s file %s', $filesystem, $file));
$this->fs->executeScript($file, $filesystem);
}
}
}

View file

@ -6,9 +6,38 @@ class UserConfig
public function __construct($params = null)
{
if(isset($params['config']))
{
$this->config = $params['config'];
}
else
{
$path = dirname(dirname(dirname(__FILE__)));
$params = parse_ini_file(sprintf('%s/configs/defaults.ini', $path), true);
if(file_exists($overrideIni = sprintf('%s/configs/override.ini', $path)))
{
$override = parse_ini_file($overrideIni, true);
foreach($override as $key => $value)
{
if(array_key_exists($key, $params))
{
if(is_array($value))
$params[$key] = array_merge((array)$params[$key], $value);
else
$params[$key] = $value;
}
else
{
$params[$key] = $value;
}
}
}
$configParams = array($params['epi']['config']);
if(isset($params['epiConfigParams']))
$configParams = array_merge($configParams, $params['epiConfigParams']);
EpiConfig::employ($configParams);
$this->config = getConfig();
}
if(isset($params['utility']))
$this->utility = $params['utility'];
@ -22,7 +51,8 @@ class UserConfig
$configFile = $this->getConfigFile();
if(!$configFile)
return false;
return parse_ini_file($configFile, true);
$iniString = $this->config->getString($configFile);
return parse_ini_string($iniString, true);
}
public function writeSiteSettings($settings)
@ -41,7 +71,8 @@ class UserConfig
public function load()
{
$this->config->load('defaults.ini');
$path = dirname(dirname(dirname(__FILE__)));
$this->config->loadString(file_get_contents(sprintf('%s/configs/defaults.ini', $path)));
$configFile = $this->getConfigFile();
// backwards compatibility for 1.2.1 -> 1.2.2 upgrade
@ -55,17 +86,22 @@ class UserConfig
// we need to load the deps to get the theme modules
require $this->config->get('paths')->libraries . '/dependencies.php';
$this->config->load(sprintf('%s/html/assets/themes/%s/config/settings.ini', dirname(dirname(dirname(__FILE__))), getTheme()->getThemeName()));
$utilityObj = $this->getUtility();
if($utilityObj->isMobile() && file_exists($mobileSettings = sprintf('%s/html/assets/themes/%s/config/settings-mobile.ini', dirname(dirname(dirname(__FILE__))), getTheme(false)->getThemeName())))
$this->config->load($mobileSettings);
return true;
}
return false;
}
public function loadTheme()
{
if(!$this->getConfigFile())
return;
$this->config->loadString(file_get_contents(sprintf('%s/html/assets/themes/%s/config/settings.ini', dirname(dirname(dirname(__FILE__))), getTheme()->getThemeName())));
$utilityObj = $this->getUtility();
if($utilityObj->isMobile() && file_exists($mobileSettings = sprintf('%s/html/assets/themes/%s/config/settings-mobile.ini', dirname(dirname(dirname(__FILE__))), getTheme(false)->getThemeName())))
$this->config->loadString(file_get_contents($mobileSettings));
}
protected function getUtility()
{
if(isset($this->utility))
@ -78,7 +114,7 @@ class UserConfig
private function getConfigFile()
{
$configFile = sprintf('%s/userdata/configs/%s.ini', $this->basePath, $this->host);
if(!file_exists($configFile))
if(!$this->config->exists($configFile))
return false;
return $configFile;
}

View file

@ -246,7 +246,7 @@ class Utility
{
$licenses = $this->getLicenses();
$link = '';
if(isset($licenses[$key]))
if(isset($licenses[$key]) && isset($licenses[$key]['link']))
$link = $licenses[$key]['link'];
return $this->returnValue($link, $write);
}

View file

@ -17,7 +17,7 @@ class FacebookConnectPlugin extends PluginBase
return array('id' => null, 'secret' => null);
}
public function onBodyEnd()
public function onBodyEnd($params = null)
{
parent::onBodyEnd();
$conf = $this->getConf();

View file

@ -17,7 +17,7 @@ class GoogleAnalyticsPlugin extends PluginBase
return array('id' => null);
}
public function onHead()
public function onHead($params = null)
{
parent::onHead();
$conf = $this->getConf();

View file

@ -11,12 +11,12 @@ class HelloWorldPlugin extends PluginBase
parent::__construct();
}
public function onBody()
public function onBodyBegin($params = null)
{
parent::onBody();
parent::onBodyBegin();
}
public function onView()
public function onView($params = null)
{
parent::onView();
}

View file

@ -15,6 +15,16 @@ getApi()->get('/action/([a-zA-Z0-9]+)/view.json', array('ApiActionController', '
getApi()->post('/action/([a-zA-Z0-9]+)/delete.json', array('ApiActionController', 'delete'), EpiApi::external); // delete an action (/action/{id}/delete.json)
getApi()->post('/action/([a-zA-Z0-9]+)/(photo)/create.json', array('ApiActionController', 'create'), EpiApi::external); // post an action (/action/{id}/{type}/create.json)
/*
* Activity endpoints
* All activity endpoints follow the same convention.
* Everything in []'s are optional
* /activit(y|ies)/{action}.json
*/
getApi()->get('/activities/list.json', array('ApiActivityController', 'list_'), EpiApi::external); // retrieve activities (/activities/list.json)
getApi()->get('/activity/([a-zA-Z0-9]+)/view.json', array('ApiActivityController', 'view'), EpiApi::external); // retrieve activity (/activity/:id/view.json)
getApi()->post('/activity/create.json', array('ApiActivityController', 'create'), EpiApi::internal); // post an action (/action/{id}/{type}/create.json)
/*
* Photo endpoints
* All photo endpoints follow the same convention.
@ -26,7 +36,8 @@ getApi()->get('/photo/([a-zA-Z0-9]+)/edit.json', array('ApiPhotoController', 'ed
getApi()->post('/photo/([a-zA-Z0-9]+)/update.json', array('ApiPhotoController', 'update'), EpiApi::external); // update a photo (/photo/{id}/update.json)
getApi()->get('/photo/([a-zA-Z0-9]+)/view.json', array('ApiPhotoController', 'view'), EpiApi::external); // get a photo's information (/photo/view/{id}.json)
getApi()->get('/photos/?(.+)?/list.json', array('ApiPhotoController', 'list_'), EpiApi::external); // get all photos / optionally filter (/photos[/{options}]/view.json)
getApi()->post('/photos/update.json', array('ApiPhotoController', 'updateBatch'), EpiApi::external); // get all photos / optionally filter (/photos[/{options}]/view.json)
getApi()->post('/photos/update.json', array('ApiPhotoController', 'updateBatch'), EpiApi::external); // update multiple photos (/photos/update.json)
getApi()->post('/photos/delete.json', array('ApiPhotoController', 'deleteBatch'), EpiApi::external); // delete multiple photos (/photos/delete.json)
getApi()->post('/photo/upload.json', array('ApiPhotoController', 'upload'), EpiApi::external); // upload a photo
getApi()->get('/photo/([a-zA-Z0-9]+)/url/(\d+)x(\d+)x?([A-Zx]*)?.json', array('ApiPhotoController', 'dynamicUrl'), EpiApi::external); // generate a dynamic photo url (/photo/{id}/url/{options}.json) TODO, make internal for now
getApi()->get('/photo/([a-zA-Z0-9]+)/nextprevious/?(.+)?.json', array('ApiPhotoController', 'nextPrevious'), EpiApi::external); // get a photo's next/previous (/photo/{id}/nextprevious[/{options}].json)

View file

@ -24,6 +24,7 @@ class CredentialTest extends PHPUnit_Framework_TestCase
$this->credential = new Credential(array('utility' => $utility));
$this->credential->sendHeadersOnError = false;
$this->credential->reset();
$this->token = 'abcdefghijklmnopqrstuvwxyz0123456789';
}

View file

@ -12,6 +12,8 @@ class PhotoTest extends PHPUnit_Framework_TestCase
$params = array('user' => new FauxObject, 'utility' => new stdClass, 'url' => new stdClass, 'image' => new FauxObject);
$this->photo = new Photo($params);;
$config = new stdClass;
$config->site = new stdClass;
$config->site->allowOriginalDownload = 1;
$secrets = new stdClass;
$secrets->secret = 'secret';
$config->secrets = $secrets;
@ -58,6 +60,81 @@ class PhotoTest extends PHPUnit_Framework_TestCase
$this->assertEquals(10, $res['photo10x10xCR'][2], 'The height is not correct in the photo array');
}
public function testAddApiUrlsOriginalAsOwner()
{
$user = $this->getMock('User', array('isOwner'));
$user->expects($this->any())
->method('isOwner')
->will($this->returnValue(true));
$url = $this->getMock('Url', array('photoView'));
$url->expects($this->any())
->method('photoView')
->will($this->returnValue('/url'));
$utility = $this->getMock('Utility', array('getProtocol'));
$utility->expects($this->any())
->method('getProtocol')
->will($this->returnValue('http'));
$config = $this->photo->config;
$config->site->allowOriginalDownload = 0;
$this->photo->inject('user', $user);
$this->photo->inject('url', $url);
$this->photo->inject('utility', $utility);
$this->photo->inject('config', $config);
$res = $this->photo->addApiUrls($this->photoData, array('10x10'));
$this->assertTrue(isset($res['pathOriginal']));
}
public function testAddApiUrlsOriginalAsNonOwner()
{
$user = $this->getMock('User', array('isOwner'));
$user->expects($this->any())
->method('isOwner')
->will($this->returnValue(false));
$url = $this->getMock('Url', array('photoView'));
$url->expects($this->any())
->method('photoView')
->will($this->returnValue('/url'));
$utility = $this->getMock('Utility', array('getProtocol'));
$utility->expects($this->any())
->method('getProtocol')
->will($this->returnValue('http'));
$config = $this->photo->config;
$config->site->allowOriginalDownload = 1;
$this->photo->inject('user', $user);
$this->photo->inject('url', $url);
$this->photo->inject('utility', $utility);
$this->photo->inject('config', $config);
$res = $this->photo->addApiUrls($this->photoData, array('10x10'));
$this->assertTrue(isset($res['pathOriginal']));
}
public function testAddApiUrlsOriginalNotAllowed()
{
$user = $this->getMock('User', array('isOwner'));
$user->expects($this->any())
->method('isOwner')
->will($this->returnValue(false));
$url = $this->getMock('Url', array('photoView'));
$url->expects($this->any())
->method('photoView')
->will($this->returnValue('/url'));
$utility = $this->getMock('Utility', array('getProtocol'));
$utility->expects($this->any())
->method('getProtocol')
->will($this->returnValue('http'));
$config = $this->photo->config;
$config->site->allowOriginalDownload = 0;
$this->photo->inject('user', $user);
$this->photo->inject('url', $url);
$this->photo->inject('utility', $utility);
$this->photo->inject('config', $config);
$res = $this->photo->addApiUrls($this->photoData, array('10x10'));
$this->assertFalse(isset($res['pathOriginal']));
}
public function testDeleteCouldNotGetPhoto()
{
$db = $this->getMock('db', array('getPhoto'));
@ -280,10 +357,11 @@ class PhotoTest extends PHPUnit_Framework_TestCase
{
// This *should* work
$now = time();
$ym = date('Ym');
$res = $this->photo->generatePaths('foobar');
$this->assertNotEquals("/original/201201/{$now}-foobar", $res['pathOriginal'], 'original path not correct, if it is a timestamp mismatch - ignore');
$this->assertTrue(preg_match('#/original/201201/[a-z0-9]{6}-foobar#', $res['pathOriginal']) == 1, 'original path not correct, if it is a timestamp mismatch - ignore');
$this->assertEquals("/base/201201/{$now}-foobar", $res['pathBase'], 'base path not correct, if it is a timestamp mismatch - ignore');
$this->assertNotEquals("/original/{$ym}/{$now}-foobar", $res['pathOriginal'], 'original path not correct, if it is a timestamp mismatch - ignore');
$this->assertTrue(preg_match("#/original/{$ym}/[a-z0-9]{6}-foobar#", $res['pathOriginal']) == 1, 'original path not correct, if it is a timestamp mismatch - ignore');
$this->assertEquals("/base/{$ym}/{$now}-foobar", $res['pathBase'], 'base path not correct, if it is a timestamp mismatch - ignore');
}
public function testGenerateUrlOriginal()

View file

@ -8,7 +8,7 @@ class PluginBaseTest extends PHPUnit_Framework_TestCase
{
public function setUp()
{
$this->pluginBase = new PluginBase;
$this->pluginBase = new PluginBase(array('plugin' => new FauxObject));
}
public function testDefineConf()

View file

@ -35,7 +35,7 @@ class UserConfigTest extends PHPUnit_Framework_TestCase
mkdir("{$this->userConfigDir}/userdata/configs");
}
$params = array('utility' => new FauxObject);
$params = array('utility' => new FauxObject, 'config' => new FauxObject);
$_SERVER['HTTP_HOST'] = 'example.com';
$this->userConfig = new UserConfigWrapper($params);
$this->userConfig->inject('basePath', $this->userConfigDir);
@ -54,7 +54,15 @@ class UserConfigTest extends PHPUnit_Framework_TestCase
public function testGetSiteSettingsWithoutSections()
{
file_put_contents("{$this->userConfigDir}/userdata/configs/example.com.ini", "foo=bar");
$config = $this->getMock('config', array('getString','exists'));
$config->expects($this->any())
->method('getString')
->will($this->returnValue('foo=bar'));
$config->expects($this->any())
->method('exists')
->will($this->returnValue(true));
$this->userConfig->inject('config', $config);
$res = $this->userConfig->getSiteSettings();
$expected = array('foo' => 'bar');
$this->assertEquals($expected, $res);
@ -62,7 +70,15 @@ class UserConfigTest extends PHPUnit_Framework_TestCase
public function testGetSiteSettingsWithSections()
{
file_put_contents("{$this->userConfigDir}/userdata/configs/example.com.ini", "[stuff]\nfoo=bar");
$config = $this->getMock('config', array('getString','exists'));
$config->expects($this->any())
->method('getString')
->will($this->returnValue("[stuff]\nfoo=bar"));
$config->expects($this->any())
->method('exists')
->will($this->returnValue(true));
$this->userConfig->inject('config', $config);
$res = $this->userConfig->getSiteSettings();
$expected = array('stuff' => array('foo' => 'bar'));
$this->assertEquals($expected, $res);
@ -70,12 +86,24 @@ class UserConfigTest extends PHPUnit_Framework_TestCase
public function testGetSiteSettingsDNE()
{
$config = $this->getMock('config', array('exists'));
$config->expects($this->any())
->method('exists')
->will($this->returnValue(false));
$this->userConfig->inject('config', $config);
$res = $this->userConfig->getSiteSettings();
$this->assertFalse($res);
}
public function testWriteSiteSettingsDNE()
{
$config = $this->getMock('config', array('exists'));
$config->expects($this->any())
->method('exists')
->will($this->returnValue(false));
$this->userConfig->inject('config', $config);
$res = $this->userConfig->writeSiteSettings(array('foo'));
$this->assertFalse($res);
}