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 #### 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 update
apt-get upgrade 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 a2enmod rewrite
There are also a few optional but recommended packages and modules. 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 #### OpenPhoto, a photo service for the masses
## OS: Linux Ubuntu Server 10.04+ ## OS: Linux Ubuntu Server 10.04+
This guide instructs you on how to install OpenPhoto on Cherokee Web Server on an Ubuntu server. 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 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 update
apt-get upgrade 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. There are also a few optional but recommended packages and modules.

View file

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

View file

@ -1,11 +1,44 @@
<?php <?php
$status = true; $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 $sql = <<<SQL
ALTER TABLE `{$this->mySqlTablePrefix}elementTag` ADD INDEX ( `element` ) ALTER TABLE `{$this->mySqlTablePrefix}elementTag` ADD INDEX ( `element` )
SQL; SQL;
$status = $status && mysql_1_4_0($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 $sql = <<<SQL
UPDATE `{$this->mySqlTablePrefix}admin` SET `value`=:version WHERE `key`=:key UPDATE `{$this->mySqlTablePrefix}admin` SET `value`=:version WHERE `key`=:key
SQL; SQL;

View file

@ -3,7 +3,7 @@ $domains = $this->db->get_domain_list("/^{$this->domainPhoto}(Action|Credential|
if(count($domains) == 7) if(count($domains) == 7)
return true; 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); $this->domainPhoto, $this->domainTag, $this->domainUser, $this->domainWebhook);
$queue = new CFBatchRequest(); $queue = new CFBatchRequest();

View file

@ -23,6 +23,7 @@ javascript="jquery"
[frontApis] [frontApis]
photos="GET /photos/list.json?pageSize=20&returnSizes=800x450xCR" photos="GET /photos/list.json?pageSize=20&returnSizes=800x450xCR"
#activities="GET /activities/list.json?pageSize=10&groupBy=hour"
[pagination] [pagination]
pagesToDisplay=10 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); 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, 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"); 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")}}, 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()),
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>'+ 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">'+
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=== 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();
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, $.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")},
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", 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)}):
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.")}); 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(),
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(), 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.")},
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.", "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,
"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, "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();
{},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(); 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 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 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");
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= $("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.")});
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 "+ 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&&
$("#"+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", $.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)&&
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()!= (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())},
""&&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= 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(""),
$(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>"})}); 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")})}},
$("#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", 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.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); 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: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", 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.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, 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);
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", 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",
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>'+ 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()}))},
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)}}, 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>'+
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>'}, 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">'+ 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", 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)}}}}}(); {assertion:a},opTheme.user.base.loginProcessed)}}}}}();

View file

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

View file

@ -1609,7 +1609,7 @@ aside.sidebar .meta li span {
top:2px; top:2px;
left:0; left:0;
position:absolute; position:absolute;
width:15px; width:20px;
} }
aside.sidebar .meta li.date span { aside.sidebar .meta li.date span {
@ -1631,6 +1631,9 @@ aside.sidebar .meta li.license span {
aside.sidebar .meta li.location span { aside.sidebar .meta li.location span {
background:transparent url("../images/icons.png") 0 -626px no-repeat; 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 { aside.sidebar .meta li.location img {
-webkit-border-radius: 2px; -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($photos[0]['totalRows'] == 0) { ?>
<?php if($this->user->isOwner()) { ?> <?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> <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])) { ?> <?php if(!empty($photo[$key])) { ?>
<li><?php printf($value, $this->utility->safe($photo[$key], false)); ?></li> <li><?php printf($value, $this->utility->safe($photo[$key], false)); ?></li>
<?php } ?> <?php } ?>
<?php } ?> </ul>
</ul> </li>
</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 } ?>
<?php if($this->user->isOwner()) { ?> <?php if($this->user->isOwner()) { ?>
<li class="edit"> <li class="edit">
<span></span> <span></span>
<a href="<?php $this->url->photoEdit($photo['id']); ?>" class="button photo-edit-click">Edit this photo</a> <a href="<?php $this->url->photoEdit($photo['id']); ?>" class="button photo-edit-click">Edit this photo</a>
</li> </li>
<?php } ?> <?php } ?>
</ul> </ul>
</aside> </aside>

View file

@ -19,7 +19,7 @@
<link rel="stylesheet" href="<?php echo getAssetPipeline(true)->addCss($this->theme->asset('stylesheet', 'bootstrap.min.css', false))-> <link rel="stylesheet" href="<?php echo getAssetPipeline(true)->addCss($this->theme->asset('stylesheet', 'bootstrap.min.css', false))->
addCss("/assets/stylesheets/upload.css")-> addCss("/assets/stylesheets/upload.css")->
addCss($this->theme->asset('stylesheet', 'main.css', false))-> addCss($this->theme->asset('stylesheet', 'main.css', false))->
getUrl(AssetPipeline::css, 'd'); ?>"> getUrl(AssetPipeline::css, 'e'); ?>">
<?php } ?> <?php } ?>
<?php $this->plugin->invoke('onHead', array('page' => $page)); ?> <?php $this->plugin->invoke('onHead', array('page' => $page)); ?>
@ -130,7 +130,7 @@
<?php } else { ?> <?php } else { ?>
'<?php echo getAssetPipeline(true)->addJs('/assets/javascripts/openphoto-batch.min.js')-> '<?php echo getAssetPipeline(true)->addJs('/assets/javascripts/openphoto-batch.min.js')->
addJs($this->theme->asset('javascript', 'openphoto-theme-full-min.js', false))-> addJs($this->theme->asset('javascript', 'openphoto-theme-full-min.js', false))->
getUrl(AssetPipeline::js, 'd'); ?>' getUrl(AssetPipeline::js, 'e'); ?>'
<?php } ?> <?php } ?>
], ],
onComplete: function(){ 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); console.log(msg);
}; };
return{ return{
init: { callback: {
attach: function(PhotoSwipe) { login: function(el) {
$('div.gallery-page').live('pageshow', function(e){ navigator.id.getVerifiedEmail(function(assertion) {
var if (assertion) {
currentPage = $(e.target), opTheme.user.loginSuccess(assertion);
photoSwipeInstanceId = parseInt(Math.random()*10000), } else {
photoSwipeInstance = PhotoSwipe.getInstance(photoSwipeInstanceId) opTheme.user.loginFailure(assertion);
options = {}; }
if ($("ul.gallery a").length > 0 && (typeof photoSwipeInstance === "undefined" || photoSwipeInstance === null)) {
photoSwipeInstance = $("ul.gallery a", e.target).photoSwipe(options, photoSwipeInstanceId);
}
return true;
}); });
} }
},
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}; var params = {assertion: assertion};
OP.Util.makeRequest('/user/browserid/login.json', params, opTheme.user.loginProcessed, 'json'); 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; 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 { .photo-details .location img {
display: block; display: block;
} }

View file

@ -8,6 +8,16 @@
</div> </div>
</form> </form>
<ul data-role="listview" data-inset="true"> <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 <li><a href="/photos/list"><img
src="<?php $this->theme->asset('image', 'header-navigation-photos.png'); ?>" alt="my photos" src="<?php $this->theme->asset('image', 'header-navigation-photos.png'); ?>" alt="my photos"
class="ui-li-icon">Photos</a></li> 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="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="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> <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)) { ?> <?php if($this->utility->licenseLink($photo['license'], false)) { ?>
<a rel="license" href="<?php $this->utility->licenseLink($photo['license']); ?>"> <a rel="license" href="<?php $this->utility->licenseLink($photo['license']); ?>">
<?php $this->utility->licenseLong($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"> <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="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="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'); ?>"> <?php if($this->config->site->mode === 'dev') { ?>
<link rel="stylesheet" href="<?php $this->theme->asset('stylesheet', 'photoswipe.css'); ?>"> <link rel="stylesheet" href="<?php $this->theme->asset('stylesheet', 'jquery.mobile.css'); ?>">
<link rel="stylesheet" href="<?php $this->theme->asset('stylesheet', 'pagination.css'); ?>"> <link rel="stylesheet" href="<?php $this->theme->asset('stylesheet', 'photoswipe.css'); ?>">
<link rel="stylesheet" href="<?php $this->theme->asset('stylesheet', 'mobile.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)); ?> <?php $this->plugin->invoke('onHead', array('page' => $page)); ?>
</head> </head>
<body class="<?php echo $page; ?>"> <body class="<?php echo $page; ?>">
<?php $this->plugin->invoke('onBodyBegin', array('page' => $page)); ?> <?php $this->plugin->invoke('onBodyBegin', array('page' => $page)); ?>
<?php echo $body; ?> <?php echo $body; ?>
<script type="text/javascript" src="<?php $this->theme->asset($this->config->dependencies->javascript); ?>"></script> <?php if($this->config->site->mode === 'dev') { ?>
<script type="text/javascript" src="<?php $this->theme->asset('util'); ?>"></script> <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> <script>
OP.Util.init(jQuery, { OP.Util.init(jQuery, {
eventMap: {
click: {
'login-click':'click:login'
}
},
js: { js: {
assets: [ assets: [
<?php if($this->config->site->mode === 'dev') { ?>
'<?php $this->theme->asset('javascript', 'jquery.mobile.js'); ?>', '<?php $this->theme->asset('javascript', 'jquery.mobile.js'); ?>',
'<?php $this->theme->asset('javascript', 'openphoto-theme-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> </script>
<?php $this->plugin->invoke('onBodyEnd', array('page' => $page)); ?> <?php $this->plugin->invoke('onBodyEnd', array('page' => $page)); ?>

View file

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

View file

@ -163,6 +163,41 @@ class DatabaseMySql implements DatabaseInterface
return include $file; 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 * 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", $action = $this->db->one("SELECT * FROM `{$this->mySqlTablePrefix}action` WHERE `id`=:id AND owner=:owner",
array(':id' => $id, ':owner' => $this->owner)); array(':id' => $id, ':owner' => $this->owner));
if(empty($action)) if(empty($action))
{
return false; return false;
}
return $this->normalizeCredential($action); return $this->normalizeAction($action);
} }
/** /**
@ -364,7 +398,7 @@ class DatabaseMySql implements DatabaseInterface
if(!empty($actions)) if(!empty($actions))
{ {
foreach($actions as $action) foreach($actions as $action)
$photo['actions'][] = $action; $photo['actions'][] = $this->normalizeAction($action);
} }
} }
return $photo; return $photo;
@ -773,6 +807,21 @@ class DatabaseMySql implements DatabaseInterface
return ($res == 1); 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 * Add a new action to the database
* This method does not overwrite existing values present in $params - hence "new action". * This method does not overwrite existing values present in $params - hence "new action".
@ -1149,6 +1198,18 @@ class DatabaseMySql implements DatabaseInterface
return $versions; 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 * Normalizes data from MySql into schema definition
* *
@ -1157,7 +1218,7 @@ class DatabaseMySql implements DatabaseInterface
*/ */
private function normalizeAction($raw) 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; 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 /** Prepare credential to store in the database
*/ */
private function prepareCredential($params) 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. * Member variables holding the names to the SimpleDb domains needed and the database object itself.
* @access private * @access private
*/ */
private $config, $db, $domainAction, $domainCredential, $domainPhoto, private $config, $db, $domainAction, $domainActivity, $domainCredential, $domainPhoto,
$domainTag, $domainUser, $domainWebhook, $errors = array(), $owner; $domainTag, $domainUser, $domainWebhook, $errors = array(), $owner;
/** /**
@ -32,6 +32,7 @@ class DatabaseSimpleDb implements DatabaseInterface
$this->domainPhoto = $this->config->aws->simpleDbDomain; $this->domainPhoto = $this->config->aws->simpleDbDomain;
$this->domainAction = $this->config->aws->simpleDbDomain.'Action'; $this->domainAction = $this->config->aws->simpleDbDomain.'Action';
$this->domainActivity = $this->config->aws->simpleDbDomain.'Activity';
$this->domainCredential = $this->config->aws->simpleDbDomain.'Credential'; $this->domainCredential = $this->config->aws->simpleDbDomain.'Credential';
$this->domainGroup = $this->config->aws->simpleDbDomain.'Group'; $this->domainGroup = $this->config->aws->simpleDbDomain.'Group';
$this->domainUser = $this->config->aws->simpleDbDomain.'User'; $this->domainUser = $this->config->aws->simpleDbDomain.'User';
@ -200,6 +201,36 @@ class DatabaseSimpleDb implements DatabaseInterface
return false; 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 * Retrieve a credential with $id
* *
@ -709,6 +740,20 @@ class DatabaseSimpleDb implements DatabaseInterface
$this->logErrors($res); $this->logErrors($res);
return $res->isOK(); 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 * Add a new credential to the database
@ -948,6 +993,26 @@ class DatabaseSimpleDb implements DatabaseInterface
return $action; 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 * 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() public function __construct()
{ {
parent::__construct(); parent::__construct();
$this->authentication = getAuthentication();
} }
/** /**
@ -26,8 +27,9 @@ class ActionController extends BaseController
*/ */
public function create($targetId, $targetType) public function create($targetId, $targetType)
{ {
getAuthentication()->requireAuthentication(false); // does not need to be owner, anyone can comment
getAuthentication()->requireCrumb($_POST['crumb']); $this->authentication->requireAuthentication(false);
$this->authentication->requireCrumb($_POST['crumb']);
$res = $this->api->invoke(sprintf('%s.json', $this->url->actionCreate($targetId, $targetType, false)), EpiRoute::httpPost); $res = $this->api->invoke(sprintf('%s.json', $this->url->actionCreate($targetId, $targetType, false)), EpiRoute::httpPost);
$result = $res ? '1' : '0'; $result = $res ? '1' : '0';
// TODO: standardize messaging parameter // TODO: standardize messaging parameter

View file

@ -40,6 +40,11 @@ class ApiActionController extends ApiBaseController
{ {
$action = $this->action->view($id); $action = $this->action->view($id);
getPlugin()->invoke('onAction', $action); 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); 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 statusError = 500;
const statusSuccess = 200; const statusSuccess = 200;
const statusCreated = 201; const statusCreated = 201;
const statusNoContent = 204;
const statusForbidden = 403; const statusForbidden = 403;
const statusNotFound = 404; 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 string $message A friendly message to describe the operation
* @param mixed $result The result with values needed by the caller to take action. * @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) 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) 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) 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) 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) 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(); getAuthentication()->requireAuthentication();
$res = getDb()->deleteCredential($id); $res = getDb()->deleteCredential($id);
if($res) if($res)
return $this->success('Oauth credential deleted', true); return $this->noContent('Oauth credential deleted', true);
else else
return $this->error('Could not delete credential', false); return $this->error('Could not delete credential', false);
} }

View file

@ -35,7 +35,7 @@ class ApiPhotoController extends ApiBaseController
if($status) if($status)
{ {
$this->tag->updateTagCounts($res['result']['tags'], array(), 1, 1); $this->tag->updateTagCounts($res['result']['tags'], array(), 1, 1);
return $this->success('Photo deleted successfully', true); return $this->noContent('Photo deleted successfully', true);
} }
else 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. * Get a form to edit a photo specified by the ID.
* *
@ -260,11 +289,33 @@ class ApiPhotoController extends ApiBaseController
} }
elseif(isset($_POST['photo'])) 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'); $localFile = tempnam($this->config->paths->temp, 'opme');
$name = basename($localFile).'.jpg'; if(isset($_POST['filename']))
file_put_contents($localFile, base64_decode($_POST['photo'])); $name = $_POST['filename'];
$photoId = $this->photo->upload($localFile, $name, $attributes); 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) if($photoId)
@ -272,23 +323,28 @@ class ApiPhotoController extends ApiBaseController
if(isset($returnSizes)) if(isset($returnSizes))
{ {
$sizes = (array)explode(',', $returnSizes); $sizes = (array)explode(',', $returnSizes);
foreach($sizes as $size) if(!in_array('100x100xCR', $sizes))
{ $sizes[] = '100x100xCR';
$options = $this->photo->generateFragmentReverse($size); }
$hash = $this->photo->generateHash($photoId, $options['width'], $options['height'], $options['options']); else
$this->photo->generate($photoId, $hash, $options['width'], $options['height'], $options['options']); {
} $sizes = array('100x100xCR');
} }
$params = array(); foreach($sizes as $size)
if(isset($returnSizes)) {
$params = array('returnSizes' => $returnSizes); $options = $this->photo->generateFragmentReverse($size);
$photo = $this->api->invoke("/photo/{$photoId}/view.json", EpiRoute::httpGet, array('_GET' => $params)); $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); $webhookApi = $this->api->invoke('/webhooks/photo.upload/list.json', EpiRoute::httpGet);
if(!empty($webhookApi['result']) && is_array($webhookApi['result'])) if(!empty($webhookApi['result']) && is_array($webhookApi['result']))
{ {
$photoAsArgs = $photo['result']; $photoAsArgs = $photo;
$photoAsArgs['tags'] = implode(',', $photoAsArgs['tags']); $photoAsArgs['tags'] = implode(',', $photoAsArgs['tags']);
foreach($webhookApi['result'] as $key => $hook) 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'])); $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); return $this->error('File upload failure', false);
@ -358,8 +421,10 @@ class ApiPhotoController extends ApiBaseController
if($photoUpdatedId) if($photoUpdatedId)
{ {
$photo = $this->api->invoke("/photo/{$id}/view.json", EpiRoute::httpGet); $apiResp = $this->api->invoke("/photo/{$id}/view.json", EpiRoute::httpGet, array('_GET' => array('returnSizes' => '100x100xCR', 'generate' => 'true')));
return $this->success("photo {$id} updated", $photo['result']); $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); 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 * Parameters to be updated are in _POST
* This method also manages updating tag counts * This method also manages updating tag counts
* *
* @param string $id ID of the photo to be updated.
* @return string Standard JSON envelope * @return string Standard JSON envelope
*/ */
public function updateBatch() public function updateBatch()

View file

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

View file

@ -792,7 +792,7 @@ class SetupController extends BaseController
file_get_contents("{$configDir}/template.ini") 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) if(!$iniWritten)
return false; return false;

View file

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

View file

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

View file

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

View file

@ -21,7 +21,9 @@ class Epi
'cache-apc' => array('base', 'EpiCache.php', 'EpiCache_Apc.php'), 'cache-apc' => array('base', 'EpiCache.php', 'EpiCache_Apc.php'),
'cache-file' => array('base', 'EpiCache.php', 'EpiCache_File.php'), 'cache-file' => array('base', 'EpiCache.php', 'EpiCache_File.php'),
'cache-memcached' => array('base', 'EpiCache.php', 'EpiCache_Memcached.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'), 'form' => array('EpiForm.php'),
'logger' => array('EpiLogger.php'), 'logger' => array('EpiLogger.php'),
'session' => array('base', 'EpiSession.php', 'session-php', 'session-apc', 'session-memcached'), '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 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); $type = array_shift($params);
if(!file_exists($file = dirname(__FILE__) . "/{$type}.php")) 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] = new $type($params);
self::$instances[$hash]->hash = $hash; self::$instances[$hash]->hash = $hash;
return self::$instances[$hash]; return self::$instances[$hash];

View file

@ -1,46 +1,16 @@
<?php <?php
class EpiConfig class EpiConfig
{ {
private static $instance; const FILE = 'EpiConfig_File';
private $config; const MYSQL = 'EpiConfig_MySql';
private static $employ;
protected static $instances;
protected $config;
public function __construct() public function __construct()
{ {
$this->config = new stdClass; $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) public function get($key = null)
{ {
if(!empty($key)) if(!empty($key))
@ -57,22 +27,74 @@ class EpiConfig
$this->config->$key = $val; $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 * EpiConfig::getInstance
*/ */
public static function getInstance() public static function getInstance()
{ {
if(self::$instance) $params = func_get_args();
return self::$instance; $hash = md5(json_encode($params));
if(isset(self::$instances[$hash]))
return self::$instances[$hash];
self::$instance = new EpiConfig; $type = $params[0];
return self::$instance; 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() 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 {} 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() public static function getInstance()
{ {
$params = func_get_args(); $params = func_get_args();
$hash = md5(implode('.', $params)); $hash = md5(json_encode($params));
if(isset(self::$instances[$hash])) if(isset(self::$instances[$hash]))
return self::$instances[$hash]; return self::$instances[$hash];
$type = array_shift($params); $type = $params[0];
self::$instances[$hash] = new $type($params); if(!isset($params[1]))
$params[1] = array();
self::$instances[$hash] = new $type($params[1]);
self::$instances[$hash]->hash = $hash; self::$instances[$hash]->hash = $hash;
return self::$instances[$hash]; return self::$instances[$hash];
} }
@ -34,9 +36,9 @@ class EpiSession
public static function employ(/*$const*/) public static function employ(/*$const*/)
{ {
if(func_num_args() === 1) if(func_num_args() === 1)
{
self::$employ = func_get_arg(0); self::$employ = func_get_arg(0);
} elseif(func_num_args() > 1)
self::$employ = func_get_args();
return self::$employ; return self::$employ;
} }
@ -53,8 +55,9 @@ if(!function_exists('getSession'))
function getSession() function getSession()
{ {
$employ = EpiSession::employ(); $employ = EpiSession::employ();
if($employ && class_exists($employ)) $class = array_shift($employ);
return EpiSession::getInstance($employ); if($employ && class_exists($class))
return EpiSession::getInstance($class, $employ);
elseif(class_exists(EpiSession::PHP)) elseif(class_exists(EpiSession::PHP))
return EpiSession::getInstance(EpiSession::PHP); return EpiSession::getInstance(EpiSession::PHP);
elseif(class_exists(EpiSession::APC)) elseif(class_exists(EpiSession::APC))

View file

@ -8,6 +8,7 @@ if(isset($_GET['__route__']) && strstr($_GET['__route__'], '.json'))
$basePath = dirname(dirname(__FILE__)); $basePath = dirname(dirname(__FILE__));
$epiPath = "{$basePath}/libraries/external/epi"; $epiPath = "{$basePath}/libraries/external/epi";
require "{$epiPath}/Epi.php"; require "{$epiPath}/Epi.php";
require "{$basePath}/libraries/compatability.php";
require "{$basePath}/libraries/models/UserConfig.php"; require "{$basePath}/libraries/models/UserConfig.php";
Epi::setSetting('exceptions', true); Epi::setSetting('exceptions', true);
@ -20,10 +21,18 @@ Epi::init('api','cache','config','curl','form','logger','route','session','templ
$userConfigObj = new UserConfig; $userConfigObj = new UserConfig;
$hasConfig = $userConfigObj->load(); $hasConfig = $userConfigObj->load();
EpiCache::employ(getConfig()->get('epi')->cache); $configObj = getConfig();
EpiSession::employ(getConfig()->get('epi')->session); 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(); 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 // determine if this is a login endpoint
$loginEndpoint = $assetEndpoint = false; $loginEndpoint = $assetEndpoint = false;
if(isset($_GET['__route__']) && preg_match('#/user/(.*)(login|logout)#', $_GET['__route__'])) if(isset($_GET['__route__']) && preg_match('#/user/(.*)(login|logout)#', $_GET['__route__']))
@ -45,7 +54,7 @@ if($hasConfig && !$runSetup)
$runUpgrade = false; $runUpgrade = false;
if(!getUpgrade()->isCurrent()) if(!getUpgrade()->isCurrent())
$runUpgrade = true; $runUpgrade = true;
require getConfig()->get('paths')->libraries . '/routes.php'; require $configObj->get('paths')->libraries . '/routes.php';
// initializes plugins // initializes plugins
getPlugin()->load(); getPlugin()->load();
@ -68,15 +77,15 @@ else
$paths->models = "{$baseDir}/libraries/models"; $paths->models = "{$baseDir}/libraries/models";
$paths->templates = "{$baseDir}/templates"; $paths->templates = "{$baseDir}/templates";
$paths->themes = "{$baseDir}/html/assets/themes"; $paths->themes = "{$baseDir}/html/assets/themes";
getConfig()->set('paths', $paths); $configObj->set('paths', $paths);
if(!$hasConfig) 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 $configObj->get('paths')->libraries . '/routes-setup.php';
require getConfig()->get('paths')->libraries . '/routes-error.php'; require $configObj->get('paths')->libraries . '/routes-error.php';
require getConfig()->get('paths')->controllers . '/SetupController.php'; require $configObj->get('paths')->controllers . '/SetupController.php';
getConfig()->load(sprintf('%s/html/assets/themes/%s/config/settings.ini', dirname(dirname(__FILE__)), getTheme()->getThemeName())); $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 // Before we run the setup in edit mode, we need to validate ownership
$userObj = new User; $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; return $url;
} }
public function setMode($mode)
{
$this->mode = $mode;
return $this;
}
private function addAsset($src, $type) private function addAsset($src, $type)
{ {
// verify this file exists // verify this file exists

View file

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

View file

@ -30,6 +30,9 @@ class Photo extends BaseModel
$this->user = $params['user']; $this->user = $params['user'];
else else
$this->user = new User; $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'])) if(isset($photo['pathBase']))
unset($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); $photo['url'] = $this->getPhotoViewUrl($photo);
return $photo; return $photo;
@ -453,6 +460,7 @@ class Photo extends BaseModel
} }
$tagObj = new Tag; $tagObj = new Tag;
$attributes = $this->whitelistParams($attributes); $attributes = $this->whitelistParams($attributes);
$filenameOriginal = $name;
$paths = $this->generatePaths($name); $paths = $this->generatePaths($name);
$exiftran = $this->config->modules->exiftran; $exiftran = $this->config->modules->exiftran;
if(is_executable($exiftran)) if(is_executable($exiftran))
@ -536,6 +544,7 @@ class Photo extends BaseModel
'exifExposureTime' => @$exif['exposureTime'], 'exifExposureTime' => @$exif['exposureTime'],
'exifISOSpeed' => @$exif['ISO'], 'exifISOSpeed' => @$exif['ISO'],
'exifFocalLength' => @$exif['focalLength'], 'exifFocalLength' => @$exif['focalLength'],
'filenameOriginal' => $filenameOriginal,
'width' => @$exif['width'], 'width' => @$exif['width'],
'height' => @$exif['height'], 'height' => @$exif['height'],
'dateTaken' => $dateTaken, 'dateTaken' => $dateTaken,
@ -712,7 +721,7 @@ class Photo extends BaseModel
$returnAttrs = array(); $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, $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, '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); $patterns = array('exif.*' => 1,'date.*' => 1,'path.*' => 1);
foreach($attributes as $key => $val) foreach($attributes as $key => $val)
{ {

View file

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

View file

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

View file

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

View file

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

View file

@ -6,9 +6,38 @@ class UserConfig
public function __construct($params = null) public function __construct($params = null)
{ {
if(isset($params['config'])) if(isset($params['config']))
{
$this->config = $params['config']; $this->config = $params['config'];
}
else 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(); $this->config = getConfig();
}
if(isset($params['utility'])) if(isset($params['utility']))
$this->utility = $params['utility']; $this->utility = $params['utility'];
@ -22,7 +51,8 @@ class UserConfig
$configFile = $this->getConfigFile(); $configFile = $this->getConfigFile();
if(!$configFile) if(!$configFile)
return false; return false;
return parse_ini_file($configFile, true); $iniString = $this->config->getString($configFile);
return parse_ini_string($iniString, true);
} }
public function writeSiteSettings($settings) public function writeSiteSettings($settings)
@ -41,7 +71,8 @@ class UserConfig
public function load() 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(); $configFile = $this->getConfigFile();
// backwards compatibility for 1.2.1 -> 1.2.2 upgrade // 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 // we need to load the deps to get the theme modules
require $this->config->get('paths')->libraries . '/dependencies.php'; 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 true;
} }
return false; 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() protected function getUtility()
{ {
if(isset($this->utility)) if(isset($this->utility))
@ -78,7 +114,7 @@ class UserConfig
private function getConfigFile() private function getConfigFile()
{ {
$configFile = sprintf('%s/userdata/configs/%s.ini', $this->basePath, $this->host); $configFile = sprintf('%s/userdata/configs/%s.ini', $this->basePath, $this->host);
if(!file_exists($configFile)) if(!$this->config->exists($configFile))
return false; return false;
return $configFile; return $configFile;
} }

View file

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

View file

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

View file

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

View file

@ -11,12 +11,12 @@ class HelloWorldPlugin extends PluginBase
parent::__construct(); parent::__construct();
} }
public function onBody() public function onBodyBegin($params = null)
{ {
parent::onBody(); parent::onBodyBegin();
} }
public function onView() public function onView($params = null)
{ {
parent::onView(); 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]+)/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) 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 * Photo endpoints
* All photo endpoints follow the same convention. * 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()->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('/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()->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()->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]+)/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) 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 = new Credential(array('utility' => $utility));
$this->credential->sendHeadersOnError = false; $this->credential->sendHeadersOnError = false;
$this->credential->reset();
$this->token = 'abcdefghijklmnopqrstuvwxyz0123456789'; $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); $params = array('user' => new FauxObject, 'utility' => new stdClass, 'url' => new stdClass, 'image' => new FauxObject);
$this->photo = new Photo($params);; $this->photo = new Photo($params);;
$config = new stdClass; $config = new stdClass;
$config->site = new stdClass;
$config->site->allowOriginalDownload = 1;
$secrets = new stdClass; $secrets = new stdClass;
$secrets->secret = 'secret'; $secrets->secret = 'secret';
$config->secrets = $secrets; $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'); $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() public function testDeleteCouldNotGetPhoto()
{ {
$db = $this->getMock('db', array('getPhoto')); $db = $this->getMock('db', array('getPhoto'));
@ -280,10 +357,11 @@ class PhotoTest extends PHPUnit_Framework_TestCase
{ {
// This *should* work // This *should* work
$now = time(); $now = time();
$ym = date('Ym');
$res = $this->photo->generatePaths('foobar'); $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->assertNotEquals("/original/{$ym}/{$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->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/201201/{$now}-foobar", $res['pathBase'], 'base 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() public function testGenerateUrlOriginal()

View file

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

View file

@ -35,7 +35,7 @@ class UserConfigTest extends PHPUnit_Framework_TestCase
mkdir("{$this->userConfigDir}/userdata/configs"); mkdir("{$this->userConfigDir}/userdata/configs");
} }
$params = array('utility' => new FauxObject); $params = array('utility' => new FauxObject, 'config' => new FauxObject);
$_SERVER['HTTP_HOST'] = 'example.com'; $_SERVER['HTTP_HOST'] = 'example.com';
$this->userConfig = new UserConfigWrapper($params); $this->userConfig = new UserConfigWrapper($params);
$this->userConfig->inject('basePath', $this->userConfigDir); $this->userConfig->inject('basePath', $this->userConfigDir);
@ -54,7 +54,15 @@ class UserConfigTest extends PHPUnit_Framework_TestCase
public function testGetSiteSettingsWithoutSections() 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(); $res = $this->userConfig->getSiteSettings();
$expected = array('foo' => 'bar'); $expected = array('foo' => 'bar');
$this->assertEquals($expected, $res); $this->assertEquals($expected, $res);
@ -62,7 +70,15 @@ class UserConfigTest extends PHPUnit_Framework_TestCase
public function testGetSiteSettingsWithSections() 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(); $res = $this->userConfig->getSiteSettings();
$expected = array('stuff' => array('foo' => 'bar')); $expected = array('stuff' => array('foo' => 'bar'));
$this->assertEquals($expected, $res); $this->assertEquals($expected, $res);
@ -70,12 +86,24 @@ class UserConfigTest extends PHPUnit_Framework_TestCase
public function testGetSiteSettingsDNE() 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(); $res = $this->userConfig->getSiteSettings();
$this->assertFalse($res); $this->assertFalse($res);
} }
public function testWriteSiteSettingsDNE() 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')); $res = $this->userConfig->writeSiteSettings(array('foo'));
$this->assertFalse($res); $this->assertFalse($res);
} }